Serielle Daten zeilenweise einlesen mit Python

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
u-Bloxer
User
Beiträge: 7
Registriert: Donnerstag 15. September 2016, 12:02

Hey ihr alle,

erstmal kurz eine Beschreibung von dem was ich vorhab:
Ich habe ein textfile das in etwa so aussieht:

Eingangsfile:
% (lat/lon/height=WGS84/ellipsoidal,Q=1:fix,2:float,3:sbas,4:dgps,5:single,6:ppp,ns=# of satellites)
% GPST latitude(deg) longitude(deg) height(m)
2016/09/14 13:37:05.000 49.861581899 8.650614733 :D
....

Dieses wird von einem Windows 10 PC an einen USB Port weitergeleitet von dort geht das File über eine UART schnittstelle in ein Beagle Bone Black (BBB). Das heißt der PC wird mit einem USB zu UART Kabel an das BBB angeschlossen. Nun möchte ich ein python skript schreiben, dass mir am besten zeilenweise die Datei die es über die UART Schnittstelle bekommt einliest. Dabei möchte ich jede zeile mit einem zeitstempel (der Systemzeit des Beagle Bones) versehen und in ein text file auf dem Beagle bone abspeichern. Im speziellen soll dann mein output file so aussehen dass dort wo ich jetzt mal den smile in dem Input file hingemacht habe immer die systemzeit zu der die datei ankam steht also soetwas:

Ausgabefile:
2016/09/14 13:37:05.000 49.861581899 8.650614733 SYSTEMZEIT
....

Nun möchte ich einfach mal fragen wie man dieses Projekt am besten angeht beziehunsweise im konkreten würde mich interessieren ob ich zum Beispiel einfach folgende Befehle als loop verwenden kann:

ser = serial.Serial('/dev/ttyS1', 9600)
line = ser.readline() # read a '\n' terminated line
ser.close()

ser.readline() soll ja bis zu einem <CR> immer lesen aber ich bin mir nicht sicher ob in dem .txt File ein <CR> tatsächlich auch irgendwie drinnen steht - gibt es eine Möglichkeit das rauszufinden?
Achja und das wichtigste dabei ist natürlich dass durch die Ausführung des python scripts möglichst keine zeit verloren geht. Daher es soll immer direkt nach dem einlesen einer zeile ein zeitstempel mit dieser zeile in ein file geschrieben werden und dann soll die nächste zeile eingelesen werden....

Ich freue mich von euch zu hören - und vor allem ob man das ganze projekt am besten mit line read realisieren würde oder ob es da evtl. große Probleme mit gibt.

Liebe Grüße
u-Bloxer
BlackJack

@u-Bloxer: Klar gibt es eine Möglichkeit das heraus zu finden: Ausprobieren. :-)

Statt `readline()` in einer Schleife aufzurufen kann man auch einfach direkt über das `Serial`-Exemplar iterieren:

Code: Alles auswählen

with serial.Serial('/dev/ttyS1', 9600) as lines:
    for line in lines:
        do_something(line)
u-Bloxer
User
Beiträge: 7
Registriert: Donnerstag 15. September 2016, 12:02

Hey wow für deine Antwort funktioniert tadellos:
Alle zeilen von einem File das ich an meinen usb port ausgebe und dann über mein Beagle Bone über Serial einlese klappt wunderbar mit folgendem code:

Code: Alles auswählen

import serial
from time import sleep
import time
import datetime

f = open("serial_Test1.txt", "w")
i  = 0

nachher = time.time()

with serial.Serial('/dev/ttyO1', 230400) as lines:
    for line in lines:
	print ("Line written",i)
	f.write('%s  %s  %.6f  ' % (line,datetime.datetime.now(),time.time()-nachher))
   	nachher = time.time()
	i=i+1
Echtzeitfähigkeit???
Noch zwei kleinere Fragen: Wie viel Zeit geht denn mit diesem Code verloren also damit meine ich vor allem auf dem Weg:
Ich sende die Daten mit einem win 10 pc (lenovo z500) und dann beutzte ich die UART1 Schnittstelle des Beagle Bones um sie wie oben zu sehen in ein File zu schreiben. Bei meiner Anwendung geht es um wenige millisekunden. Daher wäre es äußerst wichtig herauszufinden wie viel zeit auf dem Weg "verloren" geht und das am besten in millisekunden...

Ich hab mal gehört man kann das beagle bone vllt. mal über die schnittstelle anpingen oder so und dann schauen wie viel zeit das dauert und das entspricht dann ungefähr der Zeit ?

Liebe Grüße
Markus
Zuletzt geändert von Anonymous am Donnerstag 15. September 2016, 17:22, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@u-Bloxer: Die Einrückung stimmt nicht. Hast Du eventuell mit was anderem als Leerzeichen eingerückt?

`sleep` wird importiert aber nicht verwendet. `datetime` *und* `time` ist ein bisschen zu viel. Was Du da machst kann man entweder mit dem einen, oder mit dem anderen machen.

`f` ist kein guter Name. Sind einbuchstabige Namen selten, wenn es nicht gerade `i`, `k`, `j` für ganze Zahlen oder `x`, `y`, `z` für Koordinaten sind, oder der Gültigkeitsbereich auf einen Ausdruck beschränkt ist.

Dateien die man öffnet, sollte man auch wieder schliessen. Die ``with``-Anweisung ist da recht nützlich.

`i` wird ziemlich weit vor der Verwendung initialisiert. Und mit `enumerate()` kann man sich das initialisieren und manuelle hochzählen ganz sparen.

Zwischen einer Funktion und der öffnenden Klammer für den Aufruf gehört kein Leerzeichen.

Die `print()`-Ausgabe steht an der falschen Stelle oder gibt das falsche aus. Denn 'written' bedeutet die Zeile wurde bereits geschrieben, das wird sie aber erst nach dem `print()`-Aufruf.

`nachher` ist ein komischer Name. Ich würde da nicht darauf kommen, das es sich um den Zeitstempel der letzten empfangenen Zeile handelt.

Die im Grunde gleiche Zeit wird viel zu oft ermittelt. Was dazu führt das die Werte alle leicht unterschiedlich sind, weil während des ermittelns ja auch ein wenig Zeit vergeht.

Ich lande dann ungefähr hierbei (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
from datetime import datetime as DateTime
import serial
 

def main():
    with open('serial_Test1.txt', 'w') as out_file:
        previous_timestamp = DateTime.now()
        with serial.Serial('/dev/ttyO1', 230400) as lines:
            for i, line in enumerate(lines):
                now = DateTime.now()
                out_file.write(
                    '{0}  {1}  {2:.6f}  '.format(
                        line, now, (now - previous_timestamp).total_seconds()
                    )
                )
                print('Line written', i)
                previous_timestamp = now()


if __name__ == '__main__':
    main()
Echtzeitfähigkeit? Klares Nein! Da sind zu viele Komponenten für sich genommen schon nicht echtzeitfähig sind. Allen voran die Betriebssysteme auf beiden Seiten der Verbindung. Und wo speichert der Beagle denn die Textdatei?
u-Bloxer
User
Beiträge: 7
Registriert: Donnerstag 15. September 2016, 12:02

Hey BlackJack,

hab gerade mal deinen Code ausgetestet: - ich bekomme folgende ausgabe in dem terminal in dem ich das script aufrufe:

Line written 0
Traceback (most recent call last):
File "serial1.py", line 20, in <module>
main()
File "serial1.py", line 16, in main
previous_timestamp = now()
TypeError: 'datetime.datetime' object is not callable

der beagle bone speichert die textdatei einfach auf dem Desktop also auf seinem internen EMCC flasher speicher ist das nehm ich mal an. Auf jeden fall habe ich keine externe micro SD card drinnen und auch sonst nichts angeschlossen.

Achja und ich bin noch ganz neu in python daher verzeih die dumme frage aber was genau machen diese beiden zeilen:

Code: Alles auswählen

if __name__ == '__main__':
    main()
BlackJack

@u-Bloxer: Das sollte natürlich ohne die Klammern. :oops:

Ich fragte nach dem Ziel der Datei weil das auch die Echtzeitfähigkeit stören wird, denn immer wenn der Ausgabepuffer voll ist, beziehungsweise wenn das Betriebssystem dann letztendlich schreibt, wird das System ausgebremst. eMMC-Flash-Speicher ist langsamer beim schreiben als zum Beispiel die übliche Festplatte oder SSD.

Die letzten beiden Zeilen rufen die `main()`-Funktion auf wenn `__name__` den Wert '__main__' hat. Was nur der Fall ist wenn man das Modul als Programm ausführt, aber nicht wenn man es importiert.
u-Bloxer
User
Beiträge: 7
Registriert: Donnerstag 15. September 2016, 12:02

So vielen Dank jetzt hab ichs mal zum laufen gebracht... dafür einfach nach dem now() im obigen code nur now schreiben!

Ausgabe:
2014-04-23 20:33:10.622759 0.039469 2016/09/06 11:20:27.123 49.976361280 9.755629563
2014-04-23 20:33:25.915385 15.292626 2016/09/06 11:20:27.248 49.976361060 9.755629967
2014-04-23 20:33:26.900839 0.985454 2016/09/06 11:20:27.373 49.976360729 9.755630189
2014-04-23 20:33:27.043635 0.142796 2016/09/06 11:20:27.500 49.976360544 9.755630010
2014-04-23 20:33:27.153618 0.109983 2016/09/06 11:20:27.625 49.976360531 9.755630014
2014-04-23 20:33:27.285777 0.132159 2016/09/06 11:20:27.750 49.976360525 9.755629938
2014-04-23 20:33:27.410588 0.124811 2016/09/06 11:20:27.875 49.976360396 9.755629967
2014-04-23 20:33:27.547739 0.137151 2016/09/06 11:20:28.000 49.976360237 9.755629920

-> jap also etwas seltsam, das ganze braucht ein wenig "Anlaufzeit" wie man an den 15 sec sieht danach klappt es aber erstmal soweit mit dem code.

Nun zu einem etwas schwierigeren Problem:
Ich hab nun auch noch eine andere Eingabedatei - Diese enthält zum einen NMEA sentences (Beginnend mit $ und endend mit <CR><LF>) UND
UBX Rohdaten. Ein solcher Rohdatensatz beginnt immer mit:
Beginn: "A UBX message starts with two Bytes: 0xB5 and 0x62 (in ASCII: µ b)"
Ende: "CK_ A and CK_ B which is a 16 Bit checksum."

ein solcher Datensatz sieht wie folgt aus:
$PUBX,04,094646.50,060916,208006.50,1913,17D,-112,-24.077,21*5B
µb0 ½d Ay /0ãÅ@QŸ£tAÛl“×g›AvhE S) ,fRÎsA¾Ë-šAôYÄ S, ÝM +ÀvAø˺EñœAT­Å S' ÿsÕØwAò,ð$ÿ[žAš<Å S% £K ¬ásAÝhÆ/ššA@RD S, ÷Ê»fÂeuA@ž«ltœAœÌ\D S) c¨0ltAi ‚½”ÔšAÿ?lB S* ò©Zwè˜vA j3dù¯A­EE S# …ZEª.{vA''Só눝A’RE S' €h$GNDTM,W84,,0.0,N,0.0,E,0.0,W84*71
$GNRMC,094646.63,V,,,,,,,060916,,,N*67
$GNVTG,,,,,,,,,N*2E

Nun ist die Frage ob ich während des Einlesens einfach alle Rohdatensätze rausfiltern kann also alle mit µb beginnend und alle anderen Datensätze möchte ich einen Zeitstempel drauf drücken.

Meint ihr das funktioniert einfach so ohne weiteres oder geht dabei zu viel Zeit verloren?

Würd mich über nen kleines Minimal beispiel freuen.


Mein Minimal beispiel sieht wie folt aus - ich will erstmal die ersten beiden Zeichen einlesen und schauen ob die gleich µb sind wenn ja dann soll
einfach alles ausgelassen werden bis zum CK_A und CK_B - ich weiß aber noch nicht wie ich das überprüfe. Oder man kann natürlich auch alles weglassen bis zum nächsten $ Zeichen.

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
from datetime import datetime as DateTime
import serial
import unicodedata
     
def main():
    with open('serial_Test1.txt', 'w') as out_file:
        previous_timestamp = DateTime.now()
        with serial.Serial('/dev/ttyO1', 230400) as lines:
            for i, line in enumerate(lines):
                now = DateTime.now()
                out_file.write('{0}  {1}  {2:.6f}  '.format(line, now, (now - previous_timestamp).total_seconds()))
                print('Line written', i)
		a=unicodedata.normalize('NFKD', line.split()[0][0]).encode('ascii', 'ignore')
		print ('outupt',a)
                previous_timestamp = now
     
main()
Liebe Grüße und Tausend Dank für all eure Bemühungen!
u-Bloxer
u-Bloxer
User
Beiträge: 7
Registriert: Donnerstag 15. September 2016, 12:02

ups sry leider konnte ich meinen code nicht mehr ändern:

nun ich möchte aus der Textdatei nur die NMEA sentences auslesen die mit $GNGGA beginnen bzw. auch die die mit $GNRMC beginnen:

Hiefür mein Code Beispiel:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function
from datetime import datetime as DateTime
import serial
import unicodedata


def main():
	i = 0
    with open('serial_Test1.txt', 'w') as out_file:
        previous_timestamp = DateTime.now()
        with serial.Serial('/dev/ttyO1', 230400) as lines:
            for line in lines:
				if((line.split()[0][0]=='$') and (line[0:6]=='$GNGGA')):
					now = DateTime.now()
					out_file.write('{0}  {1}  {2:.6f}  '.format(line, now, (now - previous_timestamp).total_seconds()))
					i=i+1
					print('Line written', i)
					previous_timestamp = now
     
main()

#... sry wenn das mit dem Einzug net stimmen sollte - bekomm das irwie nicht hin. (Hier zumindest)
So dieser Code funktioniert soweit-das einzige große Problem: Wenn ich die Datenübertragung am PC stoppe dann dauert das ca. 8 Sekunden bis
auch die ausgabe mit print line am Beagle Bone stoppt..... heißt das dass ich dadurch ne Zeitverzögreung von 8 sekunden habe?

Viele Grüße
u-Bloxer
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@u-Bloxer: je nach dem, wo Du welche Puffer hast, können schon noch etliche tausend Zeichen schon gesendet sein, wenn Du glaubst, dass Du das Programm abgebrochen hast. Bei Deinem if sind dreiviertel aller Klammern überflüssig und wann glaubst Du, dass die erste Bedingung erfüllt ist, wenn die zweite nicht erfüllt ist? Nimm startswith: if line.startswith(('$GNGGA', '$GNRMC')):
BlackJack

@u-Bloxer: Schauen wir mal was der Python Package Index zum Suchwort NMEA hat: https://pypi.python.org/pypi?:action=se ... mit=search

So etwas programmiert man nicht selber wenn es bereits mehrere Lösungen gibt.
u-Bloxer
User
Beiträge: 7
Registriert: Donnerstag 15. September 2016, 12:02

@Sirius3 line.startswith(('$GNGGA', '$GNRMC')): - funktioniert auch wunderbar - vielen Dank dir!

jop ich wüde halt ganz gerne messen wie lange es dauert einfach nur einen datensatz etc. zwischen meinem PC und dem BBB hin und her zu schicken.
Gibts da keine Möglichkeit diese Zeit die das ca. dauert zu ermitteln? Und vor allem kann man diese zeit geringer halten wenn man die Baud rate einfach auf das maximum 230400 setzt?

Hat hiermit jemand von euch schon Erfahrung gemacht? Ich hab im Googel dazu auch nicht allzu viel gefunden.

@BlackJack - danke für den Hinweis auf das Paket. Jap ich will ja damit erstmal nur daten mit einem zeitstempel aufzeichnen.
Das analysieren mach ich hinterher eh mit einem NMEA matlab script - das funktioniert dann auch alles wunderbar.

Bis jetzt siehts ja so aus als würde alles sehr gut funktionieren. Jedoch wäre es super diese Übermittelungszeit irgendwie herauszufinden. Wie lange brauchen die Daten wenn sie vom PC losgeschickt wurden bis sie beim BBB an der UART1 schnittstelle ankommen. Und ich kenn mich ehrlich gesagt auch überhaupt nicht damit aus wie das mit dem buffern und wie im detail die read line in funktioniert.... :oops:
u-Bloxer
User
Beiträge: 7
Registriert: Donnerstag 15. September 2016, 12:02

Hey vielen Dank nochmal euch zwei.

Habe nun alles soweit hinbekommen! :D
Antworten