Seite 1 von 2

Daten von einen Serial Port empfangen und verarbeiten

Verfasst: 26.02.2016 21:59
von HHYayo
Schönen guten Abend.

Derzeitig arbeite ich mal wieder ein wenig mit Purebasic, und hänge derzeitig an einem Vorhaben in Verbindung mit den Serial Port Funktionen.

Ich sende Daten an einem virtuellen Port (Läuft über com0com) auf den ein Simulator zugreift. Dieser antwortet mir entsprechend mit wichtigen Informationen, die ich später von Hex in Dezimal umrechnen muss, um mit diesen Werten arbeiten zu können.

Derzeitig ist mein Code ziemlich einfach:

Code: Alles auswählen

If OpenSerialPort(0, "COM5", 9600, #PB_SerialPort_EvenParity , 8, 1, #PB_SerialPort_NoHandshake, 1024, 1024)
   If WriteSerialPortString(0,"010D"+#CRLF$)
   Delay(100)
     While AvailableSerialPortInput(0)>0
       If ReadSerialPortData(0,@Byte,1)    
         text.s=text.s+Chr(Byte)
       EndIf
     Wend
   EndIf
   Debug text
Else
   MessageRequester("Error", "")
EndIf
und die Ausgabe vom Text sieht folgendermaßen aus:
010D

41 0D 00
>
Relevant dabei sind in der dritten Zeile die Stellen nach "41 OD". Je nach Daten die von den Simulator aus geschickt werden variiert die Ausgabe. Jedoch muss ich irgendwie alles bis auf die Zeichen nach den genannten Werten entfernt bekommen. Ich habe versucht direkt aus "text.s" mit FindString/RemoveString diese zu entfernen, scheinbar jedoch stetig ohne Erfolg. Ich könnte natürlich alles aus "text.s" in einen .txt Dokument speichern, die Strings suchen und bearbeiten, speichern, und wieder neu einlesen. Das Problem ist jedoch das ich später einen Timer und eine Schleife parallel laufen haben werde, die am besten alle 100-200ms den Wert überprüfen und den Timer stoppen sollen (Ob der virtuelle Port die Anzahl an Anfragen mitmacht in der Geschwindigkeit weiß ich derzeitig auch noch nicht, war der Meinung 200ms ist die absolute Grenze). Daher denke ich nicht das es sinnvoll wäre, in den kurzen Zeitspannen irgendwelche Dateien ständig zu öffnen, zu verändern, und wieder neu auszulesen.

Könnte man nicht irgendwie mit Arrays oder ähnlichen arbeiten ?

Mit freundlichen Grüßen
HHYayo

Re: Daten von einen Serial Port empfangen und verarbeiten

Verfasst: 27.02.2016 08:36
von Bisonte
Über die Ausleserate weiss ich zwar nichts, aber in Deinem Fall bietet sich eine LinkList geradezu an.

So frei nach dem Motto :

Code: Alles auswählen

NewList Text.s()

If OpenSerialPort(0, "COM5", 9600, #PB_SerialPort_EvenParity , 8, 1, #PB_SerialPort_NoHandshake, 1024, 1024)
  
  Repeat
    If WriteSerialPortString(0,"010D"+#CRLF$)
      Delay(100)
      
      AddElement(Text())    
      While AvailableSerialPortInput(0)>0
        If ReadSerialPortData(0,@Byte,1)             
          Text() + Chr(Byte)
        EndIf
      Wend
      Debug Text()
      
      StopZeit = ElapsedMilliseconds() + 100
      
      ;: Diese While Wend Schleife soll mind. 100ms dauern
      While ElapsedMilliseconds() <= StopZeit
        
        If ListSize(Text()) > 100
          ; Hier Speicher Routine
          ; Dann sind "100" Messwerte vorhanden
          ClearList(Text()) ; Und Liste wieder löschen
          
          ; Hier die Dauer in ms wielange der Speichervorgang dauert.
          Debug ElapsedMilliseconds() - (StopZeit - 100)
          ; Um zu variieren die Listengrösse anpassen oder die Stopzeit
        EndIf
        
        Delay(1)
        
      Wend
      
    EndIf
    
  ForEver ; Somit sollte sichergestellt sein, das min. 200ms zwischen den Datenabrufen liegen.
  
Else
  MessageRequester("Error", "")
EndIf 
Ungetestet, da nix serielles vorhanden.... ;)

Edit : Achja. Alles über LinkLists steht in der Hilfe (F1) im Kapitel LinkList.

Re: Daten von einen Serial Port empfangen und verarbeiten

Verfasst: 27.02.2016 16:04
von HHYayo
Schönen guten Tag.

Erstmal vielen Dank für dein Code! Ich habe jetzt definitiv die Umsetzung von dir übernommen, da ich zwar bereits selber die Schleife geschrieben habe kontinuierlich Daten dorthin zu schicken, jedoch absolut unschön und garantiert nicht Ressourcen schonend. Ich habe gestern bis in die Nacht eine Lösung für mein Problem zu finden, wie ich es hinbekomme das er die Ausgabe soweit löscht, dass lediglich von dem Inhalt was ich in meinem ersten Post als Zitat geschrieben habe nur alles nach dem "41 0D" übrig bleibt.

Ich hab es hinbekommen, in dem ich nach jedem Senden das Empfangene in eine Datei speichere, diese schließe, denn über eine Procedur die drei mal ausgeführt wird nach und nach die überflüssigen Zeilen löscht (Öffnen, löschen, schließen) und denn die Datei nochmal öffnet und mit dem Befehl Right nur die letzten Zahlen wiedergibt.

Derzeitig sieht der Code folgendermaßen aus:

Code: Alles auswählen

Procedure.s EraseTextLines(MyDat.s,line.l)
  Protected Result.s,Format
  NewList TLine.s()
  If FileSize(MyDat)=>0

    OpenFile(0,MyDat)
    Format=ReadStringFormat(0)
    While Eof(0)=0
      AddElement(TLine())
      TLine()=ReadString(0,Format)
    Wend
    CloseFile(0)
    
    CreateFile(0,MyDat)
    SelectElement(TLine(),line-1)
    Result=TLine()
    DeleteElement(TLine())
    ResetList(TLine())
    While NextElement(TLine())
      WriteStringN(0,TLine(),Format)
    Wend
    CloseFile(0)
   Else
     MessageRequester("Error","Datei nicht vorhanden oder leer")
   EndIf
   ProcedureReturn Result
 EndProcedure
 
 Procedure.q Hex2Dec(Hex.s)
   Protected result.q, n, temp, pow.q=1
   hex=UCase(hex)
   For n=MemoryStringLength(@Hex)-1 To 0 Step -1
      temp=PeekC(@Hex+n*SizeOf(Character))-48
      If temp >= 17 And temp <= 22
         temp-7
      ElseIf temp < 0 Or temp > 9
         Break
      EndIf
      result+temp*pow
      pow*16
   Next
   ProcedureReturn result
 EndProcedure
 
 Global Time.l
 Global zeit.s
 
Procedure timerCB(hwnd, uMsg, idEvent, dwEvent)
  Static NewTime.l
  Protected hours.l, minutes.l, seconds.l, centiseconds.l, zeit.s
 
  If NewTime <> Time
    NewTime = ElapsedMilliseconds() - Time
    centiseconds = (NewTime / 10) % 100
    seconds = (NewTime / 1000) % 60
    minutes = (NewTime / 60000) % 60
    hours = (NewTime / 3600000) % 60
   
    zeit = RSet(Str(hours), 2, "0") + ":" + RSet(Str(minutes), 2, "0") + ":" + RSet(Str(seconds), 2, "0") + "::" + RSet(Str(centiseconds), 3, "0")
 Debug zeit
  EndIf
EndProcedure

Procedure _SetTimer_Callback(Hwnd,Msg, iIDTimer, dwTime)

   WriteSerialPortString(0,"010D"+#CRLF$)
     Delay(100)
     CreateFile(2,"Text.txt")
     While AvailableSerialPortInput(0)>0
       If ReadSerialPortData(0,@Byte,1)    
         text.s=text.s+Chr(Byte)
        
       EndIf
     Wend
   
   WriteStringN(2,text)
   CloseFile(2)
   EraseTextLines("test.txt",4)
   EraseTextLines("test.txt",1)
   EraseTextLines("test.txt",1)
   
   ReadFile(1,"test.txt")
   While Eof(1)=0
     t$=ReadString(1)
     Wend
     If Hex2Dec(Right(t$,3)) >= 100
       KillTimer_(0, 1)
       KillTimer_(0,iIDTimer)
       Debug "finish"
       EndIf 
     CloseFile(1)

EndProcedure

OpenSerialPort(0, "COM5", 9600, #PB_SerialPort_EvenParity , 8, 1, #PB_SerialPort_NoHandshake, 1024, 1024)

Timertime=100
iIDTimer=SetTimer_(0,#PB_Event_FirstCustomValue+1,Timertime,@_SetTimer_Callback())
Time = ElapsedMilliseconds()
SetTimer_(0, 1, 20, @timerCB())
MessageRequester("Test","")
Der Code ist jetzt ziemlich durcheinander, da der wahrscheinlich sowieso noch um einiges verbessert werden kann. So habe ich meine Ziele umgesetzt das in einer entsprechenden Zeit die Anfragen gesendet werden und er daraufhin kontrolliert ob der Wert nach der Umrechnung von Hex in Dezimal 100 erreicht hat und den Timer stoppt und anzeigt wie lange er dafür gebraucht hat.

Nur extrem unschön, das muss doch schöner und sinnvoller gehen oder ? Ich versuche immer meine Ziele so gut es geht selber zu erreichen, jedoch stoße ich hier derzeitig an meine Grenzen wenn es um die Effizienz geht :|

Mit freundlichen Grüßen
HHYayo

Re: Daten von einen Serial Port empfangen und verarbeiten

Verfasst: 29.02.2016 10:09
von HeX0R
Ich habe mir das jetzt dreimal durchgelesen, verstehe es aber immer noch nicht?!
Du schreibst nur eine Zeile in Deine Datei, löschst aber drei, d.h. vermutlich kommen da auch LF und/oder CR im Rückgabewert vor.

Kannst Du nicht mal eine komplette Antwort des Simulators posten, damit ich mir das besser vorstellen kann?
Können da auch 0-Bytes vorkommen? Dann kannst Du eh nicht mit String arbeiten.

Re: Daten von einen Serial Port empfangen und verarbeiten

Verfasst: 29.02.2016 14:56
von HHYayo
Schönen guten Tag,

einmal die komplette Ausgabe war bereits im ersten Post in einen Zitat gepostet:
010D

41 0D 00
>
Jedoch brauche ich wie gesagt nur die Zahlen die nach 41 0D kommen, in diesen Fall der Wert "00", der aber auch zum Beispiel "FF" sein kann, wenn der Wert 255 ist.

Entweder sendet der Simulator mindestens "00" für 0 zurück oder garnichts, weil gar nicht erst etwas versendet oder empfangen werden konnte!

Mit freundlichen Grüßen
HHYayo

Re: Daten von einen Serial Port empfangen und verarbeiten

Verfasst: 29.02.2016 15:02
von NicTheQuick
Wie wäre es mit dieser simplen Zeile statt deiner komische Dateikonstruktion:

Code: Alles auswählen

sim.s = ~"010D\n\n41 0D 00\n>"
Debug sim
Debug Right(StringField(ReplaceString(sim, ~"\r\n", ~"\n"), 3, ~"\n"), 2)
Und wenn du es gleich in eine Zahl umgerechnet haben willst, dann eben noch mit Val:

Code: Alles auswählen

sim.s = ~"010D\n\n41 0D 00\n>"
Debug sim
Debug Val("$" + Right(StringField(ReplaceString(sim, ~"\r\n", ~"\n"), 3, ~"\n"), 2))

Re: Daten von einen Serial Port empfangen und verarbeiten

Verfasst: 29.02.2016 16:09
von HHYayo
NicTheQuick hat geschrieben:Wie wäre es mit dieser simplen Zeile statt deiner komische Dateikonstruktion:

Code: Alles auswählen

sim.s = ~"010D\n\n41 0D 00\n>"
Debug Right(StringField(ReplaceString(sim, ~"\r\n", ~"\n"), 3, ~"\n"), 2)
Und wenn du es gleich in eine Zahl umgerechnet haben willst, dann eben noch mit Val:

Code: Alles auswählen

sim.s = ~"010D\n\n41 0D 00\n>"
Debug Val("$" + Right(StringField(ReplaceString(sim, ~"\r\n", ~"\n"), 3, ~"\n"), 2))
Meine komische Dateikonstruktion ist entstanden, weil ich zeigen wollte, dass ich auch selber versuche einen Lösungsweg zu finden (Und nicht nur Codeschnipsel schnorre, ohne einmal selbst nachzudenken).

Aber mit deinem Code Funktioniert alles einwandfrei!

Ich würde mich freuen, wenn du einmal erklären könntest wieso man mit ~"\r\n" und ~"\n" auf den Wert hinter 41 0D kommst. Also was ReplaceString als String$ zurück gibt für StringField, welches entsprechend die empfangenen Sachen splitted.

Mit freundlichen Grüßen
HHYayo

Re: Daten von einen Serial Port empfangen und verarbeiten

Verfasst: 29.02.2016 16:30
von NicTheQuick
Ich würde mich freuen, wenn du einmal erklären könntest wieso man mit ~"\r\n" und ~"\n" auf den Wert hinter 41 0D kommst. Also was ReplaceString als String$ zurück gibt für StringField, welches entsprechend die empfangenen Sachen splitted.
Gerne.

Code: Alles auswählen

sim.s = ~"010D\n\n41 0D 00\n>"
Debug #DQUOTE$ + sim + #DQUOTE$

; Falls 'sim' nicht #LF$, sondern #CRLF$ enthält, ändere es wieder in #LF$
sim = ReplaceString(sim, ~"\r\n", ~"\n")
Debug #DQUOTE$ + sim + #DQUOTE$

; Dritte Zeile suchen, in dem mit 'StringField' der String in seine Bestandteile zwischen jedem #LF% zerlegt wird
sim = StringField(sim, 3, ~"\n")
Debug #DQUOTE$ + sim + #DQUOTE$

; Nimm nur die letzten beiden Hexziffern
sim = Right(sim, 2)
Debug #DQUOTE$ + sim + #DQUOTE$

; Wandle den Hex-String in eine Zahl um
Debug Val("$" + sim)
Hier ist es vielleicht etwas übersichtlicher, weil ich die Escaped Strings nicht benutze:

Code: Alles auswählen

sim.s = "010D" + #CRLF$ + #CRLF$ + "41 0D 00" + #CRLF$ + ">"
Debug #DQUOTE$ + sim + #DQUOTE$

; Falls 'sim' nicht #LF$, sondern #CRLF$ enthält, ändere es wieder in #LF$
sim = ReplaceString(sim, #CRLF$, #LF$)
Debug #DQUOTE$ + sim + #DQUOTE$

; Dritte Zeile suchen, in dem mit 'StringField' der String in seine Bestandteile zwischen jedem #LF% zerlegt wird
sim = StringField(sim, 3, #LF$)
Debug #DQUOTE$ + sim + #DQUOTE$

; Nimm nur die letzten beiden Hexziffern
sim = Right(sim, 2)
Debug #DQUOTE$ + sim + #DQUOTE$

; Wandle den Hex-String in eine Zahl um
Debug Val("$" + sim)
Klar soweit?

Re: Daten von einen Serial Port empfangen und verarbeiten

Verfasst: 29.02.2016 16:50
von HHYayo
Besser hätte man es nicht erklären können :allright:

Vielen vielen Dank!

Re: Daten von einen Serial Port empfangen und verarbeiten

Verfasst: 29.02.2016 22:33
von HeX0R
Es scheint doch aber so zu sein, dass das Paket, dass Du bekommst immer gleich lang ist, richtig?
Dann lässt sich das Ganze doch aber wirklich recht einfach umsetzen.

Je nachdem, ob da jetzt als Zeilenvorschub nur ein oder zwei Zeichen kommen, ist Dein Rückgabepaket immer 16 oder 19 Zeichen lang (ich denke mal die eckige Klammer gehört noch dazu).
Also musst Du einfach warten, bis Dir AvailableSerialPortInput(0) 16 oder 19 zurückgibt, dann liest Du alles in einem Rutsch in einen Memorypuffer und holst Dir die Bytes Nr. 12 + 13 bzw. 14 + 15.

Oder aber, falls die Pakete variabel in der Länge sind, ist evtl. die eckige Klammer das Abschlusszeichen, dann solange den Puffer füllen, bis die am Ende da steht.

[Edit]
Hier mal ein simples Beispiel (ungetestet)

Code: Alles auswählen

Enumeration
	#SEND_TIMER
	#RECEIVE_TIMER
EndEnumeration

Procedure main()
	Protected *Buffer, BufferPos, AvailableBytes, Value
	
	If OpenSerialPort(0, "COM5", 9600, #PB_SerialPort_EvenParity , 8, 1, #PB_SerialPort_NoHandshake, 1024, 1024) = 0
		MessageRequester("Error", "Can't open RS232!")
		ProcedureReturn
	EndIf
	
	*Buffer = AllocateMemory(8192)
	If *Buffer = 0
		MessageRequester("Error", "No memory available!")
		ProcedureReturn 
	EndIf
	
	;create a stupid window
	OpenWindow(0, 0, 0, 215, 80, "RS232", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
	TextGadget(0, 5, 15, 100, 24, "Value:")
	StringGadget(1, 110, 15, 100, 24, "", #PB_String_ReadOnly)
	CreateStatusBar(0, WindowID(0))
	AddStatusBarField(#PB_Ignore)
	
	;send any 200ms
	AddWindowTimer(0, #SEND_TIMER, 200)
	;receive any 5ms
	AddWindowTimer(0, #RECEIVE_TIMER, 5)
	
	
	Repeat
		
		Select WaitWindowEvent()
			Case #PB_Event_CloseWindow
				Break
				
			Case #PB_Event_Timer
				Select EventTimer()
					Case #SEND_TIMER
						If WriteSerialPortString(0, "010D" + #CRLF$, #PB_Ascii) = 0
							MessageRequester("Error", "SendString Error!")
							Break
						EndIf
					Case #RECEIVE_TIMER
						AvailableBytes = AvailableSerialPortInput(0)
						If AvailableBytes > 0
							ReadSerialPortData(0, *Buffer + BufferPos, AvailableBytes)
							BufferPos + AvailableBytes
							StatusBarText(0, 0, "Received: " + Str(BufferPos) + "Bytes")
							;check for '>'
							If PeekB(*Buffer + BufferPos - 1) = 62
								;end of packet
								
								If BufferPos = 16
									;one linefeed char
									Value = Val("$" + PeekS(*Buffer + 12, 2, #PB_Ascii))
								Else
									;two linefeed chars
									Value = Val("$" + PeekS(*Buffer + 14, 2, #PB_Ascii))
								EndIf
								SetGadgetText(1, Str(Value))
								BufferPos = 0
							EndIf
						EndIf
				EndSelect
		EndSelect
		
	ForEver
	
	CloseSerialPort(0)
EndProcedure

main()
Musst nur aufpassen, dass bei Fehlfunktion der Puffer nicht überläuft.
Ich mache da üblicherweise eine dynamische Anpassung, war ich jetzt aber zu faul :mrgreen: