Source file: /~heha/mb-iwp/Bergwerk/fba-rpi-230421.zip/fba3.cpp

#ifdef DEBUGPIN
# include "heha_gpio.h"
#endif
#include "heha_tui.h"
#include <signal.h>	// usleep(), ...
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <semaphore.h>
#include <cerrno>	// errno
#include <cstdio>	// sscanf()
#include <cstring>	// strerror()
#include <chrono>	// std::chrono
//#include <ctime>
/* I²C-Abfrage- und Steuerprogramm (TUI-Version) für
Fernbedien-Adapter der BGA-Fahrzeuge mit ATmega328 (etwa Arduino Uno)
 230207 heha
*/

// Tabellenartige Bildschirmaufteilung; Spaltenbreiten:
enum{
 R1C1 = 18,	// "  Ultraschall-Echo"
 R1C2 = 20,
 R1C3 = 17,	// " Licht Fahrerhaus"
 R2C1 = 12,	// " Kanaldaten "
};

/******************
 * Hex/ASCII-Dump *
 ******************/

// Hex-Dump: Gesamtbreite = (l0+l1+l2)*3
static void dumpHex(byte const*p,unsigned l0, unsigned l1, unsigned l2,const ChrColor*colors=0) {
 for (unsigned i=0; i<l0; i++) heha::print("   ");
 if (l1) for (unsigned i=0;;) {
  if (colors) colors[i].out();
  heha::print("%02X",p[i]);
  if (colors) ChrColor::clr();
  if (++i==l1) break;
  putchar(' ');
 }
 for (unsigned i=0; i<l2; i++) heha::print("   ");
}
// ASCII-Dump: Gesamtbreite = l0+l1+l2
static void dumpAsc(byte const*p,unsigned l0, unsigned l1, unsigned l2) {
 auto printable=[](char c) {return 32<=c && c<127 ? c : '.';};
 // TODO: UTF8-Sonderbehandlung
 for (unsigned i=0; i<l0; i++) heha::print(" ");
 for (unsigned i=0; i<l1; i++) heha::print("%c", printable(p[i]));
 for (unsigned i=0; i<l2; i++) heha::print(" ");
}
// Hexdump: Adresse und bis zu 16 Bytes pro Zeile
static void dump(void const*pv,size_t l,unsigned a=0, unsigned linelen=16, unsigned l0=0) {
 auto p=reinterpret_cast<byte const*>(pv);
 while (l) {
  unsigned l1 = linelen-l0; if (l1>l) l1=(unsigned)l;	// "Fleisch"
  unsigned l2 = linelen-l1-l0;
  csi(1,36,'m');
  heha::print("%04X ",a-l0);	// Adresse türkis
  csi(32,'m');
  dumpHex(p,l0,l1,l2);		// Hexbytes grün
  putchar(' ');
  csi(33,'m');
  dumpAsc(p,l0,l1,l2);		// ASCII gelb
  p+=l1; l-=l1; a+=l1; l0=0;	// weitere Zeilen von vorn füllen
  nextline();
 }
}


/*********************
 * I²C-Kommunikation *
 *********************/
namespace OneWire{
 byte crc8(const byte*p,byte l) {
  byte r = 0;
  const byte poly = 0x8C;
  do{
   r^=*p++;
   byte bits=8; do{
    if (r&1) r = r>>1 ^ poly;
    else r>>=1;
   }while(--bits);
  }while (--l);
  return r;
 }
}

// Die Funktionen dieser Klasse sind geschwätzig bei Fehler
static struct I2C{
 int f;		// I²C-Handle
 ::sem_t*sem;
 char semname[16];
 I2C():f(-1) {}	// Handle ungültig initialisieren
 bool open(byte,byte);
 bool send(const void*p,int l,const char*caller) const {
  return queryLong(p,l,0,0,caller);
//  dump(p,l);
//  lock();
//  bool ret=write(p,l,caller);
//  unlock();
//  return ret;
 }
 bool queryLong(const void*s,int sl,void*r,int rl,const char*caller) const {
  if (sl<0 || rl<0) return false;	// Negative Längen: Fehler
  if (sl && !s || rl && !r) return false;	// Länge ohne Zeiger: Fehler
  if (!sl && !rl) return true;	// Durchläufer: Nichts zu tun
  auto sb = reinterpret_cast<const byte*>(s);
  auto rb = reinterpret_cast<byte*>(r);
  if (!rl) return queryShort(sb,sl,0,0,caller);
  bool ret = true, first = true;
  byte o = sl>=2 ? sb[1] : 0;
  while (rl) {
   byte buf[65];
   int ll = rl>64 ? 64 : rl;	// max. 64 Bytes + CRC lesen
   byte sn[2]={sb[0],o};
   if (queryShort(first?sb:sn,first?sl:2,buf,ll+1,caller)
   && checkCRC(buf,ll,caller)) {
    memcpy(rb,buf,ll);
    rb+=ll;
    rl-=ll;
    o+=ll;
   }else return false;
   first=false;
  }
  return ret;
 }
 bool query(byte b,void*q,int ql,const char*func) const{
  return queryLong(&b,1,q,ql,func);
 }
 void close() {::close(f); f=-1; sem_unlink(semname); sem=0;}
private:
// bool write(const void*,int,const char*) const;
 bool queryShort(const byte*,int,byte*,int,const char*) const;
 bool checkCRC(const byte*,int,const char*) const;
 int lock() const;
 int unlock() const {return ::sem_post(sem);}
}i2c;

int I2C::lock() const {
 timespec ts;
 clock_gettime(CLOCK_REALTIME,&ts);
 ts.tv_nsec+=100'000'000;	// 100 ms;
 if (ts.tv_nsec>=1'000'000'000) {	// 1 s überschritten?
  ts.tv_nsec-=1'000'000'000;
  ts.tv_sec++;
 }
 return ::sem_timedwait(sem,&ts); // Was für ein Linux-Mumpitz!
// Bei springender Uhrzeit kann diese Funktion ewig warten!
// Denn welche Art von <clock> sem_timedwait() will ist nicht dokumentiert.
}

bool I2C::open(byte nr,byte i2cslave) {
 char devname[32];
 heha::snprint(devname,sizeof devname,"/dev/i2c-%u",nr);
 f=::open(devname,O_RDWR);
 if (f<0) {
  heha::print(stderr,"Kann %s nicht öffnen: %d: %s",
    devname,errno,strerror(errno));
  nextline();
  return false;
 }
 if (ioctl(f,I2C_SLAVE,i2cslave)<0) {
  close();
  heha::print(stderr,"Kann I²C-Slave-Adresse %02x (AVR:%#02X) nicht setzen, errno=%d: %s",
    i2cslave,i2cslave<<1,errno,strerror(errno));
  nextline();
  return false;
 }
// "Wohlgeformter" Name aus I²C-Nummer und i2cdetect-mäßiger Slave-Adresse.
// Muss im Python-Programm übereinstimmen, wenn beide Programme
// (Prozesse) nebeneinander laufen dürfen sollen.
// Dazwischen funkende I²C-Aktionen auf andere Slaves
// (bspw. Echtzeituhr, Inertialsensor) stören nicht,
// sofern diese der Software-Treiber gefälligst atomar behandelt.
 heha::snprint(semname,sizeof semname,"/i2c%u.%02x",nr,i2cslave);
 sem = sem_open(semname,O_CREAT,0660,1);
 if (!sem) {
  close();
  heha::print(stderr,"Kann Semaphore %s nicht erstellen oder öffnen, errno=%d: %s!",
    semname,errno,strerror(errno));
  nextline();
  return false;
 }
 return true;
}

static void Fehlermeldung(const char*typ, int ist, int soll, const char*caller) {
 heha::print(stderr,typ);
 if (ist<0) heha::print(stderr,
   " (%d Bytes): errno=%d: %s,",
   soll,errno,strerror(errno),caller);
 else heha::print(stderr,
   ": %d≠%d Bytes!",
   ist,soll);
 heha::print(stderr," Aufrufer=%s",caller);
 nextline();
}

bool I2C::queryShort(const byte*sb,int sl,byte*rb,int rl,const char*caller) const{
 if (sl<0 || rl<0) return false;
 if (sl && !sb || rl && !rb) return false;
 if (!sl && !rl) return true;
 if (rl>65) return false;
 bool ret=true;
 lock();
#ifdef DEBUGPIN
 gpio::clr(DEBUGPIN);
#endif
 if (sl) {
  int versuche=5, r=0;
  do{
   using namespace std::chrono;
   auto start = steady_clock::now();
   while (steady_clock::now()-start<100us);
   r=::write(f,sb,sl);
  } while (r<0 && (errno==121 || errno==5) && --versuche);
  if (r!=sl) {
   ret=false;
#ifdef DEBUGPIN
   gpio::set(DEBUGPIN);
#endif
   Fehlermeldung("Schreibfehler",r,sl,caller);
  }
 }
 if (rl && ret) {
  int versuche=5, r=0;
  do{
//Dem AVR etwas Zeit geben
   using namespace std::chrono;
   auto start = steady_clock::now();
   while (steady_clock::now()-start<100us);
#ifdef DEBUGPIN
   if (r<0) gpio::clr(DEBUGPIN);
#endif
// Der Fehler 121 ist Timeout durch unresponsiven AVR, d.h.
// er hat noch mit Rechnen zu tun, macht Clock-Stretch
// und antwortet dem Hardwaretreiber zu langsam.
// 230726: Beim von Felix eingerichteten Raspberry Pi 4
// Compute Module kommt stattdessen Fehlerkode 5.
   r=::read(f,rb,rl);
#ifdef DEBUGPIN
// Clock-Stretch-Versuche ebenfalls am Debugpin oszillografieren
   if (r<0) gpio::set(DEBUGPIN);
#endif
  } while (r<0 && (errno==121 || errno==5) && --versuche);
  if (r!=rl) {
   ret=false;
#ifdef DEBUGPIN
// Nur für den (seltenen) Fall dass r≥0 aber r≠rl ist
   gpio::set(DEBUGPIN);
#endif
   Fehlermeldung("Lesefehler",r,rl,caller);
  }
 }
 unlock();
 return ret;
}

bool I2C::checkCRC(const byte*rb,int rl,const char*caller) const{
 if (rl>64) {
  heha::print(stderr,"%s:%d: Interner Programmfehler! Aufrufer=%s",__FILE__,__LINE__,caller);
  nextline();
  return false;
 }
 byte crc=~OneWire::crc8(rb,rl);
 if (crc!=rb[rl]) {
#ifdef DEBUGPIN
  gpio::set(DEBUGPIN);	// Auch CRC-Fehler anzeigen
#endif
  heha::print(stderr,"%s: CRC-Fehler, %02X!=%02X! Aufrufer=%s",__FUNCTION__,crc,rb[rl],caller);
  nextline();
  return false;
 }
 return true;
}

static void writeaction(unsigned a, const char*pw, unsigned l) {
 char*cmd=new char[2+l];
 cmd[0]=char(a>>8);
 cmd[1]=char(a);
 memcpy(cmd+2,pw,l);
 i2c.send(cmd,2+l,__FUNCTION__);
}

static void emitLinks2(const char*title) {
 if (screen.paintAll) heha::print("║%*s │ ",R2C1-1,title); else csi(R2C1+3,'C');
}
static void frameRechts() {
 if (screen.paintAll) {csi(screen.w,'C'); heha::print("║");}
// Bewirkt keinen Zeilenumbruch und kein Schreiben außerhalb des Terminalfensters
}

/****************
 * Ebene 0: Uhr *
 ****************/
// War ursprünglich als Echtzeituhr für den Raspberry geplant,
// aber das mit dem Uhrenquarz und der PLL hat nie ordentlich funktioniert,
// es gab (auch bei unbeeinflusstem OSCCAL) immer wieder Probleme
// mit der seriellen Schnittstelle sowie Instabilitäten beim PWM-Output.
// 230221: Vermutlich war es (noch) ein anderes Problem,
// denn die gelegentlichen Prüfsummenfehler sind immer noch da
// und stören nicht mehr.
// Die verwendete Raspberry-USV hat einen (ungenutzten) Echtzeituhr-Chip.

static struct __attribute__((packed)) L0{
 byte 	  ms10;	// Hundertstel-Sekunde
 uint32_t losec;// Ganze Sekunden seit 1.1.1970
 char     hisec;// Überlauf-Byte (kommt erst im nächsten Jahrhundert)
 bool query() {return i2c.query(0x60,this,sizeof*this,"L0::query");}
 void show() const;
 L0() = default;
 L0(std::chrono::system_clock::time_point t) {set(t,false);}
 static int find(const Mouse&);
 void set(std::chrono::system_clock::time_point,bool writeout=true);
 operator char*() {return (char*)this;}
}l0;

void L0::show() const{
 struct LONGLONG{	// Nur für Little Endian (Big Endian ist ausgestorben)
  uint32_t lo;
  int32_t hi;
  operator int64_t() const {return *(int64_t*)this;}
 }s = {losec,hisec};
 ::time_t time = s;
 tm t;
 ::localtime_r(&time,&t);
 heha::print("%4d-%02u-%02u %02u:%02u:%02u,%02u",	// 21 Zeichen
  t.tm_year+1900,
  t.tm_mon+1,
  t.tm_mday,
  t.tm_hour,
  t.tm_min,
  t.tm_sec,
  ms10);
}

int L0::find(const Mouse&mouse) {
 if (mouse.y!=13) return -1;
 int c = mouse.x-51;
 if (c<0) return -1;
 if (c>23) return -1;
 return 0;
}

void L0::set(std::chrono::system_clock::time_point t, bool writeout) {
 using namespace std::chrono;
 milliseconds ms = duration_cast<milliseconds>(t.time_since_epoch());
 int64_t      s  = duration_cast<seconds>(ms).count();
 ms10  = ms.count()/10%100;	// 0..99
 losec = s;
 hisec = s>>32;	// 5 Bytes für Sekunden genügen auf sehr lange Sicht
 if (writeout) writeaction(0x6000,(char*)this,sizeof*this);
}

/*****************************************
 * Ebene 1: EEPROM (Konfigurationsdaten) *
 *****************************************/

template<byte N>struct __attribute__((packed)) CalibData{
 byte n;
 struct __attribute__((packed)) A{
  int16_t adcvalue;
  int8_t per125;
 }a[N];
};

static struct __attribute__((packed)) L1{
 byte lamps,
      spiaddr;	// 0xBA: Radlader, 0xBC: Muldenkipper
 CalibData<11>lenkung;
 CalibData<3>hubarm,löffel;
 int16_t t0,ts,rsv[2];		// für (nutzlosen) Temperatursensor
 struct __attribute__((packed)) PID{
  char Kp[2],Ki[2],Kd[2],	// PID-Koeffizienten je nach Richtung (normalerweise gleich)
  	Z[4],	// Spreizung: lo-hi festklebend, lo-hi losgerissen
        L[4];	// Limit Integralwert, lo-hi je nach Richtung
  byte T,M;	// Zeit; Schwellwert zur "Losgerissen"-Erkennung (> Rauschen)
 }pi[4];	// PI-Reglerdaten
 struct __attribute__((packed)) Fz{
  int8_t sp[2];	// Maximalgeschwindigkeit in cm/s (zurück/vorwärts)
  int16_t onestep;	// 0,1 µm pro Schritt: 2553 beim Muldenkipper, positiv
  int16_t q[4];	// Winkeländerung pro Schritt für 10°, 20°, 30° und max. Lenkwinkel, positiv
  int16_t rsv[2];
 }fz;		// Fahrzeugparameter
 byte debug[32];
 bool query() {return i2c.query(0x61,this,sizeof*this,"L1::query");}
 bool queryDebug() {byte q[]={0x61,debug-(byte*)this}; return i2c.queryLong(q,2,&debug,sizeof debug,"L1::queryDebug");}
 void show() const;
 static int find(const Mouse&);
}l1;
static_assert(sizeof l1==0xB0);

void L1::show() const{
 static byte odebug[sizeof debug];
 static ChrColor hilite[sizeof debug];
 for (unsigned i=0; i<sizeof debug; i++) {
  if (odebug[i]!=debug[i]) {
   odebug[i]=debug[i];
   hilite[i].b=33;	// (33-17) / 10Hz = 2 s
  }else if (hilite[i].b && --hilite[i].b==16) hilite[i].b=0;
 }
 emitLinks2("Debug");
 byte len=(screen.w-R2C1-3)/3;	// Anzahl Bytes in Zeile bei Fensterbreite
 if (len>sizeof debug) len=sizeof debug;
 dumpHex(debug,0,len,0,hilite);
 frameRechts();
 nextline();
}

int L1::find(const Mouse&mouse) {
 if (mouse.y!=15) return -1;
 int c = mouse.x-(R2C1+3);
 if (c<0) return -1;
 if (c%3==2) return -1;
 return c/3;
}

/************************
 * Ebene 4: A/D-Wandler *
 ************************/
int MulDiv(int a, int b, int c) {return (long long)a*b/c;}
unsigned uMulDiv(unsigned a, unsigned b, unsigned c) {
	return (unsigned long long)a*b/c;
}

static struct L4{
 int16_t lin[7];	// "Menschenlesbare" linearisierte Werte
 uint8_t flags,lamps;
 int16_t raw[8];
 uint32_t kmz;
 bool query() {return i2c.query(0x64,this,sizeof*this,"L4::query");}
 void show() const;
 static int find(const Mouse&);		// Gesetzt werden diverse Zähler
 static void set(char,char);	// Flags setzen als Firmware-Hintertür
}l4;

static const char*utf8next(const char*s) {
 while (*++s<-64);	// Trailbytes (ohne weitere Prüfung) übergehen
 return s;
}

static void emitHorizontalDivider(const char*s,...) {
 va_list va;
 va_start(va,s);
 while (*s) {
  auto p=utf8next(s);
  fwrite(s,p-s,1,stdout);
  if (!*p) break;
  auto q=utf8next(p);
  int count=va_arg(va,int);
  for (int i=0; i<count; i++) fwrite(p,q-p,1,stdout);
  s=q;
 }
 va_end(va);
}

static void emitLinks(byte k, const char*name,const char*unit,short value,unsigned short raw) {
 if (screen.paintAll) {
  heha::print("║");
  byte spc = R1C1;
  if (k) {
   csi(1,'m');
   heha::print("K%u",k);
   csi('m');
   spc-=2;
  }
  heha::print("%*s:",spc,name);
 }else{
  csi(1+R1C1+1,'C');
 }
 heha::print("%7,2hj %-5s(%04X)",value,unit,raw);
 if (screen.paintAll) heha::print(" ║"); else csi(2,'C');
}

static void emitRechts(const char*name,unsigned selector,const char*sel) {
 if (screen.paintAll) heha::print("%*s: ",R1C3,name); else csi(R1C3+2,'C');
 auto flags=selector; selector&=7;
 char fmt[64];
 heha::snprint(fmt,sizeof fmt,"%%8(%s) ",sel);
 static const ChrColor clr[2]={{0,1},{0,2}};
 if (!(flags&0x10)) clr[flags&0x40?0:1].out();	// Bit 4 = unbunt, Bit 6 = rot statt grün
 heha::print(fmt,selector);	// 9 Zeichen breit
 if (!(flags&0x10)) ChrColor::clr();
 if (!(flags&0x20)) heha::print(" (%u)",selector);	// Bit 5 = keine Zahl dahinter
 frameRechts();
 nextline();
}

void L4::show() const{
 if (screen.paintAll) {
  emitHorizontalDivider("╔═╦═╗",R1C1+1+R1C2,screen.w-R1C2-R1C1-4);
 }
 nextline();
 const bool isMk = l1.spiaddr==0xBC;	// ist Muldenkipper
 emitLinks(1,"Lenkung",		"°",	lin[0],raw[0]);
	emitRechts("Zündung", 		flags>>3&1,	"aus|ein");
 emitLinks(2,"Geschwindigkeit",	"cm/s",	lin[1],raw[6]);
 	emitRechts("Rundumleuchte", 	flags>>5,	"aus|ein|langsam|blitzend|blinkend");
 emitLinks(3,isMk?"Ladefläche":"Hubarm", "°",		lin[2],raw[1]);
 	emitRechts("Licht Fahrerhaus",	lamps>>5,	"aus|ein|-*|....--|blinkend");
 emitLinks(4,isMk?"Verwindung":"Löffel", "°",		lin[3],raw[2]);
 	emitRechts("Licht Fahrzeug", 	lamps&7,	"aus|vorn|beide|aus|hinten|beide");
 emitLinks(0,"Traktionsakku",	"V",	lin[4],raw[3]);
 	emitRechts("Blinklicht", 	lamps>>3&3,	"aus|blitzend|blinkend");
 emitLinks(0,"Chiptemperatur",	"°C",	lin[5],raw[4]);
 	emitRechts(isMk?"Blinker":"Blinkgeräusch", 	flags>>4&1, "aus|ein");
 emitLinks(0,"Ultraschall-Echo", "dm",	lin[6],raw[5]);
 	emitRechts(isMk?"Rückfahrtaster":"funktionslos",flags&1|0x30, "aus|ein");
 if (screen.paintAll) heha::print("║%*s:",R1C1-5,"Fahrstrecke"); else csi(R1C1-3,'C');
 heha::print("%'12,2u m (%08X)║",
        uMulDiv(kmz,l1.fz.onestep,100000U),
        kmz);
        emitRechts("Notaus",		flags>>1&3|0x40,"nein|ja|intern|remote");
 if (screen.paintAll) {
  emitHorizontalDivider("╠═╤═╩═╣",R2C1,R1C1+R1C2-R2C1,screen.w-R1C1-R1C2-4);
 }
 nextline();
}

int L4::find(const Mouse&mouse) {
 char relX=screen.mouse.x-(1+R1C1+1+R1C2+1+R1C3+2);
 if ((unsigned)relX<9) switch (screen.mouse.y) {
  case 1: return 0;	// Zündung
  case 2: return 5;	// Rundumleuchte
  case 3: return 2;	// Licht Fahrerhaus, nicht beim Muldenkipper
  case 4: return 1;	// Licht Fahrzeug
  case 5: return 3;	// Blinklicht
  case 6: return 4;	// Blinkgeräusch, beim Muldenkipper Blinker (links+rechts gleich)
  case 8: return 6;	// Externes Zwangs-Notaus
 }
 if (12<=mouse.x && mouse.x<40 && mouse.y==8) return 7;	// Kilometerzähler rücksetzen
 return -1;
}

void L4::set(char k, char count) {
 char buf[]={0x64,k,count};	// Schaltet einen der Schalter (Zündung, Licht, Licht, Rundum) weiter,
 i2c.send(buf,sizeof buf,"L4::set");	// um das Timing kümmert sich die Firmware (1 Puls reicht dem jeweiligen µC)
}

/*******************
 * Ebene 2: Servos *
 *******************/
static struct L2{	// Servos
 char pwms[9];
 byte pwmok;
 bool query() {return i2c.query(0x62,this,sizeof*this,"L2::query");}
 void show() const;
 static void showRuler();
 enum{
  REL=0x72,
  ABS=0x62,
 };
 static int find(const Mouse&);
 static void move(char, char, char prefix=REL);
 static void set(char k, char to) {move(k,to,ABS);}
}l2;

void L2::showRuler() {
 emitLinks2("Kanaldaten");
 if (screen.paintAll) for (int i=0; i<10; i++) {
  if (i<4) csi(1,'m');	// Die Kanäle mit Steuerknüppeln hervorheben
  heha::print(" K%2u  ",i+1);
  if (i<4) csi('m');
 }
 frameRechts();
 nextline();
}

void L2::show() const{
 emitLinks2("Servos");
 for (int i=0; i<sizeof pwms; i++) {	// 6x9 = 54 Spalten
  csi(i==4||i==8?100:44,'m');	// 44 = Hintergrund blau weil editierbar
 				// 100 = grau weil nicht sinnvoll editierbar
  heha::print("%4hhj ",pwms[i]);	// max. '-127' = 4 Spalten
  csi('m');
  heha::print(" ");
 }
 heha::print("%4u",pwmok);
 frameRechts();
 nextline();
}

int L2::find(const Mouse&mouse) {
 if (mouse.y!=11) return -1;	// 10. Zeile von oben
 int c = mouse.x-R2C1-3;	// Breite für Titel
 if (c<0) return -1;
 if (c>=9*6) return -1;
 if (c%6==5) return -1;
 return c/6;
}

void L2::move(char k, char by, char prefix) {
 char buf[]={prefix,k,by};	// Fernbedienung stets(!) entreißen; Bit 4: Addieren
 i2c.send(buf,sizeof buf,"L2::send");
}

/***********************
 * Ebene 3: Controller *
 ***********************/

static struct L3{	// Fernbedienung
 char values[10];
 bool query(bool steal=false);
// Mit <true> wird die mikrocontroller-interne Weiterleitung
// von Controller-Meldungen zum PWM-Ausgang für reichlich 1 s gesperrt.
// Dies ist dazu gedacht, dass der I²C-Master den Controller
// (durch zyklische Abfrage) kapert und irgendetwas anderes mit den Daten macht.
// Wird in diesem Programm nicht benutzt.
 void show() const;
}l3;

bool L3::query(bool steal) {
 return i2c.query(steal?0x73:0x63,this,sizeof*this,"L3::query");
}

void L3::show() const{
 emitLinks2("Controller");
 for (int i=0; i<sizeof values; i++) {	// 6x10 = 60 Spalten
  heha::print("%4hhj  ",values[i]);	// max. '-127' = 4 Spalten
 }
 frameRechts();
 nextline();
}

/*******************
 * Ebene 5: Regler *
 *******************/

static struct Regler{	// Regler
 char so;	// Sollwert
 struct F{
  byte vmax:7;	// Geschwindigkeit (beim Fahrregler Beschleunigung??)
  bool losgerissen:1;
 }f;
 uint16_t to;	// Timeout
 int16_t eΣ;	// Integralwert
 int16_t eΩ;	// Letzter Wert (für Differenzbildung), geglättet
 static bool query();
 static void showSoll();	// ohne Newline
 static void showSpeed();
 enum{
  REL=0x75,
  ABS=0x65,
 };
 static void move(char,char,char cmd=REL);
 static void set(char k, char c);
 static MiniEdit*edit[4];
 static void createWindows();
 void showReglerData() const;
 static char firstActive();
}regler[4];

MiniEdit*Regler::edit[4];

void Regler::createWindows() {
 for (int i=0; i<4; i++) edit[i]=new MiniEdit(0x016500|i,R2C1+3+i*6,13);	// nullbasiert
}

bool Regler::query() {return i2c.query(0x65,regler,sizeof regler,"Regler::query");}

static void showText(MiniEdit*ed,const char*fmt,...) {
 if (Window::focus!=ed) {
  va_list va;
  va_start(va,fmt);
  heha::snvprint(ed->text,31,fmt,va);
  va_end(va);
  ed->onNewText();
 }
 ed->paint();
}

void Regler::showSoll() {
 emitLinks2("Regler");
 for (int i=0; i<4; i++) {
  showText(edit[i],"%hhj",regler[i].so);	// max. '-127' = 4 Spalten
//  csi(44,'m');
//  csi('m');
  heha::print(i==1?" ":"°");
 }	// kein frameRechts, Uhr kommt daneben
}

void Regler::move(char k, char by, char prefix) {
 char buf[]={prefix,k,by};
 i2c.send(buf,sizeof buf,"Regler::send");	// TODO (Firmware): Von NaN aus ist „relativ“ vom Istwert
}

void Regler::set(char k, char c) {
 move(k,c,ABS);
}

char Regler::firstActive() {
 for (char i=0; i<4; i++) if (regler[i].so!=heha::cNaN) return i;
 return heha::cNaN;
}

void Regler::showReglerData() const{
 if (screen.paintAll) csi('K');
 emitLinks2("ReglerData");
 heha::print("so=%hhj, v=%u, %(festklebend|losgerissen), to=%,2u, eΣ=%,2hj, eΩ=%,2hj  ",
 	so,f.vmax,f.losgerissen,to<<1,eΣ*100>>8,eΩ);
 frameRechts();
 nextline();
}

/**********************
 * Ebene 6: Odometrie *
 **********************/

static struct Odo{
 int32_t x,y,α;	// Neu: α = 256 Vollkreise und 24 Nachkomma-Bits
 int16_t q,λ;
 bool query() {return i2c.query(0x66,this,sizeof*this,"Odo::query");}
 void show() const;
 enum{
  REL=0x76,
  ABS=0x66,
 };
 static void move(char,char,char cmd=REL);
 static void set(char k, char c);
 static MiniEdit*edit[5];
 static void createWindows();
}odo;
static_assert(sizeof odo==16);

MiniEdit*Odo::edit[5];

void Odo::createWindows() {
 edit[0]=new MiniEdit(0x046600,R2C1+5,		14,9,1,0,pal1);
 edit[1]=new MiniEdit(0x046604,R2C1+5+13,	14,9,1,0,pal1);
 edit[2]=new MiniEdit(0x046608,R2C1+5+13+13,	14,9,1,0,pal1);
 edit[3]=new MiniEdit(0x02660C,R2C1+5+13+13+13,	14,7,1,0,pal1);
 edit[3]->style|=1<<Window::DISABLED;
 edit[4]=new MiniEdit(0x02660E,R2C1+5+13+13+13+11,14,7,1,0,pal1);
 edit[4]->style|=1<<Window::DISABLED;
}

void Odo::show() const{
 emitLinks2("Odometrie");
 if (screen.paintAll) heha::print("x="); else csi(2,'C');
 showText(edit[0],"%,3j",x/10000);	// max. '-127' = 4 Spalten
 if (screen.paintAll) heha::print("m y="); else csi(4,'C');
 showText(edit[1],"%,3j",y/10000);
 if (screen.paintAll) heha::print("m α="); else csi(4,'C');
 showText(edit[2],"%,3j",(α>>8)*1000>>16);
 if (screen.paintAll) heha::print("U q="); else csi(4,'C');
 showText(edit[3],"%hj",q);
 if (screen.paintAll) heha::print("µ λ="); else csi(4,'C');
 showText(edit[4],"%,2hj",λ);
 if (screen.paintAll) heha::print("°");
 frameRechts();
 nextline();
}

/*************************
 * Ebene 7: Fahraufträge *
 *************************/

struct __attribute__((packed)) L7{
 char soll[4],speed[4];
 int32_t s;
 uint16_t w;
 L7();
 bool fromString(const char*);
};

L7::L7():s(0),w(0) {
 memset(soll,heha::cNaN,8);
}

static bool parseerror(const char*fmt,...) {
 va_list va;
 va_start(va,fmt);
 heha::vprint(fmt,va);
 nextline();
 va_end(va);
 return false;
}

bool L7::fromString(const char*s) {
 char*q;
 byte items=0;
 for(;;) {
  while (*s==' ') ++s;
  if (!*s) break;
  unsigned sel = strtoul(s,&q,10);
  if (q==s) return parseerror("Keine Zahl als Selektor, ungültiges Zeichen!");
  s=q;
  if (sel>=6) return parseerror("Selektor zu groß! Nur 0..5");
  if (items&1<<sel) return parseerror("Selektor mehrfach!");
  items|=1<<sel;
  if ((items&0b010010)==0b010010) return parseerror("Selektor 1 und 4 dürfen nicht gemeinsam gegeben werden!");
  if (*s!=':') return parseerror("Nach Selektor muss Doppelpunkt folgen!");
  int zahl=strtol(++s,&q,10);
  if (q==s) return parseerror("Zahlenangabe fehlt oder ungültiges Zeichen!");
  s=q;
  switch (sel) {
   case 5:
   if (zahl<=0) return parseerror("Zahl muss positiv sein!");
   if (zahl>50*60*3) return parseerror("Wartezeit zu groß! Max. 3 Minuten");
   w=zahl; break;
   case 4: this->s=zahl; break;
   default:
   if (zahl>125 || zahl<-125) return parseerror("Zielangabe außerhalb ±125");
   soll[sel]=zahl;
  }
  if (sel<5 && *s==':') {
   zahl=strtol(++s,&q,10);
   if (q==s) return parseerror("Zusatzzahl nach Doppelpunkt ungültig");
   s=q;
   switch (sel) {
    case 4:
    if (zahl<-125 || zahl>125) return parseerror("Geschwindigkeit außerhalb ±125");
    soll[1]=zahl;
    if (*s==':') {
     zahl=strtol(++s,&q,10);
     if (q==s) return parseerror("Beschleunigung nach Doppelpunkt ungültig");
     s=q;
     if (!zahl || zahl>125) return parseerror("Beschleunigung muss im Bereich 1..125 liegen");
     speed[1]=zahl;
    }break;
    default:
    if (!zahl || zahl>125) return parseerror("Regeltempo muss im Bereich 1..125 liegen");
    speed[sel]=zahl; break;
   }
  }
 }
 if (!items) return parseerror("Keine Aufträge, leerer String");
 if (items&2 && !(items&0x20)) return parseerror("Geschwindigkeits-Fahrauftrag ohne Zeitlimit fraglich");
 return true;
}

namespace Auftrag{

static MiniEdit*edit;

static void createWindow() {
 edit=new MiniEdit(0x0E6700,R2C1+5,16,60,1,0,pal0);
 edit->style|=1<<MiniEdit::LEFTALIGN;
 edit->onNewText();	// neu ausrichten
}

static void show() {
 emitLinks2("Auftrag");
 static bool dirty;
 if (screen.paintAll || Window::focus==edit) {
  edit->paint();
  dirty=true;
 }else if (dirty) {
  edit->paint();	// nichtfokussierten Zustand 1x malen
  dirty=false;
 }
 frameRechts();
 nextline();
}

static Window::LPARAM onEnter(Window::UINT msg) {
 if (msg!=MiniEdit::EN_ENTER) return 0;
 edit->text[edit->tlen]=0;
 L7 auftrag;
 bool r = auftrag.fromString(edit->text);
 edit->text[edit->tlen]=' ';
 if (!r) return 0;
 byte buf[2+sizeof auftrag];
 buf[0]=0x67;
 buf[1]=0;
 memcpy(buf+2,&auftrag,sizeof auftrag);
 i2c.send(buf,sizeof buf,"Auftrag::onEnter");
 return 1;
}


}// namespace

static int showMouseHelp() {
 int c=L0::find(screen.mouse);
 if (c>=0) return heha::print("Uhr: Links = Uhr stellen");
 c=L1::find(screen.mouse);
 if (c>=0) return heha::print("Debug-Byte an Adresse %u = %#02X, siehe <fba.h>",c,c);
 c=L2::find(screen.mouse);
 if (c>=0) return heha::print("Servo %d: Links = +125, Mitte = 0, rechts = -125, Scrollrad = ±5, mit Strg ±10",c+1);
 c=L4::find(screen.mouse);
 if (c>=0) return c==7
 	? heha::print("(Kilo-)Meterzähler: Rechtsklick = nullsetzen")
        : !c || c==6
 	? heha::print("%(Zündung|Notaus): Linksklick = schalten, Rechtsklick = nullsetzen ohne schalten",c)
 	: heha::print("%(Licht|Licht|Blinklicht|Blinkgeräusch|Rundumleuchte): Linksklick = weiter, Rechtsklick = nullsetzen ohne schalten",c-1);
 auto w = screen.find(screen.mouse);
 if (w) {
  c=w->id;
  switch (c>>8&0xFF) {
   case 0x65: return heha::print("Regler %d: Mitte = Stopp, Scrollrad = ±5, mit Strg ±10",(c&0xFF)+1);
   case 0x66: if ((c&0xFF)<12) return heha::print("Mitte: Odometrie-Parameter nullsetzen");
  }
 }
 return heha::print("(Keine Mausaktion)");
}

void Screen::onMouse(const Mouse&m) {
 if ((m.code&0x60)==0x40) mousehelp=false;
 auto w = capture ? capture : find(m);
// heha::print(w?w==this?"x":"y":"z");
 if (w) {
  if (w!=this) w->onMouse(m);
  else switch (m.code&0x63) {
   case 0x20:		// lButtonDown
   case 0x21:		// mButtonDown
   case 0x22: {		// rButtonDown
    int c = L0::find(m);	// Uhr
    if (c>=0) return l0.set(std::chrono::system_clock::now());
    c = L2::find(m);
    if (c>=0) return L2::set(c,m.buttons.L ? 125 : m.buttons.R ? -125 : 0);
    c = L4::find(m);
    if (c>=0) return L4::set(c,m.buttons.L);
    if (Window::focus) Window::focus->killFocus();
    heha::print("Mausklick ohne Ziel (%d,%d,CASMRL=%06b)",m.x,m.y,m.buttons);
    nextline();
   }break;
   case 0x60:		// wheelUp
   case 0x61: {		// wheelDown
    int Δ = 5;
    if (m.buttons.C) Δ = 1;	// Control = Feinkontrolle
    if (m.buttons.S) Δ<<=1;	// Shift verdoppelt
    if (m.code&1) Δ = -Δ;	// WheelDown invertiert
    int c = L2::find(m);
    if (c>=0) return L2::move(c,Δ);
   }break;
  }
// Niemals Capture ohne gedrückte Maustaste!
  if (!(m.buttons&7) && Window::capture) Window::capture->releaseCapture();
 }
 mouse = m;		// alt = neu (Struktur aus 4 Bytes kopieren)
// Bei k<0 Schalter nullsetzen (zum Synchronisieren eines außer Tritt gekommenen Schalters)
/*
 c = Regler::find(mouse);
 if (c>=0) {
  if (k==125) {
   auto ed=Regler::edit[c];
   ed->setFocus();
   ed->setsel(mouse.x-ed->x);
  }else Regler::set(c,mouse.buttons.L?0:heha::cNaN);
  return;
 }
*/
}

/*
static void onButtonUp() {}

static void onDrag() {
 heha::print("Unverarbeitetes Maus-Zieh-Ereignis, x=%d, y=%d, CASMRL=%06b",
	mouse.x,mouse.y,mouse.buttons);
 nextline();
}

static void onWheel(int Δ) {
 int c = L2::find();
 if (c>=0) return L2::move(c,Δ);
 c = Regler::find();
 if (c>=0) return Regler::move(c,Δ);
 heha::print("Unverarbeitetes Mausrad-Ereignis, x=%d, y=%d, CASMRL=%06b, Δ=%d",
	mouse.x,mouse.y,mouse.buttons,Δ);
 nextline();
}
*/
static bool inputhandler() {
 fflush(stdout);
 char keybuf[16];
 int k=::read(0,keybuf,sizeof keybuf-1);	// blockiert 1/10 s oder bis Tastenkode/Mausereignis vorhanden
 if (k>0) {
  keybuf[k]=0;	// zur Auswertung von ESC allein
  return screen.onKey(keybuf);
 }
 return true;
}

// Aufruf (nur) mit m.code==0x20,0x21 oder 0x22:
void Screen::checkDblClk(Mouse&m) {
 using namespace std::chrono;	// Literal "ms" zugreifbar machen
 static byte lastButton = 3;
 static steady_clock::time_point lastCall;
 auto jetzt = steady_clock::now();
 if ((m.code&3) == lastButton) {// gleiche Taste?
  if (jetzt-lastCall < 250ms) {
   m.code = 0x80 | m.code&3;	// in Mehrfachklick-Kode wandeln
   m.buttons.Z++;		// Klicks zählen (maximal 4)
  }else{
   m.buttons.Z=0;		// Kein Mehrfachklick, zu langsam
  }
 }else{
  lastButton = m.code&3;	// Taste hat gewechselt, neu messen
  m.buttons.Z=0;		// Kein Mehrfachklick
 }
 lastCall = jetzt;
}

// Hier: false = Programmende!
bool Screen::onKey(const char*input) {
 if (Window::focus && Window::focus->onKey(input)) return true;
 auto e = input+strlen(input);
 bool uneaten=false;
 unsigned loopcount=0;
 while (input<e) {
  char d=5;	// Standard-Servoweg
  if (input+1==e) switch (*input++) {	// ++: Unschön bei UTF-8
   case '\e':	// Escape
   case 'q':
   case 3: return false;	// ^C
#ifdef DEBUGPIN
   case '#': gpio::set(DEBUGPIN); break;// Zum Test Pin auf High
#endif
   case 'W': d=10; nobreak;
   case 'w': L2::move(1,+d); break;	// 25 Schritte bis voll
   case 'A': d=10; nobreak;
   case 'a': L2::move(0,-d); break;
   case 'S': d=10; nobreak;
   case 's': L2::move(1,-d); break;
   case 'D': d=10; nobreak;
   case 'd': L2::move(0,+d); break;
   case 'X':
   case 'x': L2::set(1,0); break;	// Stopp Fahren
   case 'Y':
   case 'y': L2::set(0,0); break;	// Stopp Lenken
   case 'r': L2::set(5,+100); break;	// Hupe
   case 'f': L2::set(5,0); break;	// Hupe aus
   case '\t': if (screen.sub) screen.sub->setFocus(); break;
   case 'L'-'@': screen.w=0; break;	// ^L: Refresh
   default: uneaten=true;
  }else if (input+3<=e && *input=='\e' && input[1]=='[') {
   byte argv[2], argc=csidecode(input,argv,2);	// rückt <input> ans Ende der CSI-Sequenz
   switch (input[-1]) {			// Letztes Zeichen
    case 'A': L2::move(2,+d); break;	// Pfeil hoch: Ladefläche hoch, Arm runter
    case 'B': L2::move(2,-d); break;	// Pfeil runter: Ladefläche runter, Arm hoch
    case 'C': L2::move(3,+d); break;	// Pfeil rechts: Löffel auskippen
    case 'D': L2::move(3,-d); break;	// Pfeil links: Löffel
// Quelle der Zuordnung: Eurosteuerung für Bagger
    case '~': if (argc==1) switch (argv[0]) {
     case 15: screen.w=0; break;		// F5: Refresh
//    heha::print("%c:%u,%u,%u\n",pkeybuf[-1],argc,argv[0],argv[1]);
    }break;
    case 'M': if (argc==0 && input+3<=e) {
//     heha::print("Mausereignis %02X",input[0]); nextline();
     Mouse m={
	byte(input[1] - '!' - x),	// hier: nullbasiert
	byte(input[2] - '!' - y),
// Bit 4 = Strg-Taste gedrückt 	→ 	Bit 5
// Bit 3 = Alt-Taste gedrückt  	→ 	Bit 4
// Bit 2 = Shift-Taste gedrückt	→	Bit 3
// Tasten Ctrl+Alt+Shift übernehmen
// Alte Maustastenzustände übernehmen → Bit 2:0
// Mehrfachklick-Zähler übernehmen →	Bit 7:6; 0b01 = Doppelklick, 0b10 = Dreifach usw.
	mouse.buttons&0xC7,
	byte(input[0])};		// Aktionskode, v.a. Bit 7:5:
           				// 001 = ButtonDown, ButtonUp
                                        // 010 = Move / Drag
                                        // 011 = Mausrad
                                        // 100 = Mehrfachklick (nur "down")
     m.buttons.orModKeys(input[0]>>2);
     input+=3;
     switch (m.code&0b1110'0011) {
      case 0x20: m.buttons.L=1; checkDblClk(m); break;	//lButtonDown
      case 0x21: m.buttons.M=1; checkDblClk(m); break;
      case 0x22: m.buttons.R=1; checkDblClk(m); break;	//rbuttondown
      case 0x23: m.buttons&=~7; break;	//buttonup (welcher ist unklar, hier: alle)
      case 0x40: m.buttons.L=1; break;
      case 0x41: m.buttons.M=1; break;
      case 0x42: m.buttons.R=1; break;
      case 0x43: m.buttons&=~7; break;
      case 0x60: break;	// Mausrad-Drehung
      case 0x61: break;
      default: uneaten=true;
// Alle anderen Kodes verraten nicht den Zustand von Maustasten.
// Insbesondere ist es nicht möglich, zwischen den (unter Linux gängigen)
// Klickgesten L▼ ~ R▼ ~ L▲ ~ R▲  und L▼ ~ R▼ ~ R▲ ~ L▲ zu unterscheiden.
     }
     onMouse(m);
    }break;
    default: uneaten=true;
   }
  }
  if (++loopcount>5) {uneaten=true; break;}
 }
 if (uneaten) {
  static unsigned dumpcount;
  dump(input,e-input,++dumpcount);
 }
 return true;
}

static Window::LPARAM onChildInput(Window::HWND,Window::UINT msg,Window::WPARAM wParam,Window::LPARAM lParam) {
 switch (msg) {		// Unterschied zu Win32: Kein WM_COMMAND
  case MiniEdit::EN_UP:
  case MiniEdit::EN_DOWN:
  case MiniEdit::EN_ENTER: {	// Beim Drücken auf ENTER
   MiniEdit*sender=(MiniEdit*)lParam;
   if (sender==Auftrag::edit) return Auftrag::onEnter(msg);
   int v = atoi(sender->text);	// Unsauber!! Kommaangabe bei Position, NaN
   if (msg!=MiniEdit::EN_ENTER) {
    v+= msg==MiniEdit::EN_UP ? 1 : -1;
    heha::snprint(sender->text,31,"%j",v);	// Unsauber bei Kommaangabe
    sender->onNewText();
   }
   byte l=wParam>>16;	// Unterschied zu Win32: ID = 32 Bit; hier: High-Teil = Anzahl Bytes
   byte buf[2+l]={byte(wParam>>8),byte(wParam)};
   memcpy(buf+2,&v,l);
   i2c.send(buf,sizeof buf,"onChildInput");
  }break;
 }
 return 0;
}

/**************
 * Alter Mist *
 **************/

// unused:
static void readaction(unsigned a, unsigned l) {
 char cmd[]={char(a>>8),char(a)};
 char*buf=new char[l];
 if (i2c.queryLong(cmd,sizeof cmd,buf,l,__FUNCTION__)) {
//    usleep(100);	// Dem ATmega etwas Zeit zum Verdauen geben
 if (a==0x6000 && l>6) l=6;
  dump(buf,l,a);
  if ((a>>8 & 0xE0)==0x60) switch (a>>8 & 0x0F) {
   case 0: if (!(char)a && l>=6) {
    byte s100 = buf[0];
    long long s = 0;
    memcpy(&s,buf+1,5);
    std::time_t t = s;
    heha::print("%lld.%02u %s",s,s100,std::ctime(&t));
   }break;
   case 1:
   case 2:
   case 3: if ((byte)a+l<=13) for (int i=0; i<l;++i) {
    heha::print("%4d%c",(signed char)buf[i],i+1==l?'\n':' ');
   }break;
   case 4: if (!((a|l)&1) && (byte)a+l<=32) for (int i=0; i<l;i+=2) {
    short z=*(short*)(buf+i);	// Vektor aus int16 (avr-gcc: int)
    if ((byte)a+i<14) heha::print("%,2hj",z);
    else heha::print("%d",z);
    heha::print("%c",i+2>=l?'\n':' ');
   }break;
   case 5:
   case 6: if (!(byte)a && l==12) for (int i=0; i<3; i++) {
    heha::print("%7f%c",*(float*)(buf+4*i),i+1==3?'\n':'\t');
   }break;
  }
 }
 delete[]buf;
}

// ASCII-Zeichen in äquivalenten Ziffernwert umwandeln
// Returnwert >= Zahlenbasis bedeutet: Zeichen ist als Ziffer ungültig
// Böse Falle arm-gcc: char ist nicht signed!
static byte digit(signed char c) {
 if (c<='9') return c-'0';	// auch alles negative bleibt ungültig
 c|=0x20;			// in Kleinbuchstabe umwandeln
 if (c<'a') return c;		// ungültig als Ziffer (0x3A..0x3F,0x60)
 return c-'a'+10;		// '[' => '{' => 36 = ungültig
}

/*******************
 * Hier geht's los *
 *******************/
int main(int argc,char**argv) {
#ifdef DEBUGPIN
 gpio::fsel(DEBUGPIN,1);	// DEBUG: Pin zum Ausgang machen
 gpio::clr(DEBUGPIN);	// mit L anfangen (Achtung! GPIO4 aktiviert Notaus!)
#endif
 unsigned a=0x6000,l=8;	// Adresse und Länge
 char*pw=0;
 if (argc>=2) {
  if (!strcmp(argv[1],"settime")) {
// Argument der Form "settime"
   L0 time(std::chrono::system_clock::now());
   pw=time; l=sizeof time;
  }else if (pw=strchr(argv[1],'W')) {
// Argument der Form "0x2000W123456789ABCDEF0" (Hex-Bytes)
   sscanf(argv[1],"%i",&a);
   char buf[64],i;
   for (i=0; i<sizeof buf;) {
    byte j=digit(*++pw);
    if (j>=16) break;
    byte k=digit(*++pw);
    if (k<16) j = j<<4|k;
    buf[i++]=j;
    if (k>=16) break;
   }
   pw=buf; l=i;
  }else if (pw=strchr(argv[1],'F')) {
// Argument der Form "0x5000F1.23,456.7,8E-9"
   sscanf(argv[1],"%i",&a);
   char buf[64],i;
   for (i=0; i<sizeof buf; i+=sizeof(float)) {
    float f;
    int k,j=sscanf(++pw,"%f%n",&f,&k);
    if (!j) break;
    pw+=k;	// auf das Komma oder sonstiges Trennzeichen
    *(float*)(buf+i)=f;
   }
   pw=buf; l=i;
  }else sscanf(argv[1],"%iL%i",&a,&l);
// Argument der Form "0x8000L16" = Lesemodus (ohne "L" 16 Bytes)
 }
 unsigned i2cnr = 1;
 if (argc>=3) sscanf(argv[2],"%i",&i2cnr);
 unsigned i2cslave = 0x5d;
 if (argc>=4) sscanf(argv[3],"%i",&i2cslave);
 if (!i2c.open(i2cnr,i2cslave)) return 1;

 if (pw) writeaction(a,pw,l);
 else if (l1.query()) {		// eedata (128 Byte) komplett lesen
  screen.wndproc = onChildInput;
  Regler::createWindows();	// Erst <screen> initialisieren lassen ...
  Odo::createWindows();		// ... dann Fenster erzeugen
  Auftrag::createWindow();
//  heha::print("%p\n",screen.sub);
//  dump((char*)&l1,sizeof l1);
//  line=0;
  Terminal terminal;
  terminal.start();
  cursortop();
  do{		// Erst Werte holen und Fehler ausspucken lassen
   if (screen.paintAll) {
    csi(19,screen.h,'r');	// Scrollbereich für Fehlermeldungen unten
   }
   l0.query();	// Uhr
   l1.queryDebug();
   l2.query();	// PWM-Generator
   l3.query();	// Controller
   l4.query();	// A/D-Wandler und verschiedenes
   Regler::query();	// l5 = Regler
   if (l2.pwms[0] || l2.pwms[1]) odo.query();	// l6 = Odometrie (nur beim Fahren oder Lenken)
   if (!screen.paintAll) csi('s');
   cursortop();	// Dann Kursor bewegen
   l4.show();
   L2::showRuler();
   l2.show();	// PWM-Generatorwerte, NaN = keine Pulserzeugung
   l3.show();	// Werte von Fernbedienung, NaN = noch nie eingeschaltet
   Regler::showSoll();	// NaN = Regler außer Betrieb (automatisch nach Timeout)
   heha::print("%10s: ","Uhr");
   csi(42,30,'m');	// "editierbares" Feld
   heha::print(" ");
   l0.show();
   heha::print(" ");	// Interessant: Scrollt nicht am rechten Rand!
   csi('m');	// Feld beenden wenn Fenster größer ist als 80 Spalten
   frameRechts();
   nextline();
   odo.show();	// Odometriedaten (TODO: Nur bei Änderung)
   static bool regelDataShown;
   char activeRegler = Regler::firstActive();
   bool pushed_paintAll = screen.paintAll;
// Wenn sich Titel und Weißraum ändert
   if (regelDataShown != (activeRegler>=0)) screen.paintAll = true;
   if (activeRegler>=0) {
    regler[activeRegler].showReglerData();
    regelDataShown=true;
   }else{
    l1.show();	// Hexdump Debug-Bereich (TODO: Nur geänderte Bytes)
    regelDataShown=false;
   }
   screen.paintAll = pushed_paintAll;
   Auftrag::show();
   if (screen.paintAll) emitHorizontalDivider("╚═╧═╝",R2C1,screen.w-R2C1-3);
   nextline();
   if (!screen.mousehelp) {
    ChrColor statuscolor(0,7);
    statuscolor.out();	// (CSI 7 m funktioniert nicht mit CSI K!)
    csi('K');
    heha::print(" ");
    showMouseHelp();
    screen.mousehelp=true;
   }
   nextline();
   if (!screen.paintAll) csi('u');
   screen.paintAll=false;
  }while(inputhandler());	// blockiert 1/10 s
  terminal.stop();
  csi('s');
  csi('r');		// Scrollbereich aufgeben
  csi('u');
 }
 i2c.close();
}
Detected encoding: UTF-80