Wie definiert man Kommunikationsprotokolle?
Es geht um einen einfachen Fall mit wenigen Befehlen und Unterbefehlen
bei unterschiedlicher Befehlslänge sowie unterschiedlichen Antworten.
Ist es zweckmäßig, die Schnittstelle mit allen Befehlen und Antworten
als Baum darzustellen?
Beispiel:
1
Befehl 1
2
Unterbefehl 1
3
Daten 1
4
Daten 2
5
<- Antwort
6
Unterbefehl 2
7
Daten
8
<- Antwort 1
9
<- Antwort 2
10
Befehl 2
11
<- Antwort
12
Befehl 3
13
Unterbefehl 3
14
...
Eine Kommunikation ist demnach ein Weg durch den Baum. Der Baum stellt
also alle Möglichkeiten dar.
Vorteil ist: es ist eindeutig. Nachteil ist, dass es viele Baumebenen
sind. Was sind die Alternativen?
Ich kenne mehr so formale Sprachen BNF, EBNF, Charts in Form von
Eisenbahnen usw. (So ein Chart lag der ersten Version von K&R bei)
Bäume kenne ich nicht. Das Beispiel finde ich total unübersichtlich.
Hank schrieb:> Befehl 1> Unterbefehl 1> Daten 1> Daten 2> <- Antwort> Unterbefehl 2> Daten> <- Antwort 1> <- Antwort 2> Befehl 2> <- Antwort> Befehl 3> Unterbefehl 3> ...
Wenn dann ist das irgendwie falsch... Das müsste meiner Meinung nach
dann für eine Baumstruktur so sein:
1
Befehl 1
2
Unterbefehl 1
3
Daten 1
4
Daten 2
5
<- Antwort
6
Unterbefehl 2
7
Daten
8
<- Antwort 1
9
<- Antwort 2
10
Befehl 2
11
<- Antwort
12
Befehl 3
13
Unterbefehl 3
14
...
Weil Daten 1 und 2 sowieo Antwort 1 und 2 liegen ja auf den gleichen
Ästen.
Mit einem Baum kannst du keine Schleifen abbilden. Sowas wie "Header -
beliebige Anzahl an Parametern - Footer" für eine Nachricht geht damit
nicht sinnvoll, weil du für jede mögliche Parameterreihenfolge einen
eigenen Teilbaum erzeugen musst.
Ein einfaches Protokoll kannst du mit einem Baum aber durchaus
beschreiben, beim Reverse Engineering fällt anfangs auch einer raus.
Rene K. schrieb:> Weil Daten 1 und 2 sowieo Antwort 1 und 2 liegen ja auf den gleichen> Ästen.
Das ist beabsichtigt, denn Daten 2 folgen auf Daten 1 - sie sind nicht
alternativ. Jeder Ast ist eine alternative Kommunikation.
Es sollte besser Datenpaket heißen. Was drin steckt, ist erstmal egal.
BNF ist wahrscheinlich kompakter, aber auch schwerer zu lesen.
Geht es um ein Protokoll das alle moeglichen Datenstrukturen, resp
Protokolle umfassen soll ? Die eierlegende Wollmilchsau ? Baut man
nicht. Bei embedded & mikrocontroller baut man etwas, das schnell und
einfach implementiert werden kann, also eher etwas flaches.
zB
[SYN][START][LAENGE][COMMAND][DATA][CRC]
[SYN][START][LAENGE][DESTINATION][COMMAND][DATA][CRC]
[SYN][START][LAENGE][SOURCE][DESTINATION][COMMAND][DATA][CRC]
dh es gibt nur eine Ebene Commands. Die muss man naemlich erst mal
implementieren und testen. Der Anspruch bei embedded & mikrocontroller
ist fehlerfreier code ! Daher sollte der moeglichst einfach sein.
Hank schrieb:> Wie definiert man Kommunikationsprotokolle?
In hunderten von verschiedenen Arten und weisen. Ohne zu wissen was
genau du machen willst ist ein Vorschlag schwer.
Offensichtlich bist du auf der Anwendungsschicht (Schicht 7). Dort sind
hierarchische Strukturen (Baumstruktur) bei Management-Protokollen
gängig. Geht es bei dir um das Gerätemanagement?
Die hierarchische Struktur wird beim Gerätemanagement, je nach
Management-Protokoll, in einer sogenannten Management Information Base
(MIB) https://de.wikipedia.org/wiki/Management_Information_Base
beschrieben (Aber Vorsicht, nur weil in dem Artikel eine MIB anhand von
SNMP erklärt wird sollte man allerdings nicht SNMP nehmen. SNMP kann
einen in den Wahnsinn treiben).
Grundsätzlich hast du bei einer Baumstruktur immer das Problem, welche
Ordnungskriterien man nimmt. Wenn man Hardware verwaltet dann
strukturiert man die MIB häufig entlang der Hardware. Beim Managen von
Software hat man häufig nicht mal Anhaltspunkte für eine natürliche
Struktur. In beiden Fällen bleibt das Problem, dass die Struktur
trotzdem nicht immer zwingend ist.
Zum Beispiel kann bei einer Spannungsüberwachung sowohl eine Struktur
der Form
Modul-X/Rail-Y
Also auch
Rail-X/Modul-Y
sinnvoll sein.
> Es geht um einen einfachen Fall mit wenigen Befehlen und Unterbefehlen> bei unterschiedlicher Befehlslänge sowie unterschiedlichen Antworten.
Befehls- und Antworten-Längen sind doch das kleinste Problem. Entweder
verwendet man ein Ende-Zeichen (beliebt ist bei ASCII-Kodierung zum
Beispiel das Neue-Zeile Zeichen :-)), oder man kodiert die Länge mit
rein.
> Ist es zweckmäßig, die Schnittstelle mit allen Befehlen und Antworten> als Baum darzustellen?
Siehe oben. Das hat sich bei bestimmten Anwendungen bewährt.
Danke soweit für die Anregungen.
Die zwei Ebenen sind bereits festgelegt. Ich suche nur noch nach einem
Modell.
Ein Baum zur Darstellung möglicher Sequenzen ist wohl nicht ganz falsch.
Evtl. lassen sich Datensequenzen (Daten 1, Daten 2) als Datenstruktur
zusammenfassen. Aber das ist Sache des Protokolls, nicht unbedingt der
Abbildung.
Oh D. schrieb:> einfach implementiert werden kann, also eher etwas flaches.> zB>> [SYN][START][LAENGE][COMMAND][DATA][CRC]> [SYN][START][LAENGE][DESTINATION][COMMAND][DATA][CRC]> [SYN][START][LAENGE][SOURCE][DESTINATION][COMMAND][DATA][CRC]
Ich mache es mit:
[SOF][DESTINATION][SOURCE][COMMAND][LENGTH][DATA][CS][EOF]
Vorteil ist, dass schon beim zweiten Byte, wenn die Zieladresse
nicht stimmt, rausgesprungen werden kann, so dass die ISR schnell
abgearbeitet wird - nur check auf EOF und neues SOF.
Was das mit Befehl und Unterbefehl sein soll, ist mir ein bißchen
unklar.
Ich habe Befehle von 0x10 - 0x70, bei Antwort wird bit7 auf 0
gesetzt, demzufolge gehen mögliche Antworten von 0x90 - 0xF0.
Befehle 0x00-0x0F und 0xF1-0xFF sind reserviert.
Mit bits4-6 werden 8 Gruppen bezeichnet, jede mit 16 Befehlen und
es wird eine select-case Abfrage nach Gruppen gemacht, z.B. ist
Gruppe 1 Sensoren, Gruppe 2 Aktuatoren, Gruppe 3 Displays usw.
Jede Gruppe wird dann wieder mit select-case abgefragt.
Da ich in meinem Editor Collapse und Expand habe, bleibt das Ganze
ziemlich übersichtlich, wobei übersichtlich auch relativ ist - bei
mehr als 100 Befehlen kann das nie so übersichtlich werden wie ich
es gerne möchte...
Da ist wohl vieles Meinungssache!
Ich ziehe ein Protokoll vor, bei dem die Blocklänge möglichst früh
festliegt.
Also ist die Länge konstant, oder implizit durch das Schlüsselbyte
festgelegt, so sollte das Schlüsselbyte möglichst früh erscheinen. Ist
die Länge variabel, so sollte das Längenbyte möglichst früh kommen.
Der Hintergedanke dabei ist der, dass einer der wichtigsten Schritte die
vollständige Sammlung des jeweiligen Befehls ist.
Vorher ist keine Vollständigkeitsprüfung möglich und eine Überprüfung
einer CRC-Kennung ist auch nicht machbar.
S. R. schrieb:> Mit einem Baum kannst du keine Schleifen abbilden. Sowas wie "Header -> beliebige Anzahl an Parametern - Footer" für eine Nachricht geht damit> nicht sinnvoll, weil du für jede mögliche Parameterreihenfolge einen> eigenen Teilbaum erzeugen musst.
Da wird bei einem Baum gerne mittels einer Tabelle oder Liste gepfuscht.
Das Protokoll bekommt neben einer normalen Lese-Funktion (get) noch eine
Lese-weiter-Funktion (getNext), mit der durch die Zeilen einer Tabelle
durch-iteriert wird, ohne dass man jede Zeile einzeln adressieren muss.
Hank schrieb:> PittyJ schrieb:>> Charts in Form von>> Eisenbahnen usw. (So ein Chart lag der ersten Version von K&R bei)>> Was ist die genaue Bezeichnung?
"Railroad Diagram" oder "Syntax Diagram". Letzteres sagt dann auch was
es wirklich ist, eine Darstellung einer Syntax. Das kann man enbensogut
mit BNF machen. Ob das was man darstellt eine Baumstruktur hat
interessiert weder BNF noch Railroad-Diagramme.
Es gibt eine Uebertragungsschicht, die ist mit der Uebertragung selbst
beschaeftigt, und das andere packt man in das Datenpacket.
Der Sender zaehlt einfach die Anzahl Zeichen runter bis keins mehr da
ist.
Der Empfaenger zaehlt die Anzahl Zeichen hoch, bis alle da sind. Dann
wird die Meldung auseinandergenommen und bearbeitet.
Es gibt je nach Anforderung praktischere Wege, und weniger praktische.
Es gibt Wege, beim Verwerfen einer Meldung moeglichst schnell wieder
aufsetzen zu koennen, Wege zum Fehler detektieren, korrigieren, je nach
erwartbarem Fehler. Dann sollte man sich Gedanken machen was bei einer
ungueltigen Meldung geschehen soll.
Ich lass die Meldung jeweils abgezaehlt aus dem Interrupt uebergeben,
und pruefe dann im Main, ob Adressierung und CRC passen. Und dann erst
kommt die Verarbeitung. Es ist aber auch denkbar eine Meldung vorher zu
verwerfen, wenn schon das erste Feld nicht passt.
Amateur schrieb:> Da ist wohl vieles Meinungssache!>> Ich ziehe ein Protokoll vor, bei dem die Blocklänge möglichst früh> festliegt.
Wenn die Nachricht nicht für mich ist, wozu brauche ich die Länge ?
In diesem Fall wird ganz einfach auf EOF und dann neues SOF gewartet
um die ISR so schnell wie nur möglich wieder verlassen zu können.
> Vorher ist keine Vollständigkeitsprüfung möglich und eine Überprüfung> einer CRC-Kennung ist auch nicht machbar.
SOF und die eigene Adresse bleiben immer konstant, damit fängt die CRC
an, das wird nicht gerechnet.
Und die CRC sollte sowieso in main() berechnet, bzw. überprüft werden.
Marc V. schrieb:> Ich mache es mit:> [SOF][DESTINATION][SOURCE][COMMAND][LENGTH][DATA][CS][EOF]
Wegen [LENGTH] und [CS] ist meiner Meinung nach [EOF] unnötig.
Mehmet K. schrieb:> Wegen [LENGTH] und [CS] ist meiner Meinung nach [EOF] unnötig.
Wenn die Nachricht nicht für mich ist oder der uC hat sich irgendwie
verlaufen, warte ich auf:
[EOF][SOF][DESTINATION], das sind 1:16,777,216 Möglichkeiten, dass
die Bytes in [DATA] in exakt dieser Reihenfolge kommen.
Kritische Befehle müssen sowieso innerhalb von 5ms bestätigt werden,
ansonsten werden die wiederholt, somit kann kein Gerät am Bus länger
als 5ms in undefiniertem Zustand bleiben.
Amateur schrieb:> Da ist wohl vieles Meinungssache!
Und noch mehr Möglichkeiten gibt's, sich ins Knie zu schiessen.
Grundsätzlich stellt sich immer die Frage:
a) Paketbasiert oder
b) Streambasiert (SOF/EOF)
(b) ist für Zero-Copy (DMA) ungeeignet wegen der Escape-Problematik, man
ergo für ein Paket nie die Länge genau voraussagen kann, aber wenn eh
bei jedem eintreffenden Zeichen ein ISR angesprungen wird, lässt sich
damit arbeiten. Mein Fazit ist, nach vielen Jahren Gefriemel mit div.
Protokollen: Lieber paketbasiert mit sauber definierten
Timeout-Szenarien.
>> Ich ziehe ein Protokoll vor, bei dem die Blocklänge möglichst früh> festliegt.
Dito :-)
Was die Befehle angeht: auch so ein Streitpunkt, aber ich würde mal
behaupten, dass sich in der Industrie die einfachen Registerprotokolle
durchgesetzt haben. Alles kommandobasierte wird tendentiell schwerfällig
und aufwendiger zu implementieren.
D.h. man sollte eigentlich von so einer Beschreibung wie einem Baum auf
der Protokoll-Ebene eher wegwollen.
Die Baumstruktur ist dann auf dem Endgerät sinnvoll, wenn man eine
Möglichkeit haben will, abstrahierte Eigenschaften (nicht zwingend
Register) des Geräts abzufragen. Dafür benötigt man aber nur wenige
Primitiven. Siehe Bibliotheken wie div. modbus-Implementationen,
wireless-Ansätze wie SWAP oder abstrahierendere Protokolle wie netpp
(quelloffene Eigenentwicklung) oder Google protocol buffers.
Tobias:
Take a pick:
- Datenblatt eines einfachen i2c-GPIO: PCA9534
- Etwas komplexer: modbus
Also, statt Befehle parsen gibts Protokoll-Primitiven:
a) [READ] [REGISTER_ADDR] ¦ [ACK] ¦ [RDATA]
b) [WRITE] [REGISTER_ADDR] [DATA] ¦ [ACK]
Der ¦ trennt die Protokoll-Phasen (state-Maschine), ACK (acknowledge):
Gut gelaufen, oder eben nicht.
Die End-Hardware muss so nur noch die REGISTER_ADDR (also eine Zahl)
decodieren. Leichtgewichtiger gehts kaum.
Ja, sinnvollerweise ist ein Command etwas Kurzes, ein Register lesen
oder schreiben waere optimal, manchmal muss man noch etwas kurzes
Rechnen. Wenn man zB Werte lieber als ASCII uebertragen will, das
Register aber Integer oder float ist. Einen Prozess anstossen und
abwarten ist nicht so praktisch.
Ebenso sinnvoll ist es das Protokoll Zustandslos zu entwerfen. Also,
dass die Packete unabhaengig voneinander sind, und auch einzeln
wiederholt werden koennen. Das bedeutet dann zB, dass bei einem
Datentransfer eine Blocknummer dabei ist, jeder Block einzeln abgefragt
werden kann.
Moin!
Ich verwende für meine seriellen Protokolle eine simple Bibliothek.
Sie arbeitet binär und verwendet zur Identifikation ein einzelnes Byte.
Die Befehle werden mit fixen Längen in einem Array definiert, dass macht
es simpel neue Befehle hinzuzufügen.
Nachteil: Gehen tatsächlich Bytes verloren kann es zu
missinterpretierten Daten kommen :/
Hier die lib:
https://github.com/momagu/CommandAggregator
Viele Grüße,
Moritz
Hey!
Ein häufig verwendeter Ansatz für Bäume in der Vergangenheit war der
'logische Token Ring'. Wurde bzw. wird teilweise auch heute noch
verwendet im Zusammenhang mit ARCnet.
Beste Grüße!