{
normale Syntax: (alle auch mit <*> statt <MainCh> als Omni-Befehl)
<MainCh> <:> CMD <SPACE> <SubCh> <=> <Wert> <CRLF>
CMD <SPACE> <SubCh> <?> <CRLF>
Ein-Paramater-Befehle:
CMD <SPACE> <SubCh> <CRLF>
Null-Paramater-Befehle:
CMD <?> <CRLF>

Kurzform: VAL <SPACE> und <MainCh> <:> können weggelassen werden,
es wird dann der zuletzt angesprochene Kanal verwendet
Beispiel:
0:VAL 5?
0:5?
5?
0:VAL 2=1.00
0:2=1.00
2=1.00
}

// gerätespezifischer Parser-Teil

procedure ParseGetParam;
var IntInOutFlag:boolean;
    myBranch,mySDrefresh:boolean;
    myByte:byte;
    myIndex:Byte;

begin
  if Labelseek and (not (SubCh in [1000..1099])) then
    return; // nichts machen, wenn Label gesucht wird
  endif;
  IntInOutFlag:= (SubCh>=2000);
  if IntInOutFlag then  // Out-Befehl
    SubCh:=SubCh-2000;
  endif;
  myIndex:=byte(SubCh mod 10);  // angespr. Register 0..9 errechnen aus SubCh-Rest
  case SubCh of
    0:
      Param:=DispReg;
      if IntInOutFlag then  // Out-Befehl
        AccA:=Param;
      else
        WriteParamSer;
      endif;
      |
    80: // Parameter-LCD X
      ParamInt:=integer(ParamCol);
      WriteParamIntSer;
      |
    81: // Parameter-LCD Y
      ParamInt:=integer(ParamLine);
      WriteParamIntSer;
      |
    82: // Parameter-LCD-Skalierung
      ParamInt:=integer(ParamFontScaleX);
      WriteParamIntSer;
      |
    83: // Parameter-LCD-Skalierung
      ParamInt:=integer(ParamFontScaleY);
      WriteParamIntSer;
      |
    84: // Parameter-Gesamtstellen, weitere werden abgeschnitten
      ParamInt:=integer(digits);
      WriteParamIntSer;
      |
    85: // Parameter-Nachkommastellen
      ParamInt:=integer(nachkomma);
      WriteParamIntSer;
      |
    90..96: // OPT
      GetRTC;
      ParamInt:=integer(RTCarray[myIndex]);
      WriteParamIntSer;
      |
    97: // RTC valid
      if DCFready then
        ParamInt:=1;
{$IFDEF EXTRTC}
        GetRTC;
        SetExtRTC;
{$ENDIF}
      else
        ParamInt:=0;
      endif;
      WriteParamIntSer;
      |
    98: // RTC Feldstärke
      ParamInt:=integer(DCFfield);
      WriteParamIntSer;
      |
    150..159: // OPT
      ParamInt:=OptionArray[myIndex];
      WriteParamIntSer;
      |
    270: // letzter PUT/GET-Kanal
      ParamInt:=integer(TransferCh);
      WriteParamIntSer;
      |
    271: // letzter PUT/GET-Subkanal
      ParamInt:=TransferSubCh;
      WriteParamIntSer;
      |
    290: // Nächsten Stunden-Tick abwareten
      tickHours:=false;
      repeat
        mdelay(1);
      until (not IncrButton) or tickHours;
      |
    291: // Nächsten Minuten-Tick abwareten
      tickMinutes:=false;
      repeat
        mdelay(1);
      until (not IncrButton) or tickMinutes;
      |
    292: // Nächsten Sekunden-Tick abwareten
      tickSeconds:=false;
      repeat
        mdelay(1);
      until (not IncrButton) or tickSeconds;
      |
    298: // DPL
      Param:=DispReg;
      ParamToStr;
      ParamStr2LED;
      |
    299: // letzter Pausenwert
      ParamInt:=integer(Pause);
      WriteParamIntSer;
      |
    300..309: // REG, ACA, ACB
      Param:=RegisterArray[myIndex];
      WriteParamSer;
      |
    320..329: // DEC
      Param:=RegisterArray[myIndex]-1;
      RegisterArray[myIndex]:=Param;
      CompareParam:=Param;
      |
    330..339: // INC
      Param:=RegisterArray[myIndex]+1;
      RegisterArray[myIndex]:=Param;
      CompareParam:=Param;
      |
    340..349: // CMP
      CompareParam:=RegisterArray[myIndex];
      |
    400..409: // GET, Wert anfordern und warten
      WriteTransferChPrefix;
      serout('?');
      SerCRLF;
      GetWaitFlag:=true;
      DestinationRegister:=byte(myIndex);
      |
    500..509: // PUT
      Param:=RegisterArray[myIndex];
      WriteTransferParamSer;
      |
    350..359: // XCH ohne Parameter, deshalb hier!
      Param:=RegisterArray[myIndex];
      RegisterArray[myIndex]:=AccA;
      AccA:=Param;
      |
    600..609: // MUL ohne Parameter, deshalb hier!
      Param:=AccA;
      AccA:=Param*RegisterArray[myIndex];
      |
    610..619: // DIV ohne Parameter, deshalb hier!
      Param:=AccA;
      AccA:=Param/RegisterArray[myIndex];
      |
    620..629: // ADD ohne Parameter, deshalb hier!
      Param:=AccA;
      AccA:=Param+RegisterArray[myIndex];
      |
    630..639: // SUB ohne Parameter, deshalb hier!
      Param:=AccA;
      AccA:=Param-RegisterArray[myIndex];
      |
    640..649: // SQR ohne Parameter, deshalb hier!
      Param:=RegisterArray[myIndex];
      RegisterArray[myIndex]:=sqrt(Param); // square root
      |
    650..659: // SQU ohne Parameter, deshalb hier!
      Param:=RegisterArray[myIndex];
      RegisterArray[myIndex]:=sqr(Param); // square root
      |
    660..669: // NEG ohne Parameter, deshalb hier!
      Param:=RegisterArray[myIndex];
      RegisterArray[myIndex]:=negate(Param); // square root
      |
    1000..1099: // LBL X, Label setzen, auch "if LabelSeek" oben beachten!
      if INITfileIsOpen then
        myByte:=byte(SubCh mod 100) and 31;
        if (CurrentGotoLabel=myByte) then // Label gefunden
          LabelSeek:=false;
        endif;
        LabelArray[myByte]:=word(F16_FilePos(TextFile)); // aktuelle Position
        LabelValidArray[myByte]:=true;             // gültiges Label
      endif;
      |
    1100..1699: // Goto/Branch für Script-Verarbeitung, SubCh = Label-Nummer
      if INITfileIsOpen then
        CurrentGotoLabel:=byte(SubCh mod 100) and 31;
        myBranch:=false;
        case SubCh of
        1100..1199:  // BRA, GTO
          myBranch:=true;
          |
        1200..1299:  // BRG
          if CompareParam > 0 then
            myBranch:=true;
          endif;
          |
        1300..1399:  // BGE
          if CompareParam >= 0 then
            myBranch:=true;
          endif;
          |
        1400..1499:  // BEQ
          if CompareParam = 0 then
            myBranch:=true;
          endif;
          |
        1500..1599:  // BLE
          if CompareParam <= 0 then
            myBranch:=true;
          endif;
          |
        1600..1699:  // BLE
          if CompareParam < 0 then
            myBranch:=true;
          endif;
          |
        endcase;
        if not LabelValidArray[CurrentGotoLabel] then // noch leer, also vorwärts
          LabelSeek:=myBranch; // aber nur, wenn Sprung
        else
          if myBranch then
            F16_FileSeek(TextFile,longword(LabelArray[CurrentGotoLabel])); // Label aufsuchen
          endif;
        endif;
      endif;
      |
    240:
      WriteDirEntry;
      |
    241:
      FileNumTemp:=FileNum;
      if FileAnzahl>0 then
        for FileNum:= 0 to FileAnzahl-1 do
          WriteDirEntry;
        endfor;
      else
        FileNum:= 0;
        WriteDirEntry;
      endif;
      FileNum:=FileNumTemp;
      |
    242:
      ParamByte:=FileAnzahl;
      WriteParamByteSer;
      |
    251: // Fehlerzähler auslesen
      ParamInt:=integer(Errcount);
      WriteParamIntSer;
      |
    252: // Serielle Baudrate
      ParamInt:=integer(EESerBaudReg);
      WriteParamIntSer;
      |
    253: // SerTest, gibt Input-String komplett und unverändert wieder aus
      write(Serout, SerInpStr);
      SerCRLF;
      |
    254: //Wert
      WriteChPrefix;
      write(serout,Vers1Str);
      SerCRLF;
      |
    250,255: //Status
      serprompt(NoErr);
      |
  else
    serprompt(ParamErr);
  endcase;
end;

procedure ParamLineCalc(myIndex:byte);
var myLine:Integer;
begin
  myLine:=integer(myIndex);
  if myIndex>0 then
    ParamCol:=0;
    if (ParamFontScaleX = 1) and (myIndex > 6) then
      ParamCol:=65;
      myLine:=myLine-6;
    endif;
    ParamLine:=myLine*9*integer(ParamFontScaleY)+8;
  endif;
end;

procedure ParseSetParam;
var myByte:byte;
    myIndex:Byte;
    myBranch,mySDrefresh:boolean;

begin
  mySDrefresh:=false;
  if Labelseek then
    return; // nichts machen, wenn Label gesucht wird
  endif;
  ChangedFlag:=true;
  if SubCh>=2000 then  // Out-Befehl
    Param:=RegisterArray[Parambyte];
    SubCh:=SubCh-2000;
  endif;
  myIndex:=byte(SubCh mod 10);  // angespr. Register 0..9 errechnen aus SubCh-Rest
  case SubCh of
    0..9:  // Wert anzeigen, 1..9=formatiert in Zeile
      ParamLineCalc(myIndex);
      ParamToStr;
      ParamStr2LED;
      DispReg:=Param;
      |
    10..19: // Registerinhalt anzeigen, 1..9=formatiert in Zeile
      ParamLineCalc(myIndex);
      Param:=RegisterArray[ParamByte];
      ParamToStr;
      ParamStr2LED;
      |
    20..29: // Legende zur Zeile anzeigen,, ggf. 2. Spalte
      ParamLineCalc(myIndex);
{$IFDEF LCDGRAPH}        // LCD-Grafik statt LED
      ParamLineCalc(myIndex);
      gSetTextJustify(alHorRight, alVertBottom);
      ParamCol:=127;
      if (ParamFontScaleX = 1) and (myIndex < 7) then
        ParamCol:=62;
      endif;
      gDrawString(ParamCol, ParamLine, 1, 1, TxtRot0, ParamStr);
      gSetTextJustify(alHorLeft, alVertBottom);
      gdisprefresh;
      ParamCol:=0;
{$ENDIF}
      |
    30..42: // String direkt anzeigen, 1..12=formatiert in Zeile Nr.
      ParamLineCalc(byte(SubCh)-30);
      ParamAlphaStr2LED;
      |
    50:
      GetRTC;
      if ParamByte=0 then
        TimeToParamStr;
      else
        DateToParamStr;
      endif;
      ParamAlphaStr2LED;
      |
    51:  // Wert auf Skalenzeiger anzeigen
{$IFDEF LCDGRAPH}        // LCD-Grafik statt LED
      ParamNeedle(Param);
{$ENDIF}
      |
    52:  // Wert auf Bargraph anzeigen
{$IFDEF LCDGRAPH}        // LCD-Grafik statt LED
      ParamBar1(Param);
{$ENDIF}
      |
    61:  // Register auf Skalenzeiger anzeigen
{$IFDEF LCDGRAPH}        // LCD-Grafik statt LED
      Param:=RegisterArray[ParamByte];
      ParamNeedle(Param);
{$ENDIF}
      |
    62:  // Register auf Bargraph anzeigen
{$IFDEF LCDGRAPH}        // LCD-Grafik statt LED
      Param:=RegisterArray[ParamByte];
      ParamBar1(Param);
{$ENDIF}
      |
    70: // Hintergrund setzen
{$IFDEF LCDGRAPH}        // LCD-Grafik statt LED
      if ParamAlpha then
        BMPLoad(ParamStr);
        mySDrefresh:=true;
      else
        if ParamByte=0 then
          gclrscr(0);
          mySDrefresh:=true;
        endif;
        if ParamByte=1 then
          gclrscr(0);
          gDrawRect(10, 18, 117, 44, $FF);
          gDrawRect(12, 20, 115, 42, $FF);
          mySDrefresh:=true;
        endif;
      endif;
      gSetTextJustify(alHorLeft, alVertBottom);
      WriteDirEntryLCD;
      gdisprefresh;
{$ENDIF}
      |
    71:
{$IFDEF LCDGRAPH}        // LCD-Grafik statt LED
      gclrscr(0);
      BuildNeedle(ParamStr);
      mySDrefresh:=true;
      gSetTextJustify(alHorLeft, alVertBottom);
      WriteDirEntryLCD;
      gdisprefresh;
{$ENDIF}
      |
    80: // Parameter-Pixelzeile X setzen
      ParamCol:=ParamInt;
      |
    81: // Parameter-Pixelpalte Y setzen
      ParamLine:=ParamInt;
      |
    82: // Parameter-Skalierung setzen
      ParamFontScaleX:=ParamByte;
      ParamLineCalc(1); // Neue Grundeinstellung
      |
    83: // Parameter-Skalierung setzen
      ParamFontScaleY:=ParamByte;
      ParamLineCalc(1); // Neue Grundeinstellung
      |
    84: // Parameter-Stellen
      digits:=ParamByte;
      |
    85: // Parameter-Nachkommas
      nachkomma:=ParamByte;
      |
    90..96: // CLK-Funktionen
      GetRTC;
      RTCarray[myIndex]:=ParamByte;
      SetRTC;
      |
    97:  // Interne Uhr mit externer synchronisieren  97=0
{$IFDEF EXTRTC}
      GetExtRTC;
      SetRTC;
{$ENDIF}
      |
    98:  // Externe Uhr mit interner synchronisieren  98=0
{$IFDEF EXTRTC}
      GetRTC;
      SetExtRTC;
{$ENDIF}
      |
    150..159:
      if EEunLocked then
        OptionArray[myIndex]:=ParamInt;
        mdelay(3);
        InitVars;
        Param:=0;
        DispReg:=Param;
        ParamToStr;
        ParamStr2LED;
      else
        serprompt(LockedErr);
        return;
      endif;
      |
    270: // GET-Kanal
      TransferCh:=ParamByte;
      |
    271: // GET-Subkanal
      TransferSubCh:=ParamInt;
      |
    299: //Pause für Script-Verarbeitung, Wert in ms
      Pause:=word(ParamInt);
      if Pause>0 then
        mdelay(Pause);
      endif;
      |
    300..309: // REG, LDA, LDB
      RegisterArray[myIndex]:=Param;
      if subCh=309 then  // DispReg, gleich auf Anzeige bringen
        ParamToStr;
        ParamStr2LED;
      endif;
      |
    310..319: // MOV
      Param:=RegisterArray[ParamByte];
      RegisterArray[myIndex]:=Param;
      if subCh=319 then  // DispReg, gleich auf Anzeige bringen
        ParamToStr;
        ParamStr2LED;
      endif;
      |
    350..359: // XCH mit Parameter, zwei beliebige Register tauschen
      if ParamByte in [0..9] then
        Param:=RegisterArray[ParamByte];
        RegisterArray[ParamByte]:= RegisterArray[myIndex];
        RegisterArray[myIndex]:=Param;
      endif;
      |
    500: // PUT
      Param:=RegisterArray[ParamByte];
      WriteTransferParamSer;
      |
    900..999: // FWR FileWrite Registerwert in DATAxx.XLS
      myIndex:=byte(SubCh-900);
      DataWrite(myIndex,ParamByte);
      |
    240:
      FileNum:=ParamByte;
      if CardOK then
        FileLoad;
      endif;
      if FaultFlags<>0 then
        serprompt(FileErr);
        return;
      endif;
      |
    250: // EEPROM Write Enable
      |
    251: //Error-Count
      ErrCount:=integer(ParamInt);
      |
    252: // Serielle Baudrate, erst beim nächsten Reset aktiv
      if EEunLocked then
        EESerBaudReg:=ParamByte;
      else
        serprompt(LockedErr);
        return;
      endif;
      |
   else
    serprompt(ParamErr);
    return;
  endcase;
  EEunLocked:=false;
  if SubCh=250 then  // EEPROM Write Enable
    EEunLocked:=true;
  endif;
  if verbose then
    serprompt(noErr);  // = ParamErr wenn Limits erreicht
  endif;
{$IFDEF LCDGRAPH}        // LCD-Grafik statt LED
  if InitFileIsOpen and mySDrefresh then
    gdrawbitmapN(112, 0, @sdcardbitmap, wmsetpix);
    gdisprefresh;
  endif;
{$ENDIF}
end;

{###########################################################################}
// Neuer Alpha-Parser, nimmt in ParamStr auch Strings nach "=" entgegen

{$IFDEF PARSERESULT}        // Results entgegennehmen
procedure ParseTransferParam;
begin
  if SubCh=TransferSubCh then
    if DestinationRegister in [0..9] then
      RegisterArray[destinationRegister]:=Param;
      if DestinationRegister=9 then  // gleich auf Anzeige bringen
        ParamToStr;
        ParamStr2LED;
      endif;
    endif;
    GetWaitFlag:=false; // angefordertes Ergebnis eingetroffen
    SerInpStr:='';
  endif;
end;
{$ENDIF}

// allgemeiner Parser-Teil

function Cmd2Index : byte;
// Umsetzen eines Text-Befehls in Index-Eintrag der Befehlstabelle
var myCmdIndex: byte;
begin
  ParamStr:= uppercase(ParamStr);
  for myCmdIndex := 0 to CmdAnzahl do
    if ParamStr = CmdStrArr[ord(myCmdIndex)] then
      return(myCmdIndex);
    endif;
  endfor;
  return(cmderr);
end;

function ParseExtract(nachGleich:boolean):boolean;
//extrahiert ParamStr oder CmdStr aus SerInpStr,
//liefert true, wenn Parameter, sonst false, wenn Command
//akzeptiert auch alphanumerische Parameter als String nach "="
var myChar: char; myBool:boolean;
begin
  ParamStr:='';
  ParamAlpha:=false;
  myBool:=false;
  while SerInpStr[SerInpPtr] =' ' do // Leerzeichen überspringen
    inc(serInpPtr);
  endwhile;
  if SerInpStr[SerInpPtr] in ['*'..'9'] then // Zahlen oder Wildcard, es wird ein Parameter
    myBool:=true;
    for i:=SerInpPtr to length(SerInpStr) do
      mychar:=SerInpStr[i];
      if myChar in ['*'..'9'] then
        append(mychar,ParamStr);
      else // Buchstabe oder sonstirgendwas, abbrechen
        SerInpPtr:=i;
        return(mybool);
      endif;
    endfor;
  else
    for i:=SerInpPtr to length(SerInpStr) do
      mychar:=SerInpStr[i];
      if (myChar >= 'A') or ParamAlpha then
        append(mychar,ParamStr);
        if nachGleich then
          ParamAlpha:=true;
        endif;
      else // Ziffer oder sonstirgendwas, abbrechen
        SerInpPtr:=i;
        return(mybool);
      endif;
    endfor;
  endif;
  return(mybool);
end;

procedure ParamStr2Param;
begin
  Param:=StrToFloat(ParamStr);
  ParamInt:=integer(Param);
  ParamByte:=byte(ParamInt);
end;


procedure ParseSubCh;
//Überprüfen, ob Befehl oder Daten eingegangen und für uns, Parser-Vorbehandlung,
//Achtung: Erweitert um ParseResult für Anzeige-Auswertung, KEIN REFERENZ-PARSER!

var
  GleichPos,
  CheckSpos, myCheckSum,CheckSumIn:byte;
  SubChOffset:integer;

  myChar:char;
  hasMainCh, hasCRC, isResult, isOmni, isRequest: Boolean;

begin
  if SerInpStr='' then
    serprompt(NoErr);
    return;
  endif;
  hasMainCh:=(pos(':',SerInpStr)>0); // Kanaltrenner-':'
  gleichPos:=pos('=',SerInpStr); // Set-'='
  isRequest:=(GleichPos=0); // Abfrage
  myChar:=SerInpStr[1];
  isOmni:=(myChar='*');    // Omni-Flag
  isResult:=(myChar='#');  // Ergebnis-'#'
  SerInpPtr:=1;
  if isResult then
    SerInpPtr:=2;
  endif;
  if hasMainCh then
    ParseExtract(false);
    inc(SerInpPtr);
    if isOmni then // Omni-Befehl
      WriteSerInp; // weiterleiten
    else
      CurrentCh:=StrToInt(ParamStr);
    endif;
  endif;
  if isResult and (CurrentCh=TransferCh) then
    if not INITfileIsOpen then
      WriteSerInp;
    endif;
    ParseResult:=true;
  else
    if (not isOmni) and (CurrentCh<>MainCh) and hasMainCh then // nicht für uns
      WriteSerInp;
      return;
    endif;
  endif;

// Befehl/Parameter ist für uns, ab hier eigentliche Behandlung
// Wenn vorhanden, XOR-Prüfsumme checken, Präfix-"$" zählt nicht mit!
  verbose:=(pos('!',SerInpStr)>0) OR (pos('?',SerInpStr)>0); // Ausführliche Antwort erwünscht
  CheckSpos:=pos('$',SerInpStr);
  hasCRC:=CheckSpos>0; // CheckSumIn-'$'

  if hasCRC then
    ParamStr:=copy(SerInpStr,CheckSpos+1, 2);
    CheckSumIn:=HexToInt(ParamStr); // erhaltene XOR-Checksumme
    myCheckSum:=0;
    for i:= 1 to CheckSpos-1 do
      myChar:=SerInpStr[i];
      myCheckSum:=myCheckSum xor ord(myChar);
    endfor;
    if myCheckSum <> CheckSumIn then
      serprompt(CheckSumErr);
      return;
    endif;
  endif;
//  SerInpStr:=trim(SerInpStr);

//Parse einzelnen Befehl
  if ParseExtract(false) then
    SubChOffset:=0; // direkter SubCh-Aufruf
  else
    CmdWhich:=Cmd2Index; // Klartext übersetzen
    if CmdWhich = cmderr then
      serprompt(SyntaxErr);
      return;
    endif;
    SubChOffset:=Cmd2SubChArr[ord(CmdWhich)];
    ParseExtract(false); // SubCh-Parameter holen
  endif;
  SubCh:=StrToInt(ParamStr)+SubChOffset; //auf neuen SubCh umrechnen
{$IFDEF PARSERESULT}        // Results entgegennehmen
  if ParseResult then
    if not isRequest then
      SerInpPtr:=gleichPos+1; // Set-'='
      ParseExtract(true);
      ParamStr2Param;
      ParseTransferParam;
    endif;
  else
{$ENDIF}
    if isRequest then
      ParseGetParam;
    else
      SerInpPtr:=gleichPos+1; // Set-'='
      if ParseExtract(true) then
        ParamStr2Param;
      else
        Param:=0;             // Alpha-Parameter
        ParamStr2Param;
      endif;
      ParseSetParam;
    endif;
{$IFDEF PARSERESULT}        // Results entgegennehmen
  endif;
  ParseResult:=false;
{$ENDIF}
end;

