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