Hallo, ich versuche folgendes zu erreichen und habe folgende Probleme
dabei:
Platine mit einem µC wird per USB-RS232 Umwandler an PC angeschlossen.
in VB.net wird ein Programm zur Kommunikation und Steuerung davon
geschrieben.
Problem liegt beim lesen der Daten die die Platine sendet.
Per Timer wird der Status alle 1000 Millisekunden abgefragt. Dabei kommt
einiges an Daten rein, und das eben immer wenn der Status abgefragt
wird, also jede Sekunde, das muss so sein, weil gewisse Werte halbwegs
in Echtzeit übermittelt werden müssen.
Gedacht (vom µC) ist die Darstellung in zwei Zeilen.
Das ganze kann dann so aussehen:
"-Netzbetrieb-
Aktive E/As: 02"
als Beispiel.
sooo, und genau hier liegt mein Problem. Das ganze soll in einer
einfachen Textbox dargestellt werden.
Leider gibt es kein klares Zeilenende, sondern jedes "Paket" endet immer
mit 03. Nun kommen aber pro Zeile gerne mal 2 Pakete, also 4 insgesamt.
Daher kann ich "ReadLine" schon mal nicht verwenden (oder?).
Allerdings beginnt jedes "Paket" mit eigener Start-Kennung.
In diesem Fall:
"05 09" = Beginn erstes "Paket" für die obere Zeile.
"05 01" = Beginn zweites "Paket" für die obere Zeile.
Dann ist alles für die obere Zeile vollständig.
BEIDE "Pakete" enden mit "03" -> ReadLine(03) ist daher nicht möglich
(vermute ich).
Bei der zweiten Zeile ganz ähnlich...
Hier beginnt das erste Paket mit "02 0a" (Zufall dass 0a Linefeed ist?
Wahrscheinlich nicht, aber "ReadLine" zusammen mit
1
SerialPort1.NewLine = Chr (10)
funktioniert nicht. Darstellung dennoch wie am Ende des Beitrags
beschrieben)
und das zweite Paket der unteren Zeile beginnt mit "02 0d". Auch diese
Pakete enden alle mit "03".
Folgendes habe ich bereits versucht:
1
Dim empfangsversuch As String
2
empfangsversuch = SerialPort1.ReadLine
3
SerialPort1.NewLine = Chr(10) 'diese Zeile vor "ReadLine" setzen hilft auch nichts. Chr(10) sollte für dieses Format korrekt für "0a" / Linefeed sein.
4
'TextBox1.Text = empfangsversuch
--> Form friert mit Beginn der Statusabfrage ein. Beim Debugging wird
die "ReadLine" Zeile markiert und angezeigt "Der E/A-Vorgang wurde wegen
eines Threadendes oder einer Anwendungsanforderung abgebrochen."
ebenfalls probiert: ReadExisting...
--> dann blitzen einzelne Teile des Empfangs kurz vorbei..man kann mit
Glück dann kurz noch "Netzb" von "Netzbetrieb" lesen ;) Dann wird das
schon vom nächsten Teil überschrieben.
Ich möchte noch anhängen dass ich maximal schlechte Grundkenntnisse im
programmieren habe, ich hoffe mein Problem ist wenigstens verständlich.
Falls das eventuell hilfreich für eine Lösung sein könnte: Alle Pakete
aller Zeilen haben immer die gleiche Länge...
Mein Ziel: Das Programm soll warten bis BEIDE Zeilen vollständig
empfangen wurden, und die Daten erst dann in der Textbox aktualisieren,
da die angezeigten Daten beider Zeilen je nach Menü-Option auch mal
zusammen hängen.
Der Nutzer soll bestenfalls überhaupt keine kurzzeitig leere oder
halbvolle Textbox sehen, sondern quasi ohne Übergang von
"-Netzbetrieb-
Aktive E/As: 02"
zu
"-Akkubetrieb-
Aktive E/As: 09"
Zudem frage ich mich, ob ich an der TextBox selbst etwas anpassen soll?
Sie ist natürlich so groß dass die Daten für eine Zeile auch in die
Zeile reinpassen...ReadOnly ist zu dem aktiv, da das nicht zum schreiben
genutzt werden soll. Ansonsten habe ich da nichts weiter verändert.
Okay, langer Text...vielleicht hat ja jemand eine anfängerfreundliche
Erklärung / Lösung für das Problem.
Vielen Dank bereits!
Anstatt strings zu senden könntest du dir ein besser zu parsendes
Protokoll ausdenken und die strings in deinem VB Programm selber
zusammen setzen.
Alternativ, wenn es wirklich strings seien müssen, alles in einen buffer
packen und einem RegEx drüber laufen lassen
1. Benutze keine normale Textbox sondern eine Richtextbox.
2. mach dich mit den mit den Befehl DOEVENT vertraut. Erscheint bei VB
erst nach Eingabe des 3 Buchstaben, ist sonst nicht in der Pull-Liste.
Ich würde EINE String deklarieren.
Dann die Daten immer weiter dran hängen.
Dann den String in die Richtextbox übergeben und dann ein Doevent
hinterher schicken.
Das zwingt das Prg. das Display neu aufzubauen.
Bei der Richtextbox gehen da in VB 2.147.483.647 Zeichen rein. Das ist
ne Menge. ;)
Eine normale Textbox ist bei 32.767 Zeichen Schluss. Dann gibts ein
Error.
Multiline muss bei dir auf TRUE stehen. !!!!
Danke erstmal für die Ideen.
Karl Klamson schrieb:> alles in einen buffer> packen und einem RegEx drüber laufen lassen
Wenn ich RegEx richtig verstehe müsste ich dann für jede mögliche
Anzeige irgendwie Filter schreiben, das wären im meinem Fall 15 Menüs
mit jeweils ca 10 Optionen...FALLS ich das mit RegEx richtig verstehe,
wäre das also eher ungut.
Schlaumaier schrieb:> mach dich mit den mit den Befehl DOEVENT vertraut.
Auch das klingt nach meinen ersten Google-Recherchen danach, dass ich
dann quasi alle vollständigen Meldungen, die angezeigt werden könnten,
in Klartext in den Code einbauen müsste, damit er beim empfangen quasi
darauf wartet?
Das wäre ähnlich komplex wie bei der "RegEx"-Variante.
Schlaumaier schrieb:> Ich würde EINE String deklarieren.>> Dann die Daten immer weiter dran hängen.
Damit ich richtig verstehe: Das wäre ja dann der erste Teil von der
oberen Zeile, den ich deklarieren und alles weitere dahinter hängen
würde, nehme ich an...
Schlaumaier schrieb:> Multiline muss bei dir auf TRUE stehen
das hatte ich im Beitrag vergessen, steht auf True.
Ich hatte gehofft es gibt die Möglichkeit immer ab einem bestimmten
Zeichen (Beginn Zeile A erster Teil, Beginn Zeile B erster Teil) ab DANN
auf eine bestimmte Anzahl Bytes zu warten, da die Nachricht immer gleich
lang ist, und das dann entsprechend darzustellen.
Oder...die zweite Zeile beginnt ja mit "0a" (Linefeed) - könnte man VB
da nicht irgendwie "sagen" dass alles VOR dem 0a in die erste Zeile, und
alles danach in die zweite soll? BIS zum erneuten Start-Zeichen von der
oberen Zeile dann wieder...
Oder irgendwie die Beginn-Zeichen der Zeilen rausfiltern und ihm sagen
"alles nach [Startzeichen erste Zeile Teil A] in die erste Zeile BIS
Linefeed 0a / Beginn zweite Zeile" und ab dann dann entsprechend wieder
bis zum Startzeichen erste Zeile.
So einfach ist das wohl nicht möglich?
Ja + NEIN.
Man kann Doevent auch auf ein Objekt beziehen.
Dann wird nur das Objekt neu gemacht. Den Rest juckt das dann gar nicht.
Doevent verhindert sehr wirkungsvoll das die Meldung "Diese Anwendung
hat keine Rückmeldung" von Windows selbst erscheint wenn man das Fenster
anklickt.
OHNE doevent wird nämlich i.d.R. nix angezeigt im Fenster (Textfeld)
solange das Programm arbeitet. Also auch keine Zähler.
Jedenfalls kenne ich keine andere Methode um ein Zähler in einen LABEL
z.b. den Stand der Dinge anzuzeigen.
sauerbrunnen schrieb:> Oder...die zweite Zeile beginnt ja mit "0a" (Linefeed) - könnte man VB> da nicht irgendwie "sagen" dass alles VOR dem 0a in die erste Zeile, und> alles danach in die zweite soll? BIS zum erneuten Start-Zeichen von der> oberen Zeile dann wieder...
Brauchst du nicht.
Das Rich-Text-Feld erledigt den Job.
Public TX as String.
Sub lese_Daten
lauf = true
do while lauf = true
dim empfangene_daten as String
' hier Daten empfangen via Schnittselle und an den empfangene_daten
übergeben
if len(empfangen_daten) = true then ' verhindert unnötige Leerzeilen
tx = Tx + empfangen_daten ' falls CHR(13) erforderlich ist, hier
einfach dazu machen)
Rich_text_box.text = tx ' übergibt den ganzen Text an die Box.
Rich_text_box.doevent ' * sieh unten
empfangene_daten = "" ' leert den Empfangspuffer
end if
loop
end sub
Das sollte grob reichen.
* Refresh't die Anzeige ansonsten erscheint das Ergebnis erst wenn die
Sub beendet ist, oder das PRG anhält. Alternativ wenn es nicht
funktioniert. my.application.doevent. Das klappt immer.
Kirlo schrieb im Beitrag #6981803:
> Mit einem Backgroundworker
+1
DoEvents() ist nahezu immer ein Zeichen für Pfusch.
ReadLine() blockiert den laufenden Thread standardmässig, bis entweder
das eingestellte NewLine-Zeichen rein gekommen ist oder bis der (evtl.)
konfigurierte Timeout fällig ist.
Wenn ReadLine() im Standard UI-Thread aufgerufen wird, blockiert man
dadurch u.U. lange die Message-Loop des UI-Threads und Windows kann an
das Fenster solange keine Messages (z.B. Maus-Klicks ins Fenster,
Mausbewegungen, 'zeichne dich mal neu', etc) mehr zustellen und die
Anwendung hängt dann praktisch durch das ReadLine() komplett und Windows
hält die Anwendung dann für quasi tot und zeigt dann gerne auch das
"reagiert nicht mehr" an, wenn man versucht mit der hängenden Anwendung
zu interagieren.
Den SerialPort würde ich einfach in eigenem Thread (per BackgroundWorker
z.B.) abfragen und da einfach in Schleife .Read() / .ReadByte() aufrufen
und alles in einem Puffer sammeln, bis dein "03" da ist und dann den
Puffer-Inhalt verarbeiten.
Vom Thread des BackgroundWorker aus kann man z.B. via dessen
.ReportProgress() Inhalte an den UI-Thread/Form weitergeben oder per
Invoke() der Form oder eines der Controls der Form auch Code im Context
des UI-Threads ausführen - worüber dann die TextBox im UI-Thread
aktualisiert werden kann.
(Was nötig ist, weil die Standard Controls unter Windows normalerweise
nur von dem Thread aus angesteuert werden können/dürfen, von dem aus sie
auch erstellt wurden)
sauerbrunnen schrieb:> FALLS ich das mit RegEx richtig verstehe
Nein tust du nicht. Ein guter Ausdruck parst dir im optimalen Fall
alles.
Alternativ statt die Informationen aus den Daten zu extrahieren,
extrahierst du einfach nur den String raus und filterst damit deine
Startkennung, etc. und alles was du nicht brauchst raus.
Danke erneut für eure Ideen.
Schlaumaier schrieb:> Das sollte grob reichen.
Da mir das mit meinem maximal schlechten Grundkenntnissen am einfachsten
erschien, habe ich das mal ausprobiert...
1
Private Sub SerialPort1_DataReceived(ByVal sender As System.Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
2
lauf = True
3
4
Do While lauf = True
5
Dim empfangene_daten As String
6
7
empfangene_daten = SerialPort1.ReadExisting
8
9
If Len(empfangene_daten) = True Then ' verhindert unnötige Leerzeilen
10
TX = TX + empfangene_daten ' falls CHR(13) erforderlich ist, hier einfach dazu machen)
11
RichTextBox1.Text = TX ' übergibt den ganzen Text an die Box.
12
Application.DoEvents() ' ########Kann das funktionieren ohne explizit irgendwie auf die RichTextBox zu verweisen? richtextbox1.doevent hat nicht funktioniert.
13
empfangene_daten = "" ' leert den Empfangspuffer
14
End If
15
16
Loop
17
18
End Sub
Damit friert die Form aber beim Beginn des ganzen ein.
bluppdidupp schrieb:> und alles in einem Puffer sammeln, bis dein "03" da ist
Das Problem ist ja, dass alle Datenpakete mit 03 enden. Die Zeile ist
also beim ersten "03" maximal halb-fertig, oder es kommt mal zuerst ein
Paket für die untere Zeile durch, das ebenfalls mit 03 endet...
bluppdidupp schrieb:> Den SerialPort würde ich einfach in eigenem Thread (per BackgroundWorker> z.B.) abfragen
Dämliche Frage - ist meine Abfrage (siehe Code oben) in einem eigene
Thread?
1
Private Sub SerialPort1_DataReceived(ByVal sender As System.Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
Sollte oben genanntes nicht funktionieren / umsetzbar sein, versuche ich
mich nochmal mit RegEx zu befassen, wobei ich nicht weiß, wie ich das
dann genau parse (alles nach den Start-Zeichen von allen Paketen für
beide Zeilen jeweils bis zum "03" (damit endet ja "alles"), und dann
noch entsprechend ausdrücken was mit welchem Startzeichen in welche
Zeile soll). So ungefähr wahrscheinlich?
MFG
Du musst bei deiner RS232 Schnittstelle den Event suchen der ausgelöst
wird wenn ein neuer Byte in das RS232 Register geschrieben wird.
In diesem Event dann den jetz-Byte und vorheriges-Byte prüfen ob das der
Anfang einer Nachricht ist, und dann irgendwie so wie unten.
Das ganze soll Ereignisgesteuert bleiben damit das Programm alles nicht
blockiert.
1
dim Buffer as string ' als globale variable
2
dim Nachricht as string ' als globale variable
3
dim Paket_1_1
4
dim Paket_1_2
5
dim Paket_2_1
6
dim Paket_2_2
1
sub event (jetz_Byte as string)
2
3
'Buffer sammeln
4
Buffer = Buffer & jetz_Byte
5
'Wenn Buffer zwei Zeichen hat, Nachricht-Anfang suchen sonst Buffer löschen
6
if Len(Buffer) = 2 then
7
if Buffer="05 09" then
8
Paket_1_1 = Buffer
9
else if Buffer = "05 01" then
10
Paket_1_2 = Buffer
11
else if Buffer = "02 0a" then
12
Paket_2_1 = Buffer
13
else if Buffer = "02 0d" then
14
Paket_2_2 = Buffer
15
else
16
Buffer=""
17
end if
18
end if
19
20
if Paket_1_1 <> "" then
21
'Das Ende der Nachricht suchen, der Buffer wird immer weiter gespeichert. Evtl die Prüfung für max. Länge einbauen dami es nicht ganze ewigkeit auf das Ende wartet.
sauerbrunnen schrieb:> Damit friert die Form aber beim Beginn des ganzen ein.
JA. Ist klar. Aber das my.Applikation.doevent baut sie mit den Status
des Aufrufes wieder auf.
For i = 1 to 100000
Label.text = str(i)
next i
Das einzige was du siehst ist die 100000. wenn er fertig ist mit zählen.
For i = 1 to 100000
Label.text = str(i)
my.Applikation.doevent
next i
Und du siehst ihn zählen. Zwar nicht jede Zahl dazu ist er zu schnell,
aber die größeren Stellen schon.
DAS ist der Unterschied.
Du musst also jedesmal wenn das Rich_text_feld gefüllt wird, Refresh mit
my.Applikation.doevent durchführen.
Der Code von ge-nka ist fein.
Die App friert aber "gefühlsmäßig" !!!! ein.
Der Grund ist, das VB einfach keine Zeit für die Bildschirmausgabe
zulässt.
Entweder du macht nach 'jetzt Nachricht in die Textbox schreiben '
ein, my.Applikation.doevent oder du löst das durch einen Timer aus. Was
dir lieber ist.
Kleiner Hinweis am Rande. my.Applikation.doevent löst nicht nur einen
Neuzeichnen der Bildschirmausgabe aus, sondern auch die Ausführung eines
STOP-Button.
z.b.
Während das Prg. läuft klickst du auf ein Button "Abbrechen". Da kannst
du klicken bis die Maus put geht. Sobald du my.Applikation.doevent
auslöst, wird auch die Sub des Button abgearbeitet.
Vielleicht hast du mal bei anderen Programmen auf "Abbrechen" geklickt
und dich gewundert das keine Reaktion kam. Ist das selbe Problem. Liegt
in der Tiefe der Struktur von Windows begraben, und daran das das OS
kein echtes Multi-Tasking kann bzw. zulässt. Ist halt viel aus der Win
3.11 Zeit übernommen. Sonst wäre es möglich so ein Button auf einen
eigenen Task zu legen und beide miteinander reden zu lassen.
Bei mein Amiga ging das damals sogar ;)
Du benutzt die
Private Sub SerialPort1_DataReceived(ByVal sender As System.Object,
ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles
SerialPort1.DataReceived
Diese läuft in einem eigenen Thread. Bei jedem Event kannst du
auswerten,
wie viele Bytes angekommen sind und diese lesen und dann auswerten. Oder
auch eine Zeile lesen, wie du's halt brauchst.
An z.B. eine Textbox wirst du deine Daten dann über Invoke los:
TextBox1.Invoke(Sub()
TextBox1.Text = "hallo" 'da natürlich die
Daten
End Sub)
Dann blockiert auch nix.
Sooo,
ge-nka schrieb:> Du musst bei deiner RS232 Schnittstelle den Event suchen der ausgelöst> wird wenn ein neuer Byte in das RS232 Register geschrieben wird.> In diesem Event dann den jetz-Byte und vorheriges-Byte prüfen ob das der> Anfang einer Nachricht ist, und dann irgendwie so wie unten.> Das ganze soll Ereignisgesteuert bleiben damit das Programm alles nicht> blockiert.
Erstmal vielen Dank für den ausführlichen Code, das ist ungefähr was ich
mit im Kopf vorgestellt hatte, es aber nicht als Code ausdrücken konnte.
Nun habe ich folgenden Code:
1
Private Sub SerialPort1_DataReceived(ByVal sender As System.Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
2
3
jetz_byte = SerialPort1.ReadExisting()
4
5
End Sub
und dann:
1
Private Sub Timer3_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer3.Tick
2
'Buffer sammeln
3
Buffer = Buffer & jetz_byte
4
'Wenn Buffer zwei Zeichen hat, Nachricht-Anfang suchen sonst Buffer löschen
5
If Len(Buffer) = 2 Then
6
If Buffer = "05 09" Then
7
Paket_1_1 = Buffer
8
ElseIf Buffer = "05 01" Then
9
Paket_1_2 = Buffer
10
ElseIf Buffer = "02 0a" Then
11
Paket_2_1 = Buffer
12
ElseIf Buffer = "02 0d" Then
13
Paket_2_2 = Buffer
14
Else
15
Buffer = ""
16
End If
17
End If
18
If Paket_1_1 <> "" Then
19
'Das Ende der Nachricht suchen, der Buffer wird immer weiter gespeichert. Evtl die Prüfung für max. Länge einbauen dami es nicht ganze ewigkeit auf das Ende wartet.
20
If jetz_byte = "03" Then
21
Nachricht = Buffer
22
'jetzt Nachricht in die Textbox schreiben
23
Buffer = ""
24
Paket_1_1 = ""
25
End If
26
End If
27
If Paket_1_2 <> "" Then
28
If jetz_byte = "03" Then
29
Nachricht = Buffer
30
'jetzt Nachricht in die Textbox schreiben
31
Buffer = ""
32
Paket_1_2 = ""
33
End If
34
End If
35
If Paket_2_1 <> "" Then
36
If jetz_byte = "03" Then
37
Nachricht = Buffer
38
'jetzt Nachricht in die Textbox schreiben
39
Buffer = ""
40
Paket_2_1 = ""
41
End If
42
End If
43
If Paket_2_2 <> "" Then
44
If jetz_byte = "03" Then
45
Nachricht = Buffer
46
'jetzt Nachricht in die Textbox schreiben
47
Buffer = ""
48
Paket_2_2 = ""
49
End If
50
End If
51
52
RichTextBox1.Text = Nachricht
53
'Muss hier ein Timer.stop rein?
54
End Sub
55
End Class
Im SerialPort_DataReceived habe ich
1
jetz_byte = SerialPort1.ReadExisting
Erste Frag: Ist das korrekt? Der Code von ge-nka sammelt ja alles in
einem Puffer, daher vermute ich dass hier erst mal alles eingelesen
werden soll, also ReadExisting statt ReadLine o.ä.
Den Code mit dem Puffer (von ge-nka) habe ich in einen Timer gesetzt,
welcher beim Verbinden gestartet wird, aus folgendem Grund:
1
sub event (jetz_Byte as string)
Löst einen Fehler aus: "Keyword is not valid as an identifiert", bezogen
auf "event", ich habe es mit "[event]" versucht, dann kommen keine
Fehler, aber es passiert auch nichts.
Wenn der Code im Timer steht, wird beim starten die Textbox gelceared,
also der voreingestellte Text "Warten auf Verbindung..." verschwindet,
mehr passiert aber noch nicht.
Daher folgende Fragen
ge-nka schrieb:> if Paket_1_1 <> "" then> 'Das Ende der Nachricht suchen, der Buffer wird immer weiter> gespeichert. Evtl die Prüfung für max. Länge einbauen dami es nicht> ganze ewigkeit auf das Ende wartet.
wie "suche" ich das Ende der Nachricht? Bis auf Ende-Kennung "03" ist
alles andere ja je nach Daten immer unterschiedlich. Das ist die
wichtigste Frage, weil es ohne wohl nicht geht.
ge-nka schrieb:> Nachricht = Buffer> 'jetzt Nachricht in die Textbox schreiben
Das habe ich so erledigt: Im Code von ge-nka steht ja bereits "Nachricht
= Buffer", ich habe dann ganz am Ende in diesem Sub angehängt
1
RichTextBox1.text = Nachricht
Geht das so?
Danke für eure Geduld - ich komme dem Ziel näher...
Das wird wohl so nicht funktionieren bzw. ist schlecht programmiert: Du
hast dabei 2 Events, die ja fast gegeneinander arbeiten: Den Timer-Event
und den SerialPort1_DataReceived-Event. Der 2. ist eigentlich das Mittel
der Wahl, dann brauchst du keinen Timer. Oder doch über den Timer (das
nennt man Polling), ist aber zumindest nicht elegant. Der
SerialPort1_DataReceived kommt immer, wenn der µC was sendet (also eher
in "Echtzeit"). Wenn der µC einen
String raushaut, dann steht der auch komplett im
SerialPort1.ReadExisting() und du bist fertig. Wenn der einzelne Bytes
schickt solltest du doch die Anzahl kennen. Wenn nicht brauchst du
tatsächlich eine Logik zum zusammensammeln. Da ist Ende '03' aber
gefährlich, eine 03 könnte ja auch mitten auf deinem String stehen.
sauerbrunnen schrieb:> Falls das eventuell hilfreich für eine Lösung sein könnte: Alle Pakete> aller Zeilen haben immer die gleiche Länge...
Das hatte ich überlesen. Dann ist es doch einfach: überlasse dem Buffer
des Ports das zusammensammeln: Wenn die Länge z.B. 10 ist
Private Sub SerialPort1_DataReceived(ByVal sender As System.Object,
ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles
SerialPort1.DataReceived
If SerialPort1.BytesToRead < 10 Then Return
Dim s as String = SerialPort1.ReadExisting()
' s sollte jetzt 10 Bytes haben
' Diese nach deiner Logik in die 4 Fälle sortieren und mit Invoke
' auf den Text
end if
Ein Problem könnte eventuell noch darin bestehen, dass der µC schneller
sendet, als du liest - dann läuft der Eingabepuffer natürlich voll. Man
sollte
also noch checken, ob SerialPort1.BytesToRead eventuell immer größer
wird.
Schau an, in der Eigenschaft 'ReceivedBytesThreshold' des SerialPort
kannst du sogar einstellen, bei wieviel Bytes das
SerialPort1_DataReceived ausgelöst wird. Da würde ich ja die Länge
deines Strings nehmen. Man lernt nie aus....
Man soll ja auch nicht alles eins zu eins abkupfern was ich schreibe,
das war ja ein Beispiel wie der Algorithmus sein soll.
Zu erst muss man eigentlich Byte für Byte abgreifen und einzeln prüfen.
Und das kannst du nur in:
1
Private Sub SerialPort1_DataReceived(ByVal sender As System.Object, ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
2
jetz_byte = SerialPort1.ReadExisting()
3
End Sub
machen.
Apropos "Event" ist ein reserviertes Wort für Compiler.
Klar, muss man diesen "Event" so kurz wie möglich halten, aber zum
Anfang um Ergebnisse und Gefühl zu kriegen soll es reichen sind ja nur
7-8 boolesche Prüfungen, und String zusammenbauen.
(richtig wäre da nur den Buffer füllen und in anderen Funktion, Buffer
Byte nach Byte auswerten und Byte nach Byte leeren)
Zweitens du muss in klaren sein dass das was du in SerialPort kriegst
ein Byte ist, also kein String, sondern ein ASII Zeichen mit Wert von
0-255.
Ich kenne mich nicht besonders mit VB.net nicht aus, aber Grundlagen
sind überall gleich.
Also muss du eigentlich mit einem Array arbeiten Buffer[] oder mit einem
chr(xxx) das in ein String speichern, was natürlich schlecht zu debugen
ist, da die Steuerzeichen nicht lesbar in einer Textbox sind.
(Die Zeichen mit den Codes 0-31 sind nicht darstellbare Zeichen. Sie
werden auch als Steuercodes bezeichnet.)
Veto!
SerialPort1.ReadExisting() liest den kompletten Puffer und liefert einen
String.
Wenn du ein einzelnes Byte lesen willst dann mit SerialPort1.ReadByte()
ge-nka schrieb:> If Buffer = "05 09" Then
Wenn du das machst läuft du gegen die wand. Grund :
1. Du analysierst einen 5 Bytes-String. Als einfaches Bla-bla.
Wenn du Zeichen analysieren willst musst du das SO machen. Dabei ist es
völlig egal, welches Zeichen es ist. Ich habe de Routine unten, sogar
schon für Hex-Files in angewandeter Form benutzt.
das musst du SO machen.
buffer$ = ganzer_buffer
gefunden_05 = false
for i = 1 to len(ganzer_buffer)
tx$ = mid(buffer$,i,1)
if tx$ = chr(05) and gefunden_05 = false then
gefunden_05 = true
else
gefunden_05 = false
end if
if tx$ = chr(09) and gefunden_05 = true then
' mach hier was du sollst
gefunden_05 = false
end if
next i
So kann man ALLES Pharsen.
VB.net erlaubt keine Deklaration mit $
Wenn man einen String (kein Byte-Feld) der Länge 5 hat kann man den
natürlich vergleichen. Wenn man sich wie oben beschrieben den kompletten
String holt kann man sich die Kennung mit
Dim kennung as String = Puffer.Substring(0, 5)
holen
Dann kann man sich die Parserei sparen.
tom schrieb:> VB.net erlaubt keine Deklaration mit $
Sorry mein Fehler. VB-2010 ist das egal man darf es nur nicht mischen.
Dann mach halt
dim tx, buffer as string.
liegt an meiner Faulheit. ;) Ich habe mir angewöhnt vor vielen Jahren
das $ bedeutet String. Und da VB-2010 das noch erlaubt .... ;)
Ich bekomme auch deshalb in B4X ärger, ist da mein Lieblingstippfehler
;)
tom schrieb:> Dim kennung as String = Puffer.Substring(0, 5)> holen> Dann kann man sich die Parserei sparen.
Kann man nicht.
1.) Weil wenn sich die Position des Suchstring verschiebt, wenn die
Buffer_zeichenkette nicht immer genau gleich ist. ;)
2. Substring hat aber klein Parameter für binäre Vergleiche des
Suchstring.
Substring ist hervorragend geeignet um Filenamen aus Pfadnamen zu holen,
aber nicht um serielle Daten zu lesen.
https://docs.microsoft.com/de-de/dotnet/api/system.string.substring?view=net-6.0
Im Gegensatz zu INSTR Funktion.
https://docs.microsoft.com/de-de/office/vba/language/reference/user-interface-help/instr-function
Diese könnte man benutzen, müsste aber dann mit der Mid-Funktion, den
String auseinander schneiden.
Geht mit den compare parameter, den man mit vbBinaryCompare angeben
MUSS.
Ansonsten sind ALLE Vergleiche unter = Chr(31) falsch.
Weshalb ich meine Methode bevorzuge. Da ich damit weniger Aufwand habe.
Mid - Funktion liefert bei ungültigen Parametern ein Fehler. Ergo muss
ich auch noch auf den Rest prüfen, was aufwendiger ist.
Schlaumaier schrieb:> Kann man nicht.> 1.) Weil wenn sich die Position des Suchstring verschiebt, wenn die> Buffer_zeichenkette nicht immer genau gleich ist. ;)>> 2. Substring hat aber klein Parameter für binäre Vergleiche des> Suchstring.
Ich glaube, da hast du einen Denkfehler. Die Länge des Strings ist immer
gleich - das war die Anforderung. Ich lese also immer einen STRING
gleicher
Länge. In diesem Fall ist es also puppenleicht, parsen und binäre Suche
kann man sich hierbei sparen.
OK, man muss natürlich sicher stellen, dass der String komplett von
Anfang an ankommt. Wenn der IC schon feuert und man danach das
VB-Programm startet könnte man Pech haben. Dann also entweder den Puffer
leeren - oder doch parsen....
tom schrieb:> Wenn der IC schon feuert und man danach das> VB-Programm startet könnte man Pech haben. Dann also entweder den Puffer> leeren - oder doch parsen....
OK. bei Perfekt gleicher Länge ist ein Pharser wie meiner Perlen vor die
Säue das gebe ich zu.
Dann reicht auch
dim daten_im_buffer, teil_1 as string
dim lange_von_zeile as Integer = 10 ' 10 Zeichen lang, k.a.)
dim start as integer = 4 ' wo er anfangen soll zu trennen
dim lang as interger = 3 ' Anzahl der Zeichen die er ab Start lesen soll
if len(daten_im_buffer) = lange_von_zeile then
Teil_1 = Mid(daten_im_buffer,start,lang)
else
if len(Daten_im_Buffer > 0 then
textbox_1.text ="Fehlerhafte Datenlänge empfangen, MACH WAS ;)"
end if
end if
erinnert mich an Datenbankdateien im Textformat mit fester Feldgröße ;)
So was macht das Leben viel einfacher. Du MUSST aber die LEN-Abfrage
machen, ansonsten ist beim 1. fehlenden Bytes ein Absturz zu erwarten.
MID-Funktion verzeiht keine Fehler.
Schlaumaier schrieb:> Du MUSST aber die LEN-Abfrage> machen, ansonsten ist beim 1. fehlenden Bytes ein Absturz zu erwarten.> MID-Funktion verzeiht keine Fehler.
Ich will ja nicht kleinlich sein - aber MID verzeiht gar nix mehr,
gibt's im VB.NET auch nicht mehr (nur damit sauerbrunnen nicht
verzweifelt). Statt dessen substring ;)
Die Überprüfung ist im Prinzip richtig - aber hier auch nicht nötig.
Mein SerialPort1_DataReceived feuert ja genau nur dann, wenn die
erwartete Anzahl Bytes da ist.
tom schrieb:> aber MID verzeiht gar nix mehr,> gibt's im VB.NET auch nicht mehr (nur damit sauerbrunnen nicht> verzweifelt). Statt dessen substring ;)
Ich sollte mir diese nervigen Änderungen in VB.NET wirklich mal antun.
;)
Aber wie ich mich kenne, würde ich unter VB.net nur die Mid-Funktion mir
mal eben selbst schreiben. ;)
Wäre nicht die erste Funktion die ich mir nachbilde, weil die neuere
Syntax mir auf den Sack geht.
tom schrieb:> Die Überprüfung ist im Prinzip richtig - aber hier auch nicht nötig.> Mein SerialPort1_DataReceived feuert ja genau nur dann, wenn die> erwartete Anzahl Bytes da ist.
OK. Aber ich bin ein Feigling.
Und noch NIE wurde durch eins meiner Programme ein Explosit ausgelöst.
Selber wenn der Compiler ein Fehler hatte. z.b. Das man ein Eingabefeld
mit komischen Zeichen überlasten konnte.
Grund : Ich bin ein Feigling und fange auch mögliche Fehler ab, die es
nicht geben darf.
Schlaumaier schrieb:> Grund : Ich bin ein Feigling und fange auch mögliche Fehler ab, die es> nicht geben darf.
Super, dann bekommst du meine Programme jetzt immer zur Korrektur.
Ich bin Optimist und warte immer, bis der Anwender meckert ;)
Angenehme Nachtruhe.
@sauerbrunnen: Jetzt haben wir hier viel geschwafelt, Ideen entwickelt
und verworfen. Siehst du noch durch oder läuft dein Programm etwa
inzwischen schon?
Erneut ein "soooo" !
Ich habe hier alles verfolgt und hatte heute Zeit, um zu testen...
Nachdem hier einiges vorgeschlagen, widerlegt, verworfen wurde, sieht
mein Code aktuell so aus:
1
Private Sub Timer3_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer3.Tick
2
jetz_byte = SerialPort1.ReadExisting()
3
'Buffer sammeln
4
Buffer = Buffer & jetz_byte
5
6
If Len(Buffer) = 2 Then
7
If Buffer = Chr(&H5) & Chr(&H9) Then
8
Paket_1_1 = Buffer
9
ElseIf Buffer = Chr(&H5) & Chr(&H1) Then
10
Paket_1_2 = Buffer
11
ElseIf Buffer = Chr(&H2) & Chr(&HA) Then
12
Paket_2_1 = Buffer
13
ElseIf Buffer = Chr(&H2) & Chr(&HD) Then
14
Paket_2_2 = Buffer
15
Buffer = ""
16
End If
17
End If
18
If Paket_1_1 <> "" Then
19
'Das Ende der Nachricht suchen, der Buffer wird immer weiter gespeichert. Evtl die Prüfung für max. Länge einbauen dami es nicht ganze ewigkeit auf das Ende wartet.
20
If jetz_byte = Chr(&H3) Then
21
Nachricht = Buffer
22
'jetzt Nachricht in die Textbox schreiben
23
Buffer = ""
24
Paket_1_1 = ""
25
End If
26
End If
27
If Paket_1_2 <> "" Then
28
If jetz_byte = Chr(&H3) Then
29
Nachricht = Buffer
30
'jetzt Nachricht in die Textbox schreiben
31
Buffer = ""
32
Paket_1_2 = ""
33
End If
34
End If
35
If Paket_2_1 <> "" Then
36
If jetz_byte = Chr(&H3) Then
37
Nachricht = Buffer
38
'jetzt Nachricht in die Textbox schreiben
39
Buffer = ""
40
Paket_2_1 = ""
41
End If
42
End If
43
If Paket_2_2 <> "" Then
44
If jetz_byte = Chr(&H3) Then
45
Nachricht = Buffer
46
'jetzt Nachricht in die Textbox schreiben
47
Buffer = ""
48
Paket_2_2 = ""
49
End If
50
End If
51
52
RichTextBox1.Text = Buffer
Geändert habe ich mein unsinniges "05 09" etc. zu
1
Chr(&Hx)
"Chr = xx" hat nicht funktioniert, da er das bei "02 0a", also dem Start
von Zeile B nicht akzeptiert hat, beim senden verwende ich ebenfalls
"&Hxx".
Außerdem habe ich
1
RichTextBox1.Text = Nachricht
zu
1
RichTextBox1.Text = Buffer
geändert, weil die Textbox leer blieb... bei " = Buffer" erscheinen nun
Daten...nur nicht ganz so, wie erwünscht. Ich habe in den Properties vom
SerialPort das "ReceivedBytesTreshold" auf 11 gestellt (so lange sind
die Datenpakete).
--Nun bestehen noch folgende Probleme:--
- Manchmal kommt zuerst ein Paket für die zweite Zeile an... es
erscheint dann die LETZTE Hälfte vom Wort für die untere Zeile....ganz
am Anfang in der oberen Zeile, alles andere wird dann ebenso unpassend
angehängt.
- Es erfolgt eine neue Line obwohl die vorherige noch nicht "voll ist".
Also weder dass Längenmäßig der Platz in der Richtextbox fehlen würde,
noch dass das der programmierte Beginn der neue Zeile ist.
- Es erscheinen in der Textbox noch Daten die gefiltert werden müssen.
(alles was mit "02 85" beginnt, und ebenfalls mit "03" endet, dient
einer Art Handshake oder Alive-Prüfung dem Programm, dass ursprünglich
mit dem Gerät kommuniziert hat, das wird in VB als "?????" dargestellt,
das muss ich noch ignorieren lassen. -> Diese Pakete sind kürzer als
alle, die tatsächlich dargestellt werden sollen, vielleicht kann man das
so einbauen.
- Meine Startzeichen aka "05 09" etc. sowie mein Ende "03" werden in der
Richtextbox mit angezeigt... als eine Art "[]" nur dass es ein ganzes
Zeichen ist. Hier mal als Copy paste (wird hier nicht richtig angezeigt)
:
---> ich bin eigentlich davon ausgegangen dass das rausgefiltert wird,
wenn es im Code als Start/Stopzeichen eingestellt ist...entweder dem ist
nicht so, oder "mein" Code ist wirkungslos bisher, und das alles wird
rein durch "ReceivedBytesTreshold" ausgelöst? Das werde ich Morgen bei
Gelegenheit testen.
Dennoch....es geht voran...und falls nicht alle an meiner
Ahnungslosigkeit verzweifeln, bekomme ich (wir) das ganze sicher noch
fertig ;)
Danke!
Wie oben schon gesagt, würde ich das mit einem regulären Ausdruck
rausfiltern, zumal ja auch nach Hex-Zahlen (\x..) gesucht werden
kann.
\x05\x09.....\x03
wobei die Punkte dazwischen das wirklich Gesuchte definieren.
Das mußt du also noch ergänzen, z.B. [0-9]{1,5} = alle 1 - 5stelligen
Zahlen. Dann halt vorne 2 und hinten 1 Zeichen abschneiden bzw.
mit LF ergänzen.
Geht ja gut mit der Mid und Len - Funktion.
Mid(buffer,3, Len(buffer) -1)
Mit der Länge (Len() mußt du ausprobieren. In meiner Sprache
muß ich 3 von Len() abziehen.
Ich befürchte, mit dem Timer hast du dir unnötig viele Schwierigkeiten
eingehandelt. Es ist hier völlig unklar, wie viele Bytes jeweils in
einer
Sequenz ankommen. Die "ReceivedBytesTreshold" sind hier wirkungslos,
damit wird eingestellt, bei wieviel Bytes der SerialPort_DataReceived
ausgelöst wird (diesen Event würde ich wie gesagt deutlich bevorzugen).
Ich will das nochmal kurz verdeutlichen:
Wenn dein Timer sehr 'schnell' ist, dann wird in
jetz_byte = SerialPort1.ReadExisting()
genau ein Byte gelesen (mehr ist noch nicht angekommen) und der Puffer
gelöscht. Danach liest du wieder 1 Byte.
Wenn dein Timer 'langsam' ist, dann liest du XX Bytes - also irgendeine
Anzahl, die du dann parsen musst.
Ich würde also doch den SerialPort_DataReceived bevorzugen. Da holst du
immer 11 Bytes, fusselst deine Kennung auseinander, merkst dir deine 4
Teiltexte auf 4 public shared strings, und wenn alle 4 einen Wert haben
baust du deine beiden Texte zusammen und schreibst die mit Invoke auf
die beiden Textboxen.
versuch doch mal das:
Unter deiner Haupt-Klasse ('Form1')
Public Shared Text11 As String = ""
Public Shared Text12 As String = ""
Public Shared Text21 As String = ""
Public Shared Text22 As String = ""
Bei Initialisierung des Ports
SerialPort1.ReceivedBytesThreshold = 11
und dann
Private Sub SerialPort1_DataReceived(sender As Object, e As
SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
Dim Buffer = SerialPort1.ReadExisting()
'Hier gehe ich von deinen ursprünglichen Kennungen aus
Dim Kennung = Buffer.Substring(0, 5)
If Kennung = "05 09" Then
'5 Byte Kennung, hinten die "03" - d.h. von 11 bleiben 4
'Eventuell anpassen, da wir die Länge kennen kannst du
'die 03 am Ende auch weglassen
Text11 = Buffer.Substring(5, 4)
ElseIf Kennung = .....
'hier die anderen 3 auswerten
End If
If Text11.Length And Text12.Length Then
TextBox1.Invoke(Sub()
TextBox1.Text = Text11 & Text12
End Sub)
Text11 = ""
Text12 = ""
End If
'analog für Text21 + Text22
End Sub
Und wenn dir was komisch vorkommt hilft natürlich der Debugger extrem.
Am besten mal direkt nah dem ReadExisting mal nen Breakpoint setzen und
schauen, was so ankommt...
Hallo nochmal, heute habe ich endlich wieder Zeit gefunden...und hätte
nochmal ein paar Fragen dazu.
tom schrieb:> Dim Buffer = SerialPort1.ReadExisting()> 'Hier gehe ich von deinen ursprünglichen Kennungen aus
Muss da außer ReadExisting noch etwas hin? Da ja mit dem nachfolgenden
Code alles verarbeitet werden soll, müsste das ja so bleiben können.
tom schrieb:> Dim Kennung = Buffer.Substring(0, 5)> If Kennung = "05 09" Then
05 09 habe ich entsprechend mit &Hxx angepasst.
1
Buffer.Substring(0,5)
- wie komme ich auf (0,5) ?
tom schrieb:> '5 Byte Kennung, hinten die "03" - d.h. von 11 bleiben 4> 'Eventuell anpassen, da wir die Länge kennen kannst du> 'die 03 am Ende auch weglassen
und wie kommen wir bei 11 Bytes auf 5-Byte Kennung und wo sollte ich die
03 hier noch eintragen?
tom schrieb:> ElseIf Kennung = .....> 'hier die anderen 3 auswerten
Hauptproblem liegt nun da, dass ich nicht wirklich verstehe wie du beim
ersten Substring auf (0, 5) kommst und dementsprechend weiß ich nicht
wie ich hier für die nächsten Teile verfahren muss...
tom schrieb:> If Text11.Length And Text12.Length Then
Muss hier im fertigen Code .Length noch definiert werden, also wann die
Länge erreicht ist?
Danke erneut...
Bevor ich ausführlich antworte noch eine Anmerkung/Frage: Die Logik
funktioniert so nur, wenn tatsächlich IMMER jeweils ein 11 Zeichen
übergeben werden. Irgendwo hattest du doch geschrieben, dass dir
manchmal noch was anderes dazwischen haut, oder? Dann funktioniert das
so nicht.
sauerbrunnen schrieb:> Buffer.Substring(0,5)> - wie komme ich auf (0,5) ?
Das bezieht sichauf die alte Kennung mit den 5 Zeichen (z.B. "05 09")
So, mir fällt es gerade wie Schuppen aus den Haaren:
Wir reden über verschiedene Kennungen. Meine "05 09" ist ein String der
Länge 5.
Du verwendest aber in Wirklichkeit 2 Byte - also eine "59". Und am Ende
steht eine "3" keine "03"
Also:
Dim Kennung = Buffer.Substring(0, 2)
If Kennung = "59" Then
'2 Byte Kennung, hinten die "3" - d.h. von 11 bleiben 8
'Eventuell anpassen, da wir die Länge kennen kannst du
'die 3 am Ende auch weglassen
Text11 = Buffer.Substring(3, 8)
usw.
If Text11.Length.... wird intern in Boolean konvertiert,
d.h. Länge = 0 ->FALSE, Länge > 0 ->TRUE