Forum: Mikrocontroller und Digitale Elektronik String parsen mit strtok


von AVR_Dude (Gast)


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

von Sigi (Gast)


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.

von AVR_Dude (Gast)


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?

von Sigi (Gast)


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.

von Joachim B. (jar)


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

von Sheeva P. (sheevaplug)


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.

von AVR_Dude (Gast)


Angehängte Dateien:

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:
1
if (extern_string[0] == 'S' || extern_string[0] ==  's')
2
      {
3
        if (extern_string[1] == 'H' || extern_string[1] ==  'h')
4
        {
5
          if (extern_string[2] == '1')
6
          {
7
            if (extern_string[3] == '.')
8
            {
9
              if (extern_string[4] == '1')
10
              {
11
                if (extern_string[5] == ';')
12
                {
13
                  setze_position1.1();
14
                  put_string(extern_string);
15
                }
16
              }
17
            }
18
          }
19
        }
20
        
21
        if (extern_string[1] == 'L' || extern_string[1] ==  'l')
22
        {
23
          if (extern_string[2] == '1')
24
          {
25
            if (extern_string[3] == '.')
26
            {
27
              if (extern_string[4] == '1')
28
              {
29
                if (extern_string[5] == ';')
30
                {
31
                  loesche_position1.1();
32
                  put_string(extern_string);
33
                }
34
              }
35
            }
36
          }
37
        }
38
      }

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

von Peter II (Gast)


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.

von Olaf (Gast)


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

von 0815 (Gast)


Lesenswert?

Habe die Zerlegung im reinen
K&R - C. Getestet auf den Arduino.


1
int i;
2
int ch;
3
int Pos, Block;
4
int LowHigh;
5
char input [10];
6
7
 
8
void setup() {
9
  strcpy(input, "SH3.12;");   //fuer Test
10
  if(input[1] == 'H')
11
     LowHigh = 1;
12
  else
13
     LowHigh = 0;
14
15
 Block = input[2] - '0';
16
17
 for(i=4; input[i]; i++)
18
   if(input[i] == ';')
19
      input[i] = '\0';
20
21
    sscanf(input + 4, "%d", &Pos);
22
           
23
Serial.begin(9600);
24
Serial.print("LowHigh = "); Serial.println(LowHigh);
25
Serial.print("Block = "); Serial.println(Block);
26
Serial.print("Pos = "); Serial.println(Pos);
27
28
29
}
30
31
void loop() {
32
  // put your main code here, to run repeatedly:
33
34
}

von Joachim B. (jar)


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
von Tom (Gast)


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.

von Joachim B. (jar)


Lesenswert?

AVR_Dude schrieb:
> Jede Anweisung wird mit ";" beendet.

und wenn das fehlt oder nie kommt?

von Jobst Q. (joquis)


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);
 ...

von AVR_Dude (Gast)


Lesenswert?

danke für den Ansatz, Jobst Quis.

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

'block' hat danach allerdings den Wert 0.
1
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?

von AVR_Dude (Gast)


Lesenswert?

okay, <stdlib.h> hat gefehlt.

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

von AVR_Dude (Gast)


Lesenswert?

Die erste Version läuft soweit, vielen Dank an alle.

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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.