mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik String parsen mit strtok


Autor: AVR_Dude (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich muss auf einer neu entwickelten Hardware auf AVR-Basis ein 
bestehendes Protokoll zur Kommunikation/Steuerung implementieren.

Die Kommandos, die dekodiert werden müssen, haben folgendes Schema:

zB: "SH1.0;" bedeutet Block 1, Position 0, High.

Auch möglich wäre beispielsweise "SL4.12;", also: Block 4, Position 12, 
Low.

Jede Anweisung wird mit ";" beendet.

Ich muss also die drei Informationen Block, Position und High/Low für 
die Weiterverarbeitung herausfiltern.

Mir ist klar, dass ich diese Aufgabe mit strtok lösen kann, mir fehlt 
nur der Ansatz.

Für diesen Gedankenanstoß wäre ich dankbar.

Grüße
AVR_Dude

Autor: Sigi (Gast)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
Deine Beispiele nach zu urteilen lässt sich
Dein Problem mit einem endlichen Automaten
(FSM) schnell und einfach lösen.
Der erste Zustand fängt "S" ab, der 2. prüft
auf "L" oder "H" und der 3. liest eine Zahl.
Dann ein Punkt und nochmal eine Zahl, final
dann das Semikolon. Ist recht einfach.

Autor: AVR_Dude (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ja diese Unterteilung sollte funktionieren.

Ich habe vergessen zu erwähnen: Eine fehlerhafte Eingabe soll zu einem 
Verwerfen des aktuellen Strings führen.

Wie sähe die passende Grundstruktur in C dazu aus?

Autor: Sigi (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Such mal nach FSM etc. hier im Forum, da
gibt's etliche Beispiele.

Einfacher Ansatz: Nimm einen Pointer P auf
das Erste Zeichen. P wird dann je FSM-Zustand
an eine Scan-Funktion übergeben (z.B. "S"
erkennen). Ist das Ergebnis positiv, dann
wird P erhöht, sonst nicht. Evtl. müssen
mehrere Funktionen aufgerufen werden (nicht-
deterministischer Automat), wird dabei kein
positives Ergebnis geliefert, dann liegt
ein Fehler vor.

Der erste Schritt dazu ist, erst einmal den
Automaten zu modellieren. Dann die Funktionen
schreiben und letztendlich die Hauptschleife,
die den FSM-Ablauf steuert und die Funktionen
aufruft.

Fehlererkennung: Eine allgem. Bulletproof-
Lösung gibt's nicht. Bei Dir hilft es, das
Semikolon zu suchen und dann P dorthin
zu setzen.

Autor: Joachim B. (jar)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
erscheint mir einfach, nur DU musst wissen was für dich Fehler sind.

Ist nach dem ; Ende?
wird ein 0 Terminator erwartet oder CR oder CR LF oder gar nur LF?

Darf es nach dem ; weitergehen und wird ignoriert?
Darf es genau nur bis ; gehen und jedes weitere Zeichen wäre ein Fehler?

Ist S immer an erster Stelle und H oder L immer an 2ter Stelle dann 
wurde das Suchkriterium schon genannt.

Sollte SH oder SL irgendwo in der Kette liegen dann wäre strstr "SH" 
oder "SL" die richtige Methode um zu starten und sich von dort bis zum 
Ende zu hangeln.

Darf die Zahl nach dem SH/SL und vor dem . nur 1-stellig sein oder wird 
die evtl.(später) mehrstellig?

Ist lange her da hatte ich so einen Parser in der Prüftechnik für 
Baugruppenrahmen mit Relais

"set, 1, 3, 567"
"reset, 2, 6, 4"

setze
1. BG Rahmen (Baugruppen Rahmen)
2. Relaiskarte
3. Relais

Autor: Sheeva Plug (sheevaplug)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
AVR_Dude schrieb:
> Die Kommandos, die dekodiert werden müssen, haben folgendes Schema:
>
> zB: "SH1.0;" bedeutet Block 1, Position 0, High.
> [...]
> Ich muss also die drei Informationen Block, Position und High/Low für
> die Weiterverarbeitung herausfiltern.
>
> Mir ist klar, dass ich diese Aufgabe mit strtok lösen kann,

strtok(3) braucht einen Delimiter, in Deinen Kommandos hast Du aber 
keinen. Du hast nur einen Delimiter zwischen Deinen Kommandos, darum 
kannst Du strtok(3) nur nehmen, um einen Stream von mehreren Kommandos 
in einzelne Kommandos zu separieren. Um dann die Werte aus den 
vereinzelten Kommandos auszulesen, ist strtok(3) nicht geeignet; dazu 
brauchst Du sowas wie sscanf(3) oder etwas Ähnliches.

Autor: AVR_Dude (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
okay danke erstmal für den Input.

Ich habe jetzt mal eine erste (noch nicht vollständige) Version einer 
State Machine für meine Anwendung entworfen.

Um eine erste erste lauffähige Implementierung zu haben, habe ich für 
eine einzelne Position eine komplizierte Struktur mit if-Verzweigungen 
aufgebaut:
if (extern_string[0] == 'S' || extern_string[0] ==  's')
      {
        if (extern_string[1] == 'H' || extern_string[1] ==  'h')
        {
          if (extern_string[2] == '1')
          {
            if (extern_string[3] == '.')
            {
              if (extern_string[4] == '1')
              {
                if (extern_string[5] == ';')
                {
                  setze_position1.1();
                  put_string(extern_string);
                }
              }
            }
          }
        }
        
        if (extern_string[1] == 'L' || extern_string[1] ==  'l')
        {
          if (extern_string[2] == '1')
          {
            if (extern_string[3] == '.')
            {
              if (extern_string[4] == '1')
              {
                if (extern_string[5] == ';')
                {
                  loesche_position1.1();
                  put_string(extern_string);
                }
              }
            }
          }
        }
      } 

Dieses Konstrukt wird aber unfassbar kompliziert, zumal ich 4*16 
Zustände abfragen würde...

Autor: Peter II (Gast)
Datum:

Bewertung
-1 lesenswert
nicht lesenswert
AVR_Dude schrieb:
> Dieses Konstrukt wird aber unfassbar kompliziert

ja, aber das liegt an dir.

So wie du es programmiert hast, kannst du auch gleich für jede 
Möglichkeit ein Stringvergleich machen.


Dein Ablaufplan sieht doch gar nicht so schlecht aus, den musst du nur 
etwas geschickter umsetzen.

Autor: Olaf (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Für diesen Gedankenanstoß wäre ich dankbar.

Vergiss den ganzen Mist und lass dir deinen Parser automatisch von flex 
generieren. Das hat sich bei mir sehr gut bewaehrt.

Klar, beim erstenmal 2h zusaetzlich notwendige Einarbeitungszeit, aber 
bei jedem neuen Kommando oder einer kleinen Verbesserung sind es nur 
Sekunden und alles ist gut.

Olaf

Autor: 0815 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Habe die Zerlegung im reinen
K&R - C. Getestet auf den Arduino.



int i;
int ch;
int Pos, Block;
int LowHigh;
char input [10];

 
void setup() {
  strcpy(input, "SH3.12;");   //fuer Test
  if(input[1] == 'H')
     LowHigh = 1;
  else
     LowHigh = 0;

 Block = input[2] - '0';

 for(i=4; input[i]; i++)
   if(input[i] == ';')
      input[i] = '\0';

    sscanf(input + 4, "%d", &Pos);
           
Serial.begin(9600);
Serial.print("LowHigh = "); Serial.println(LowHigh);
Serial.print("Block = "); Serial.println(Block);
Serial.print("Pos = "); Serial.println(Pos);


}

void loop() {
  // put your main code here, to run repeatedly:

}


Autor: Joachim B. (jar)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
0815 schrieb:
> Habe die Zerlegung im reinen

ist aber wirklich 0815

was wenn if(input[0]!='S' ist?
was wenn strcpy(input[10], überläuft?
was wenn '.' ein ',' ist?
was wenn input[2] ein '?' o.a. ist?
was wenn nie ein ';' kommt?
 for(i=4; input[i]; i++)
   if(input[i] == ';')

Ich würde ja die Eingabezeile erst mal auf Länge testen
dann mit strstr nach "S" suchen ptr drauf setzen und auf *++ptr (wenn 
nicht 0) H oder L prüfen

und auf *++ptr (wenn nicht 0) auf >='0' && <='9' prüfen
und auf *++ptr (wenn nicht 0) auf '.' prüfen
strstr(ptr; ";") und strlen(ptr) prüfen

: Bearbeitet durch User
Autor: Tom (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Man muss nicht auf Länge testen (bei ein-/mehrstelligen Zahlen geht das 
sowieso daneben), sondern kann zeichenweise durchgehen, z.b. gleich im 
Empfangsinterrupt.


ANFANG:
c == 'S';  ~>LESE_HOEHE
sonst: ~>ANFANG

LESE_HOEHE:
c =='H': hoehe = high; ~>LESE_BLOCK_ERSTE
c =='L': hoehe = low; ~>LESE_BLOCK_ERSTE
sonst: ~>ANFANG

LESE_BLOCK_ERSTE:
c == '0'..'9': block = int_from(c); ~>LESE_BLOCK_REST
sonst: ~>ANFANG

LESE_BLOCK_REST:
c == '0'..'9': block*=10; block += int_from(c); ~>LESE_BLOCK_REST
c == '.':  ~>LESE_POSITION_ERSTE
sonst: ~>ANFANG

usw.

Autor: Joachim B. (jar)
Datum:

Bewertung
1 lesenswert
nicht lesenswert
AVR_Dude schrieb:
> Jede Anweisung wird mit ";" beendet.

und wenn das fehlt oder nie kommt?

Autor: Jobst Quis (joquis)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
strtok brauchst du dafür nicht, außer dem Zahlen einlesen mit strtol ist 
alles mit einfachen Pointervergleichen machbar.

char* parse_cmd(const char *s){ /* Rückgabe zeigt auf Stringende oder 
auf erstes fehlerhaftes Zeichen */

  int block,pos,val;

  if(*s=='S'){
    s++;
    if(*s=='H')val=1;
    else if(*s=='L')val=0;
    else return s;
    s++;
    block = strtol(s, &s, 10);
    if (*s!='.')return s;
    s++;
    pos = strtol(s, &s, 10);
    if (*s!=';')return s;
    s++;
    set_output(block,pos,val); /* Befehl ausführen */
    return s;
    }

  /* hier evt Befehle mit anderen Anfangsbuchstaben */

  }

Aufruf mit Fehlermeldung:
 ...
 s=parse_cmd(input_str);
 if( *s)printf( "kann nicht verstehen: %s\n",s);
 ...

Autor: AVR_Dude (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
danke für den Ansatz, Jobst Quis.

ich übergebe parse_cmd meinen string, komme dann auch bis
block = strtol(s, &s, 10);

'block' hat danach allerdings den Wert 0.
if (*s!='.')return s;

wird dann aber wieder korrekt übersprungen, die Pointer Position wandert 
also korrekt weiter.

Woran könnte es liegen, dass strtol 0 zurückliefert?

Autor: AVR_Dude (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
okay, <stdlib.h> hat gefehlt.

Zuvor hat er aber nicht gemeckert, dass er die Funktion nicht kennt.

Autor: AVR_Dude (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die erste Version läuft soweit, vielen Dank an alle.

Wenn nochmal Fragen/Ergänzungen aufkommen, melde ich mich.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.