Source file: /~heha/enas/Convac-Ätzer/Firmware-190517.zip/Prozess.cpp

/* Projekt Convac-Ätzer
   Henrik Haftmann, 161213
 */

#include "Convac.h"
#include "Display.h"
#include <string.h>
#include <stdlib.h>

RUN run;
static int startvalues[32];	// Platz für max. 32 Analogkanäle
//static byte prevvalues[12];	// Platz für max. 96 Digitalkanäle

int BEFEHL::getv() const{
 switch (vlen()) {
  case 0: return operation>>3&1; break;	// 1 Bit aus Bit 3
  case 1: return value[0]; break;	// vorzeichenlos!
  default: return *(int*)value; break;
//case 3: return *(long*)value; break;
 }
}

bool BEFEHL::setv(int v) {
 byte havebyte=vlen();
 if (havebyte) {		// Analogkanal
  byte needbyte=1;
  if (unsigned(v)>>8) ++needbyte;
  if (havebyte!=needbyte) {
   if (!GS::move(value+needbyte,value+havebyte)) return false;	// kein Platz
   operation=byte(operation&0xFC)|needbyte;
  }
  if (needbyte==1) value[0]=v;	// vorzeichenlos!
  else *(int*)value=v;
 }else{				// Digitalkanal
  if (unsigned(v)>1) return false;	// Bits bleiben Bits
  operation=operation&0xFB|byte(v)<<3;	// 1 Bit nach Bit 3
 }
 return true;
}

bool BEFEHL::swapAna() {
 byte op=operation;
 byte no=op;	// nop = Neuer Opcode
 if (op&3) {	// analog: umschalten zu digital
  no&=~3;	// ab jetzt digital
  if (index>>3>DIGITAL::getMax()) index=0;
 }else{
  ++no;		// ab jetzt analog 1 Byte
  if (index>ANALOG::getMax()) index=0;		// auf etwas gültiges setzen!
 }
 if (!GS::move(value+(no&3),value+(op&3))) return false;	// kein Platz
 operation=no;	// Platz geschaffen: eintragen
 return true;
}

const char* BEFEHL::sprintKanal(bool dot) const{
 if (vlen()||!dot) {
  floatstr(index);
 }else{
  char*s=sbuf;
  *s++='0'+(index>>5);	// obere 3 Bits: Karten- = Anzeigeseitennummer
  *s++='.';
  *s++='0'+(index>>3&3);	// mittlere 2 Bits: Bytenummer der Karte
  *s++='.';
  *s++='0'+(index&7);	// untere 3 Bits: Bitnummer
  *s=0;
 }
 return sbuf;
}

bool BEFEHL::sscanKanal(const char*s) {	// liefert false bei Fehler, ansonsten wird .index gesetzt
 unsigned v=strfloat(s);
 if (vlen()) {
  if (v>ANALOG::getMax()) return false;	// zu groß, INF/NAN inkklusive
 else
  if (v>>3>DIGITAL::getMax()) return false;	// zu groß (undefinierte Bits??)
 }
 index=v;
 return true;
}

const char* BEFEHL::sprintVal(bool withUnit) const{
 int v=getv();
 if (vlen()) {
  const ANALOG::scale_t*k=ANALOG::scale+index;
  v=rounding_shr8((long)v*k->gain)+k->offset;
  floatstr(v,k->nk);
  if (withUnit) {
   char*p=sbuf+strlen(sbuf);	// Ende der Zahl
   *p=' ';			// mit Leerzeichen (außer bei °; kommt hier nicht vor)
   strcpy(++p,k->unit);	// Einheit (editierbar im RAM!) anhängen
  }
 }else floatstr(v);
 return sbuf;
}

bool BEFEHL::sscanVal(const char*s) {
 int v;
 if (vlen()) {
  const ANALOG::scale_t*k=ANALOG::scale+index;
  v=strfloat(s,k->nk);
  if (word(v-32767)<3) return false;	// bei +INF, NAN oder -INF
  v=rounding_div(long(v-k->offset)<<8,k->gain);
  if (v<ANALOG::getMin(index)) return false;
  if (v>ANALOG::getMax(index)) return false;
 }else{
  v=strfloat(s);
 }
 return setv(v);
}

// Information über den Befehl preisgeben
void BEFEHL::print(byte i) const{
 d.print(
   operation&0x60?F("Wenn")	// Eingaben
   :vlen()||operation&OP_D1?F("Setze")
   :F("Lösche"));
 d.X+=3;
 d.print(vlen()?F("Wert"):F("Bit"));
 d.print('[');
 d.print(sprintKanal());
 d.print(']');
 d.X+=3;
 if (operation&0x60&&operation&0x10) d.print(F("Δ "));
 char sign=swap(operation);
 if (vlen()) sign^=ANALOG::scale[index].gain>>8;	// Größer/kleiner in der Anzeige umdrehen bei negativem Gain
 d.print(
   operation&0x60
   ?vlen()
   ?sign<0?F("größer"):F("kleiner") // lesen analog
   :operation&0x10
   ?F("wechselt zu")
   :F("gleich")
   :F("auf"));
 d.X+=3;
 d.print(sprintVal(true));
 if (operation&0xE0) d.print(F(" dann "));
 if (operation&0x60) {
  d.print(
   (operation&0x60)==OP_CNT?F("weiter")
   :(operation&0x60)==OP_DNT?F("Start verhindern")
   :F("Abbruch"));
  if (operation&OP_LOG) d.print(F(" + "));
 }
 if (operation&OP_LOG) {
  d.print(F("Log"));
 }
}

/* Abarbeitungstabelle
Schritt			S0				Sn
		Op.	Rezept läuft nicht		Rezept läuft
			ausführen?	Folge		ausführen?	Folge
Globale Sich.	OUT	ja		-		nein		-
		CNT	ja		S1 #		nein		-
		DNT	ja		S0		nein		-
		BRK	ja		S0		ja		S0

Rezept-Sich.	OUT	ja		-		nein		-
		CNT	nein		-		ja		S+1 #
		DNT	ja		S0		nein		-
		BRK	ja		S0		ja		S0

Rezept-Schritt	OUT	-		-		ja		-
		CNT	-		-		ja		S+1 #
		DNT	-		-		ja		S0 (am Ende)
		BRK	-		-		ja		S0 (sofort)
# Wenn alle zustimmen
*/


// Einzelaktion anwenden:
// * Digitalausgabe (nur) bei percentage==0
// * Keine Analogausgabe bei percentage==0, stattdessen Merken des aktuellen Analogwertes
// * Analogwertausgabe prozentual linear je nach percentage bis percentage==255
// * Digital- und Analogeingabe und Prüfung bei jedem Aufruf
// * {Flankendetektion je nach zuletzt gelesenem Wert}
void BEFEHL::apply() const{
 int v=getv();
 if (operation&0x60) {	// Eingabe-Operation mit anschließender Prüfung
  if ((operation&0x60)==OP_CNT) run.status|=1;	// Min. ein Befehl mit Continue
  if (vlen()) {		// analog
   int cur=ANALOG::getIo(index);
   if (operation&OP_D1) {
    if (cur>v) return;	// Datenbit gesetzt: "wahr" ist "größer"
   }else{
    if (cur<v) return;	// Datenbit gesetzt: "wahr" ist "kleiner"
   }
  }else{		// digital
   bool bit=DIGITAL::getBit(index);
   if (bit==(bool)v) return;	// immer prüfen: "wahr" ist "gleich"
  }
// An dieser Stelle sind Eingaben FALSCH geworden: Aktionen als Bitmaske kombinieren
  byte b=shlb(1,swap(operation)>>1&3);	// da kommt nur 0010, 0100 oder 1000 raus
  run.status|=b;			// OR-verknüpfen
  if (operation&OP_LOG) {
   ALARM::generate(this);	// jeder Befehl einzeln
   run.status|=0x80;	// für Piepton
  }
 }else{			// Ausgabe-Operation
  if (vlen()) {		// analog
   if (!run.percentage) startvalues[index]=ANALOG::getIo(index);
   else{
    if (run.percentage<0xFF) {
     v-=startvalues[index];		// vzb. Differenz
     v=rounding_shr8((long)v*run.percentage);	// Rampe realisieren
     v+=startvalues[index];
    }
    ANALOG::setIo(index,v);
   }
  }else{		// digital
   if (!run.percentage) DIGITAL::setBit(index,v);	// am Anfang setzen
  }
 }
}

void BEFEHL::applyMult(byte n) const{
 const BEFEHL*e=this;	// <this> ist leider kein lvalue
 for(;n;--n) {
  e->apply();
  e=e->fin();	// Objektzeiger zum nächsten Element vorrücken
 }
}

void SCHRIT::print(byte i) const{
 if (i) {
  d.print(F("Schritt "));
  d.print(floatstr(i));
  d.print(F(" über "));
  d.print(floatstr(ms10,2));
  d.print(F(" s"));	// Sekunden
 }else{
  d.print(F("Ständige Prüfung"));
 }
 d.print(F(", "));
 d.print(floatstr(n.c));
 d.print(F(" Befehl"));
 if (n.c!=1) d.print('e');
}
/* 
void SCHRIT::apply() const{
// run.status=0;
 be->applyMult(n.c);		// ganze Liste durchgehen

 run.applyStatus();
 if (!run.status	// keine Sonderbehandlung?
 && allowTimedNext
 && run.percentage==0xFF) run.next();	// Zeit herum? Dann Weiter!

}
*/
void REZEPT::print(byte i) const{
 d.print(F("Rezept "));
 d.print(floatstr(i+1));		// kein Name, 1-basiert anzeigen
 d.print(':');
 d.X+=3;
 d.print(floatstr(n.c-1));		// Sicherheit nicht mitzählen
 d.print(F(" Schritt"));
 if (n.c) {
  d.print(F("e, ID = "));
  d.print(floatstr(st[0].ms10));
 }
}

void RUN::applyStatus() {
 if (run.status&1) {		// Mindestens ein Continue-Befehl?
  run.status&=~1;
  if (run.status&6) {		// Tatsächliches Continue verboten?
   if (run.percentage==0xFF) {	// Am Ende der Zeit?
    ALARM::generate(run.st);	// Timeout generieren
    run.status=0x88;		// Abbruch erzwingen
   }
  }else{			// Mindestens ein Continue-Befehl, und ALLE wahr
   run.next();			// nächster Schritt (vorzeitig)
   return;
  }
 }
 if (run.status&0x80) beep(64,3);	// Log-Einträge mit Ton
 run.status&=~0x80;
 if (run.status&8) {			// Abbruch
  run.status&=~8;
  if (run.aktion) {
   byte last=run.re->n.c-1;
   if (run.aktion==last) run.next();	// zu Ende führen
   else run.next(last);			// zum letzten Schritt springen
  }
  return;
 }
}

void RUN::init() {
// DIGITAL::getInputs(prevvalues);
 run.time=0;
 run.re=curRezept();
 eedata.gs.st.apply();	// abfragen mit 0%
}

void RUN::next(byte n) {
 if (run.aktion) xorAktionMarker();	// Zeigerpfeil aus Liste weg
 else init();
 if (!n) n=run.aktion+1;	// wenn n!=0 gegeben, zu bestimmtem Schritt springen
 if (n>=run.re->n.c) {
  run.aktion=0;		// Ende des Rezepts
  ANI::show();		// wegnehmen
 }else{
  run.aktion=n;
  ANI::show();		// Zahl aktualisieren
  xorAktionMarker();	// Zeigerpfeil in Liste setzen
  run.st=curSchrit();	// Zeiger aktualisieren
//  run.percentage=0;	// Null auch für den nächsten Sicherheitsdurchlauf
//  run.re->st[0].apply(false);
//  run.st->apply(true);	// Erster Durchlauf
 }
}

void RUN::start(byte rez) {
 if (rez>=rl->nRezept) return;	// Fehler
 if (run.aktion) return;	// Fehler
 run.rezept=rez;
 run.next();		// Nur vor next() muss run.a nicht initialisiert werden
}

void RUN::tick() {
 run.re=curRezept();		// Zeiger wiederherstellen
 run.st=curSchrit();		// (können sich durch Editieren verändern)
 run.percentage=0xFF;
 run.status=0;
 eedata.gs.st.apply();		// immer abfragen
 if (run.aktion) {
  ANI::animate();
  run.time+=10;
  if (run.time>=run.st->ms10) {	// Zur nächsten Aktion?
   run.time-=run.st->ms10;
  }else{
   run.percentage=rounding_div((long)run.time<<8,run.st->ms10);
  }
  run.re->st[0].apply();
  run.st->apply();
 }
 run.applyStatus();
 if (run.aktion && run.percentage==0xFF) run.next();
}


/***************
 * Alarm-Liste *
 ***************/

ALARM ALARM::alarm[10];

ALARM*ALARM::generate(const void*id) {
 ALARM*a=find(id);
 if (!a) {
  for (a=alarm; a<alarm+elemof(alarm); a++) if (!a->occ) goto found;
  a=oldest(); a->occ=0;
found:
  a->p=id;
 }
 a->time=ticker.w;
 a->occ++;
 return a;
}
ALARM*ALARM::find(const void*id) {
 for (auto a=alarm; a<alarm+elemof(alarm); a++) if (a->p==id) return a;
 return 0;
}
ALARM*ALARM::extreme(bool min_age) {
 ALARM*a=0;		// kann NULL liefern (nur bei leerer Liste)
 word w=min_age?0xFFFF:0;
 for (auto b=alarm; b<alarm+elemof(alarm); b++) if (b->occ) {
  word age=ticker.w-b->time;
  if (min_age && w>=age || !min_age && w<=age) {a=b; w=age;}
 }
 return a;
}
void ALARM::clearAll() {
 for (auto a=alarm; a<alarm+elemof(alarm);a++) a->clear();
}
byte ALARM::n() {
 byte n=0;
 for (const ALARM*a=alarm; a<alarm+elemof(alarm);a++) if (a->occ) ++n;
 return n;
}
ALARM*ALARM::nth(byte i){
 auto a=alarm; ++i;
 for (;;a++) if (a->occ && !--i) break;
 return a;
}
void ALARM::move(const void*dst,const void*src) {
 for (auto a=alarm;a<alarm+elemof(alarm);a++) if (a->occ) {
  if (word(a->p)>=(word)src) *(word*)&a->p+=(word)dst-(word)src;
 }
}

void ALARM::print() {		// Tabellen-Ausgabe rechtsbündig
 floatstr(time,2);		// Zeitstempel
 printSbufRight(30);
 floatstr(occ);			// Anzahl des Auftretens
 printSbufRight(50);
 ITER iter(p);			// iteriert Liste bis zur gegebenen Adresse
 if (iter.type!=2) {
  d.X=60;
//  d.print(utox(iter.type,2));
  d.print(utox(p));
 }else{
  if (iter.ire==0xFF){
   d.X=55;
   d.print(F("gS"));
  }else{
   floatstr(iter.ire+1);	// Rezept
   printSbufRight(60);
   floatstr(iter.ist);		// Schritt im Rezept (0 = Sicherheit)
   printSbufRight(70);
  }
  floatstr(iter.ibe+1);		// Befehl im Schritt
  printSbufRight(80);
  d.X=85;
  be->print(iter.ibe);		// Befehlstext
 }
}

// Zeiger-Such-Konstruktor
ITER::ITER(const void*id) {
 byte nSchrit=1;
 initgs(); ire=0xFF; goto innen;
aussen:
 init();
 while (ire<rl->nRezept) {
//  type=0;
//  if (p>=id) return;
  nSchrit=re->n.c;
  st=re->st0();
innen:
  for (ist=0;ist<nSchrit;ist++) {
//   type=1;
//   if (p>=id) return;
   byte nBefehl=st->n.c;
   be=st->be0();
   type=2;
   for (ibe=0;ibe<nBefehl;ibe++) {
    if (p==id) return;
    be=be->fin();
   }// am Ende dieser Schleife steht der Zeiger auf SCHRIT*
  }// am Ende dieser Schleife steht der Zeiger auf REZEPT*
  if (!++ire) goto aussen;	// weiter mit Rezeptliste (nRezept überspringen)
 }// am Ende dieser Schleife steht der Zeiger am Ende der Rezeptliste
 type=0xFF;
}
Detected encoding: UTF-80