Moin Moin,
es dreht sich zwar um eine PC Anwendung aber es hat jedoch viel mehr mit
der Logik zu tun als mit dem PC.. Daher poste ich jetzt mein Problem
einfach mal hier..
Ich empfange von einem µC Kommandos. Diese sind verschieden lang.
So sieht mein Protokoll aus.
1
/* Protokoll
2
*
3
* Startbyte[0] : '-'
4
* Startbyte[1] : '+'
5
* Nachrichtenlänge[2] : 0..255
6
* Anzahl Nutzdaten[3] : 0..255
7
* Daten Type[4] : 0..6
8
* Nachrichten Identifikation[5] : 0..255
9
* Funktionsrückgabe Code[6] : 0..255
10
* Checksumme[7] : 0..255
11
* Nutzdaten[8..n] : 0..255
12
*/
Ich versuche jeden Befehl anhand der beiden "Startbytes" zu erkennen.
Nach den beiden "Startbytes" kommt dann die Länge des ganzen Frames.
Solange ich diese Daten nicht empfangen habe, ist auch noch kein
"gültiges" Kommando im Speicher.
Das ganze klappt nicht zuverlässig. Mal werden Kommandos erkannt, mal
nicht. Ich gehe 100 % nicht richtig vor. Es scheitert bei Mir an der
Logik..
Lass dir mal den Puffer ausgeben wenn die Kommandos nicht erkannt
werden.
Der Puffer wird nach jedem Kommando geleert?
Der Controller sendet die Kommandos so langsam dass der PC ein Kommando
sicher abgearbeitet hat bevor das nächste kommt?
Andreas K. schrieb:> Lass dir mal den Puffer ausgeben wenn die Kommandos nicht erkannt> werden.> Der Puffer wird nach jedem Kommando geleert?> Der Controller sendet die Kommandos so langsam dass der PC ein Kommando> sicher abgearbeitet hat bevor das nächste kommt?
Mein ganzes Vorgehen ist einfach nur für den Ars***..
Jan H. schrieb:> /* Protokoll> *> * Startbyte[0] : '-'> * Startbyte[1] : '+'> * Nachrichtenlänge[2] : 0..255
Das kann auch nicht funktionieren. Damit Startbytes zuverlässig erkannt
werden, dürfen sie im den restlichen Daten nicht vorkommen. Ein gültiges
Protokoll besteht z.B. aus einem Startbyte STX und Daten nur aus
lesbaren ASCII-Zeichen wie Ziffern und Buchstaben - dann kann das
Startzeichen nicht in den Daten vorkommen. Es gibt auch viele andere
Möglichkeiten eindeutig zu kodieren. Für das Ende, z.B. EOT, ETX, CR,
gilt das Gleiche.
Wird nicht zwischen Start/Stop und Nutzdaten unterschieden, kann die
Software falsche Start- oder Stopbedingungen erkennen.
Georg
georg schrieb:> Wird nicht zwischen Start/Stop und Nutzdaten unterschieden, kann die> Software falsche Start- oder Stopbedingungen erkennen.
Ich bin offen für gute Ideen. Also lieber nicht die Länge mitgeben,
dafür ein End Byte?
Ich nehme an, dass es um einen TCP Datenstrom geht. Du liest diesen
Blockweise in deinen Buffer und wertest ihn aus. Dabei gehst du davon
aus, dass der Block beim Empfänger so ankommt, wie er gesendet wurde.
Das ist aber reine Glücks-Sache. Es wird nur im lokalen Netz zuverlässig
funktionieren - wenn überhaupt.
Du musst immer davon ausgehen, dass diene Blöcke von den
Netzwerk-Geräten in mehrere kleine zerstückelt werden und dass diese
wiederum in anderen Gruppierungen zusammengefügt werden.
Das heißt für deine Anwendung, dass du nach dem Empfang der
"Nachrichtenlänge" so lange hereinkommende Daten einsammeln musst, bis
diese Länge tatsächlich empfangen wurde.
Paketorientierte Übermittlung macht man übrigens besser mit dem UDP
Protokoll. Damit kannst du 1kB (eventuell auch etwas mehr) sicher am
Stück übermitteln. UDP stellt sicher, dass das ganze Paket entweder am
Stück ankommt, oder komplett verloren geht. Bei UDP fehlt allerdings
eine automatische Wiederholung der Übertragung nach Paketverlust. Die
müsstest du ggf. selbst implementieren.
Es geht rein um die Serielle Schnittstelle. Ich kann nicht erwarten,
dass wenn ich etwas empfangen habe, das auch wirklich das komplette
Kommando schon angekommen ist das ist glaube ich das größte Problem
Jan H. schrieb:> Es geht rein um die Serielle Schnittstelle.
Ach so.
> Ich kann nicht erwarten,> dass wenn ich etwas empfangen habe, das auch wirklich das komplette> Kommando schon angekommen ist das ist glaube ich das größte Problem
Ja.
yesitsme schrieb:> Hast du mal ein komplettes Beispiel, das man auch compilieren> kann?>> Irgendwie seh ich nicht, wie der Buffer wieder geleert wird.
Bringt dir ja nichts da du die Hardware nicht hast.
georg schrieb:> Jan H. schrieb:> /* Protokoll> *> * Startbyte[0] : '-'> * Startbyte[1] : '+'> * Nachrichtenlänge[2] : 0..255>> Das kann auch nicht funktionieren. Damit Startbytes zuverlässig erkannt> werden, dürfen sie im den restlichen Daten nicht vorkommen> Georg
Dafür habe ich ja auch einen Header dort stehen die Infos drin. Dieser
hat eine bestimmte Länge. Klar kann das auch mal in die Hose gehen.. Ist
aber sicherer als ohne Start Bytes..
Jan H. schrieb:> georg schrieb:>> Wird nicht zwischen Start/Stop und Nutzdaten unterschieden, kann die>> Software falsche Start- oder Stopbedingungen erkennen.>> Ich bin offen für gute Ideen. Also lieber nicht die Länge mitgeben,> dafür ein End Byte?
Nein, so ist das nicht gemeint.
Das Problem kann daran liegen, dass die Sequenz aus den Zeichen '-'
und '+' zusätzlich auch in den Nutzdaten vorkommen.
Wenn Du nach diesen beiden Zeichen suchst, wie Du es ja tust, ist dass
alleine also kein Zeichen, dass an dieser Stelle ein Datenrahmen
beginnt.
Das Problem wird durch ein zusätzliches Ende-Zeichen nicht gelöst. Denn
auch dieses Zeichen kann in den Daten vorkommen und ist also kein
zuverlässiges Kriterium für das Ende eines Datenrahmens.
Daher der Vorschlag, den Rahmenanfang und das Ende durch solche Zeichen
zu kennzeichnen die garantiert nicht in den Nutzdaten vorkommen. Da Du
aber bisher alle Werte zwischen 0 und 255 als Daten zulassen willst,
müsstest Du von dieser zweiten Bedingung abgehen. Dafür gibt es mehrere
Möglichkeiten.
1. Du lässt in den Nutzdaten die Zeichen '-' oder '+' nicht zu.
2. Du lässt in den Nutzdaten die Folge von '-' und '+' nicht zu.
3. Du lässt in den Nutzdaten überhaupt keine nicht-druckbaren
ASCII-Zeichen zu; oder anders herum: nur druckbare Zeichen. Genauer
gesagt keine ASCII-Steuerzeichen; nur solche die wir Menschen
interpretieren können. Zusätzlich wählst Du für den Rahmenanfang gerade
eines der ASCII-Steuerzeichen.
Da gibt es so einige Varianten. Vielleicht fallen Dir noch welche ein.
Es gibt noch einen anderen Aspekt.
Die einfachste Variante so einer Kommunikation ist eigentlich, dass
jemand eine Nachricht sendet, der Empfänger die Nachricht empfängt,
verarbeitet und eine Antwort sendet. Dann erst sendet der Sender eine
neue Nachricht auf die eine Antwort kommt.
Im Gegensatz dazu mag ein Sender, nicht immer auf ein Kommando warten,
sondern einfach willkürlich senden. Oder er mag vielleicht für bestimmte
empfangende Nachrichten länger brauchen als für andere, so das die
Reihenfolge der Antworten, nicht immer strikt der Reihenfolge der
empfangenen Nachrichten folgt.
Was ich damit sagen will: Falls der erste Fall zutrifft, bräuchtest Du
den Beginn der Nachricht nicht zwingend zu "suchen". Du wüsstest ja
definitiv, dass im Buffer die nächste Nachricht steht und wo sie steht.
Dazu gibt es einige Nebenbedingungen, wie etwa ein hoher Grad an
Sicherheit der Datenübertragung und weitere.
Aber vielleicht überlegst Du Dir das mal.
Zusätzlich fällt mir an Deinem Code noch auf, dass du an dieser Stelle:
1
Length=buffer[FrameStartIndex+2];
nur eines der Längenbytes aus dem Buffer liest, aber als Länge zwei
Bytes vorgesehen hast:
1
*Nachrichtenlänge[2]:0..255
Auffällig ist auch, dass Du hier:
1
*Nachrichtenlänge[2]:0..255
2
*AnzahlNutzdaten[3]:0..255
an sich eine doppelte, redundante Information zu haben scheinst .
Ich habe jedenfalls keine Idee, warum sich beide Grössenangaben anders
als durch die Differenz der Längen aus den immer gleichen Kopf-Daten
unterscheiden sollten. Und die ist ja konstant; immer die selbe.
Ausserdem ist mir unklar, warum die Nutzdaten ein Byte mehr als die
Nachrichtenlänge haben sollen, wenn ich (vielleicht fälschlich) annehme,
dass die Nutzdaten Teil der Nachricht sind. Dann könnten die Nutzdaten
nicht länger als die gesamte Nachrichtenlänge sein.
Kein Ringbuffer.. nee... Die Meldung muss im Rx Interrupt
auseinandergenommen werden. Mit einer Zustandsmaschine.
zB wie in https://www.ibrtses.com/embedded/shortmsgprotocol.html
Und das Hauptprogramm bekommt nur etwas mit, wenn eine Meldung da ist,
als solche erkannt wurde.
Jan H. schrieb:> Mein ganzes Vorgehen ist einfach nur für den Ars***..
Vielleicht etwas suboptimal, weil:
- das DataReceived-Ereignis in der Regel nicht das vollständige
Telegramm beinhaltet. Das ist ein eigener Thread, der Dir die Daten
zurückgibt, die bis zum Zeitpunkt eines erneuten Aufrufes im Puffer
angekommen sind. Abhilfe: Schreib die Daten zuerst in einen eigenen
Puffer (z.B. Ringpuffer mit eigenem Lese- und Schreibindex) und
betrachte die Daten über mehrere Events im Zusammenhang.
- die Sache mit dem Telegrammrahmen so noch nicht eindeutig ist, wenn
die eigentlichen Nutzdaten des Telegrammes eben die Rahmenbytes auch
enthalten dürfen. Abhilfe wäre hier z.B. beim Senden zu prüfen, ob in
den Nutzdaten just das "+"-Byte auftaucht um es dann eindeutig doppelt
zu senden. Der Empfänger weiß dann: Ein einzelnes + ist der Start, ein
doppeltes + liegt irgendwo in den Daten.
Vielleicht noch ein kleiner Tipp zum Schluss:
So einen Protokollparser muss man nicht unbedingt sofort an der
seriellen Schnittstelle testen. Es geht einfacher durch eine
Sendersimulation (den PC also erst mal selber in einen imaginären Puffer
Bruchstücke von Telegrammen schreiben lassen), bei der man auch mal
gezielt einen Übertragungsfehler einstreuen kann.
georg schrieb:> Das kann auch nicht funktionieren. Damit Startbytes zuverlässig erkannt> werden, dürfen sie im den restlichen Daten nicht vorkommen. Ein gültiges> Protokoll besteht z.B. aus einem Startbyte STX und Daten nur aus> lesbaren ASCII-Zeichen wie Ziffern und Buchstaben - dann kann das> Startzeichen nicht in den Daten vorkommen. Es gibt auch viele andere> Möglichkeiten eindeutig zu kodieren. Für das Ende, z.B. EOT, ETX, CR,> gilt das Gleiche.
Excel macht es mit csv völlig sonderzeichenfrei vor. Die zeichnen die
Trennzeichen eben nur so aus, dass eine Verwechslung mit Nutzdaten
ausgeschlossen ist (',' im Text wird zu '","' und '"' im Text wird zu
'""').
Jan H. schrieb:> georg schrieb:>> Jan H. schrieb:>> /* Protokoll>> *>> * Startbyte[0] : '-'>> * Startbyte[1] : '+'>> * Nachrichtenlänge[2] : 0..255>>>> Das kann auch nicht funktionieren. Damit Startbytes zuverlässig erkannt>> werden, dürfen sie im den restlichen Daten nicht vorkommen>> Georg> Dafür habe ich ja auch einen Header dort stehen die Infos drin.
Ja schon. Aber du wertest diese Tatsache nicht aus und falls Du es
tätest, gäbe es kein zuverlässiges positives Kriterium; nur ein
negatives.
Anders ausgedrückt: Du kannst zwar für gewisse Fälle feststellen, dass
eine Nachricht nicht korrekt formatiert ist, aber nicht, dass sie
korrekt formatiert ist.
Das ist gleichwertig zu der Frage ob ein gültiger Rahmenkopf gefunden
wurde.
D.h. Du kannst zwar für gewisse Fälle feststellen, dass ein Rahmenkopf
fehlerhaft ist (hauptsächlich wegen des Feldes "Daten Type") aber nicht,
dass er nicht fehlerhaft ist, das also kein Rahmenkopf vorliegt.
Ah. Ganz übersehen. Da ist ja noch eine Prüfsumme! Beinhaltet die auch
die nachfolgenden Nutzdaten?
Jedenfalls wäre das auch noch ein Kriterium um einen korrekten
vollständigen Rahmen festzustellen und dessen Position im Buffer.
Aber das wird dann schon sehr aufwendig. Viel einfacher ist, den Rahmen
ohne jeden Zweifel mit unverwechselbaren Zeichen oder Zeichenfolgen zu
kennzeichnen.
du musst zuerst auf das Startbyte warten, also wenn ein Zeichen an der
Seriellen anliegt nur 1 Byte lesen also auf '-' warten dann das gleiche
für '+', also BytesToRead=1, wenn die 2 Zeichen kommen dann ist das 3.
Byte was kommt die Länge und das liest du auch einzeln, danach machst du
einen BytesToRead=Längenbyte, so mit Timeout dass der Frame empfangen
wird
Theor schrieb:> Ah. Ganz übersehen. Da ist ja noch eine Prüfsumme! Beinhaltet die auch> die nachfolgenden Nutzdaten?>> Jedenfalls wäre das auch noch ein Kriterium um einen korrekten> vollständigen Rahmen festzustellen und dessen Position im Buffer.>>> Aber das wird dann schon sehr aufwendig. Viel einfacher ist, den Rahmen> ohne jeden Zweifel mit unverwechselbaren Zeichen oder Zeichenfolgen zu> kennzeichnen.
Ja. Header + Nutzdaten.
Jan H. schrieb:> for ( x = 0; x < buffer.Length ; x++ )> {> if ( buffer[x] == (char)'-')> {> if ( buffer[x+1] == (char)'+')> {> return x;> }> }> }
Hier aufgepasst: wenn x das letzte Zeichen ist, dann ist x+1 außerhalb
Deines Buffers! Ich weiß, hilft nicht bei Deinem Kernproblem... ;-)
Jan H. schrieb:> Dafür habe ich ja auch einen Header dort stehen die Infos drin. Dieser> hat eine bestimmte Länge
Du verstehst das Problem überhaupt nicht: das System rennt irgendwann
los und es kommt als nächstes Byte keineswegs unbedingt ein Startbyte.
Es muss also gewartet werden, bis ein gültiges Startbyte eintrifft, aber
das ist eben nur eindeutig zu erkennen, wenn es in den restlichen Daten
NICHT vorkommen kann.
Ebenso muss nach einem Übertragungsfehler neu synchronisiert werden,
d.h. der nächste Start einer Nachricht erkannt - gleiches Problem.
Dazu wurden, nicht nur von mir, schon einige Möglichkeiten erwähnt, aber
eines ist sicher: Bytes von 0..255 sind NICHT zulässig, weil sie eben
JEDEN Wert annehmen können, auch den eines Start- oder Endbytes. Eine
einfache Möglichkeit ist, nicht ein Byte 00..FF zu senden, sondern einen
Text "000" bis "255". Dass das länger ist, spielt nur sehr selten
überhaupt eine Rolle, ausserdem nützt eine schnelle Übertragung wenig
wenn sie fehlerhaft ist.
Georg
Man kann als Trennzeichen auch die Zeit benutzen, wie es gewöhnliche
analoge und ISDN Modems tun.
Wenn man <Pause>+++<Pause> an das Modem sendet, wechselt es in den
Befehlsmodus.
Wenn aber die Pause davor oder dahinter fehlt, wird die Zeichenfolge wie
Nutzdaten behandelt.
Joachim B. schrieb:> ascii Tabelle> https://de.wikipedia.org/wiki/Steuerzeichen>> STX abwarten (Beginn der Nachricht)> Zeichen sammeln bis> ETX (End of Text)
Das heißt damit kann ich aber auch nur ASCII übertragen? Wollte
eigentlich mit reinen Bytes arbeiten.
Jan H. schrieb:> Das heißt damit kann ich aber auch nur ASCII übertragen? Wollte> eigentlich mit reinen Bytes arbeiten.
Du kannst das ganze auch z.B. von 8bit auf 7bit verschlüsseln. Das erste
bit ist dann für Steuerzeichen reserviert. Für die Datenübertragung
werden nur 7bits benutzt. Das erste Byte wird dann 7bit+ 1bit zerlegt,
das nächste in 6bit+2bit usw.
Jan H. schrieb:> Wollte> eigentlich mit reinen Bytes arbeiten.
Du hast es immer noch nicht verstanden: Binärbytes können JEDEN Wert
annehmen, also gibt es keine Möglichkeit ein Startzeichen eindeutig zu
erkennen, ausser wie Stefanus vorschlägt, du fügst Pausen ein. Dann
musst du aber eine zuverlässige Pausenerkennung programmieren!!
Georg
georg schrieb:> Jan H. schrieb:>> Wollte>> eigentlich mit reinen Bytes arbeiten.>> Du hast es immer noch nicht verstanden: Binärbytes können JEDEN Wert> annehmen, also gibt es keine Möglichkeit ein Startzeichen eindeutig zu> erkennen, ausser wie Stefanus vorschlägt, du fügst Pausen ein. Dann> musst du aber eine zuverlässige Pausenerkennung programmieren!!>> Georg
… Ich meinte damit eher das ich nicht mit Strings arbeiten will. Z.B
wenn ich jetzt den Wert "100" über die Serielle jage, brauche ich ja in
ASCII 3 Bytes.
Das in den Nutzdaten die gleiche Zeichenfolge auftauchen kann wie in
meinem Header ( sprich Startbyte(s) ) habe ich jetzt erstmal
unterbunden.
Wie gehe ich denn jetzt am besten an die Auswertung?
Jan H. schrieb:> … Ich meinte damit eher das ich nicht mit Strings arbeiten will. Z.B> wenn ich jetzt den Wert "100" über die Serielle jage, brauche ich ja in> ASCII 3 Bytes.
ASCII ist zwar schick, weil man es leicht mitlesen kann und genug
Redundanz für die Kontrolle läßt, aber sicher nicht für jedes Problem
die optimale Lösung. Wie du ja bereits erkannt hast, ergibt sich da ein
kleines Effizienzproblem.
> Das in den Nutzdaten die gleiche Zeichenfolge auftauchen kann wie in> meinem Header ( sprich Startbyte(s) ) habe ich jetzt erstmal> unterbunden.>> Wie gehe ich denn jetzt am besten an die Auswertung?
OK, dein Problem ist offensichtlich einfach nur: du müsstest zunächst
erst einmal programmieren lernen. Wer es nicht alleine hinbekommt,
ASCII-Übertragungen vernünftig zu parsen und auszuwerten, dem fehlen
einfach ganz grundlegende Programmier-Fähigkeiten...
Nein, die kannst du nicht durch den Konsum von YT-Videos erwerben und
auch nicht durch dümmliche Anfragen in irgendwelchen Webforen. Da hilft
nur: 1) selber machen, 2) Fehler machen, 3) überlegen, was schief
gelaufen sein könnte, 4) goto 1).
Jan H. schrieb:> Ich empfange von einem µC Kommandos. Diese sind verschieden lang.> So sieht mein Protokoll aus.
Ja. Gruselig. Zuerst hast du zwei Kennbytes und dann hast du Zahlen von
0..255. Was meinst du, was dabei an Verwirrung herauskommt?
Ich würde dir anraten, die Übertragung ganz anders zu organisieren. Aber
dazu müßtst du dir Gedanken machen, was und wie du zu senden
beabsichtigst.
Hier nur ein paar einfache Vorschläge:
1. sende die Nutzdaten nur als ASCII-Ziffernfolgen, ggf. einschließlich
Minuszeichen, Punkt und dem kleinen 'e'.
2. sende formatfrei, aber atomar und mit abschließendem
Kenn-Großbuchstaben. Damit hast du ein Protokoll, was sich von selbst
synchronisiert.
3. lasse auf dem PC in deinem Programm den Empfang in einem separaten
Thread laufen, der im Gegensatz zum Haupt-Thread rein prozedural abläuft
und deshalb schlichtweg fast immer in der Empfangsschleife auf ein neues
Zeichen von de Seriellen wartet.
So, wie sieht das Ganze dann aus:
zum Beispiel so:
1234X-1.7345e-3Q4711A
die Zahl 1234 wird im PC dekodiert und da sie mit 'X' abgeschlossen ist,
wird sie für das verwendet, was du mit 'X' gemeint hast, z.B. ne
Koodinate.
die Zahl -1.7345e-3 wird für das verwendet, was du unter 'Q' machen
willst und die Zahl 4711A wird z.B. für ne Adresse in Köln benutzt.
Egal, wo bei so einem Kommandostrom die Kommunikation zustande kommt,
hast du schon nach wenigen Zeichen die Synchronisation erreicht und ab
da kannst du ohne feste Datenlängen übetragen, weil du ja das Ende
zugleich mit der Marke für deren Verwendung am jeweiligen Buchstaben
erkennst.
Ist nur ein Beispiel von vielen, wie man sowas anpacken könnte.
W.S.
In Anlehnung das genannte TLV Protokoll:
Stell die Länge ganz nach vorne. Gib der Länge ein Maximal zulässigen
Wert zur Plausiblitätsprüfung. Dann vergleichst du ob die Anzahl der
empfangenen Bytes die Anzahl der vorangestellten Länge übereinstimmt.
Zusätzlich hängst du als letztes Byte eine Prüfsumme über die gesamten
Daten an. Wenn die Validierung der Prüfsumme in Ordnug war hast du was
vollständiges intaktes bekommen.
derjaeger schrieb:> In Anlehnung das genannte TLV Protokoll:>>> Stell die Länge ganz nach vorne. Gib der Länge ein Maximal zulässigen> Wert zur Plausiblitätsprüfung. Dann vergleichst du ob die Anzahl der> empfangenen Bytes die Anzahl der vorangestellten Länge übereinstimmt.>>> Zusätzlich hängst du als letztes Byte eine Prüfsumme über die gesamten> Daten an. Wenn die Validierung der Prüfsumme in Ordnug war hast du was> vollständiges intaktes bekommen.
So ähnlich habe ich mir das auch gedacht..