serial Stream aufteilen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Smasch
User
Beiträge: 9
Registriert: Dienstag 24. Januar 2017, 17:41

Hi Leute ich bin auf der Sucher nach einer Idee.
Ich lese über eine serielle Schnittstelle 200 Werte ein, die mit einem Komma getrennt sind.
Diese Werte möchte ich formatiert in einer Datei speichern. Sowas wie Variable1=Wert1, Variable2=Wert2,...
Mein Problem ist das die Werte unterschiedlich lang sind mal 4,5 oder 6 Zeichen lang.

Gibt es eine Funktion um leichter an meine Werte zu kommen? Eigentlich müssten meinen Werte doch in schönen Byte Blöcken vorhanden sein?!

Danke für eure Ideen.

Code: Alles auswählen

import serial
import time 

port = serial.Serial("/dev/tty.usbserial-FTHEUS5P", 9600, timeout = None);


port.isOpen();

while True:
	daten = port.readline(); 
	f = open('spam.txt', 'a+')
	f.write(daten.decode('utf-8'))
	f.close()
	print (daten);
	print (len(daten));
	print (daten[0:5])
Zuletzt geändert von Anonymous am Dienstag 24. Januar 2017, 18:05, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17753
Registriert: Sonntag 21. Oktober 2012, 17:20

@Smasch: die Daten kommen schon in diesem Format von der Seriellen Schnittstelle? Wenn das Komma ein eindeutiges Trennzeichen ist, würde man die Schlüssel-Wert-Paare am Komma auftrennen.
BlackJack

@Smasch: Anmerkungen zum Quelltext:

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

`time` wird importiert aber gar nicht verwendet.

Das Semikolon trennt in Python Anweisungen innerhalb einer Zeile. Das gehört also nicht ans Ende einer Zeile. Das geht zwar weil es auch die leere Anweisung gibt, aber es macht keinen Sinn.

Für Baudrate und `timeout` verwendest Du die Default-Werte, also braucht man die nicht angeben.

``port.isOpen()`` macht keinen Sinn → Weg damit.

`Serial`-Objekte sind iterierbar, und zwar genau wie bei Dateien iteriert man über die Zeilen. Also würde man statt der ``while``-Schleife eine ``for``-Schleife verwenden. Genau wie Dateiobjekte sind `Serial`-Objekte auch „context manager“, können also mit der ``with``-Anweisung verwendet werden.

Das '+' im Dateimodus ist nicht nötig und sollte auch nicht verwendet werden. Das macht selten Sinn, und wenn dann sowieso nur bei Binärdateien.

Zwischen Funktionsname und öffnender Klammer für den Aufruf gehört kein Leerzeichen.

Ich lande dann als Zwichenergebnis ungefähr hier:

Code: Alles auswählen

from serial import Serial


def main():
    with Serial('/dev/tty.usbserial-FTHEUS5P') as port:
        for daten in port:
            with open('spam.txt', 'a', encoding='utf-8') as out_file:
                out_file.write(daten.decode('utf-8'))
            print(daten)
            print(len(daten))
            print(daten[:5])


if __name__ == '__main__':
    main()
Zur Frage: Schau Dir mal in der Python-Dokumentation an was man mit Zeichenketten und `bytes` so alles machen kann.

Ansonsten wäre eine bessere Beschreibung der Daten und des gewünschten Ergebnisses sinnvoll. Es ist anscheinend nicht ganz klar ob das was Du gezeigt hast die Eingabe oder die gewünschte Ausgabe sein soll.
Smasch
User
Beiträge: 9
Registriert: Dienstag 24. Januar 2017, 17:41

Vielen Dank für diese ausführliche Antwort! :)
Ich werden mir das morgen früh genauer angucken und nachvollziehen.

Okay, ich versuche mein Vorhaben genauer zu beschreiben:
Ich habe einen Mikrocontroller der Messungen ausführt, dabei entspricht 1 Messung 200 Werten und es sollen 50/60 Messungen pro Sekunde gemacht werden. Die Standard Baudrate von 9600 hab ich nur für mich stehen lassen, um den Prozess zu verlangsamen und nachvollziehbarer zu machen. Am Ende sollen 921.600 oder vielleicht noch mehr erreicht werden. Die Messungen laufen auch über einen längeren Zeitraum (mehrere Sekunden oder Minuten) und sollen nicht nach 1 Sekunde zu Ende sein.
Mit diesem Projekt versuche ich mich auch in Python einzuarbeiten. Ich habe schon ein ähnliches Programm aber mit C geschrieben. Daher vielen Dank, dass Du mich auf diese Fehler aufmerksam gemacht hast.
Das Programm soll einmal eine Datei erstellen, in der die Daten einfach abgespeichert werden und eine zweite Datei (JSON) in der die 200 Werten Variablen zu geordnet werden und die 50x pro Sekunde geupdatet wird damit ich mit einer Webapp die Daten visualisieren kann (Graphen).

Der Code, den ich hier gepostet habe, ist nur mein Anfang den ich noch erweitern möchte.

Vom Mikrocontroller kommt so was wie: "8000,12000,15000,6500,...,\n"
Mit meiner Frage habe ich gehofft das es eine Möglichkeit gibt schon beim Lesen der Schnittstelle die Werte in eine Art Array zu packen.
BlackJack

@Smasch: Du kannst die Zeile lesen und an den Kommas in eine Liste aufteilen, dann hast Du eine Liste mit den Bytes die jeweils einen Wert ausmachen als ASCII-Darstellung ausmachen, die kann man dann mit der `int()`-Funktion und einer „list comprehension“ einfach zu einer Liste mit Zahlen umwandeln. Dann brauchst Du nur noch 200 Namen (wo kommen die her?) und kannst mit `zip()` und `dict()` ein Wörterbuch daraus machen, was mit Hilfe des `json`-Moduls zu einem JSON-Objekt serialisiert werden kann.
Smasch
User
Beiträge: 9
Registriert: Dienstag 24. Januar 2017, 17:41

Vielen Dank für die Hilfe. Ich erstelle die Liste vom Seriellen Stream mit dem split Befehl.
Diese Liste füge ich dann mit einer zweiten Liste (wellen) zusammen. Damit habe ich mein gesuchtes Format (x,y) siehe Ausgabe "messliste".

In Zeile 28-30 versuche ich jetzt das Ganze mit matplotlib schön grafisch anzeigen zu lassen. Er zeigt es auch an updatet es leider noch nicht.

Code: Alles auswählen


from serial import Serial
import datetime
import matplotlib.pyplot as plt

def main():
  try:
        dateiname = "Messung"+'{:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now())+'.txt'
        with Serial('/dev/tty.usbserial-FTHEUS5P', 9600) as port:
            for daten in port:
                liste = daten.decode('utf-8').split(",")
                if(len(liste)==289):
                    i = 1
                    while i<=287:
                        liste[i]=int(liste[i])-6040
                        i=i+1

                    #Listen (x,y)
                    messliste = []
                    i=0
                    while i<=287:
                        messliste.append([])
                        messliste[i].append(wellen[i])
                        messliste[i].append(liste[i])
                        i=i+1
                    print(messliste)

                    zip(*messliste)
                    plt.plot(*zip(*messliste))
                    plt.show()



    except KeyboardInterrupt:
        out_file.close()
        port.close()
        print("Strg+c: Programm wurde beendet!")


if __name__ == '__main__':
    main()

Ausgabe "messliste":
[[324, '8116'], [326, 2032], [329, 127], [332, 0], [335, 0], ... [891, 63], [892, 0]]
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Du solltest wohl noch etwas an deinen Python-Kenntnissen in Bezug auf Operationen auf Sequenzen arbeiten:

Code: Alles auswählen

liste = daten.decode('utf-8').split(",")
i = 1
while i<=287:
    liste[i]=int(liste[i])-6040
    i=i+1

# ...wird zu:
items = daten.decode('utf-8').split(",")[1:288]
liste = [int(item) - 6040 for item in items]
Wobei man einen besseren Namen als "liste" wählen sollte.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Und auch hier kann man es deutlich kompakter lösen:

Code: Alles auswählen

messliste = []
i=0
while i<=287:
    messliste.append([])
    messliste[i].append(wellen[i])
    messliste[i].append(liste[i])
    i=i+1

# ...wird zu:
messliste = list(zip(wellen[:288], liste[:288]))

# Oder alternativ:
from itertools import islice

slices = [islice(wellen, 288), islice(liste, 288)]
messliste = list(zip(*slices))
Dies verhindert das Erzeugen von unnötigen Listen als Zwischenschritte.
Smasch
User
Beiträge: 9
Registriert: Dienstag 24. Januar 2017, 17:41

Danke für eure Antworten!

Ich bin jetzt auf ein anderes Problem mit der seriellen Verbindung gestoßen und zwar bekomme ich ab und zu mal diese Fehler:

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe0 in position 1: invalid continuation byte
oder
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb2 in position 7: invalid start byte
bzw. das Programm gibt mir gar keine Ausgabe beim einlesen der Schnittstelle

Gibt es Möglichkeiten den Port besser einzustellen um den Fehlern entgegenzuwirken?

Mit dem folgendem Code verrennt sich das Programm immer bzw schafft es einmal die Schnittstelle auszulesen.

Code: Alles auswählen

def schnittstelle_auslesen(dateiname):
    global dia
    global fig
    fulltransition=0
    with Serial('/dev/tty.usbserial-FTHEUS5P', 500000) as port:
        while fulltransition != 289:
            daten=port.readline()
            liste=daten.decode('utf-8').split(",")
            print(liste)
            if(len(liste)==289):
                fulltransition=289
                i=0
                while i<=287:
                    liste[i]=int(liste[i])-6040
                    i=i+1
                with open(dateiname, 'a', encoding='utf-8') as out_file:
                    out_file.write('{:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now())+'  ||  ')
                    out_file.write(daten.decode('utf-8'))
                    out_file.write('\n\n')
                messliste = []
                messliste = list(zip(wellen[:288], liste[:288]))
#                if dia == None:
#                    plt.ion()
#                    #fig, ax = plt.subplots()
#                    plt.axis([300, 900, 0, 6000])
#                    dia, = plt.plot(*zip(*messliste))
#                    plt.title('Messung')
#                    plt.xlabel('Wellenlängen')
#                    plt.ylabel('Intensität')
#                    #löscht die Umrandung
#                    ax = plt.subplot(111)
#                    ax.spines["top"].set_visible(False)
#                    ax.spines["right"].set_visible(False)
#                    ##löscht die Umrandung
#                    plt.show()
#                    plt.pause(0.02)
#                else:
#                    plt.pause(0.02)
#                    dia.set_ydata(liste[:288])
                    #fig.canvas.draw()
                    


                print(messliste)

def main():
    newINTtime=INTtime=15
    newDAC0=DAC0=0
    newDAC1=DAC1=0

    kalibrieren()
    dateiname=logdatei_erstellen()
    
    #Endlos auslesen
    runningtoheaven=0

    while runningtoheaven!=1:
    	schnittstelle_auslesen(dateiname)
    	if(INTtime!=newINTtime):
    		newINTtime=INTtime=sendnewINTtime()
    	if(DAC0!=newDAC0):
    		newDAC0=DAC0=sendnewDAC0()
    	if(DAC1!=newDAC1):
    		newDAC1=DAC1=sendnewDAC1

if __name__ == '__main__':
    main()

Ich habe es nochmal mit einem kürzeren Programm versucht damit bekomm ich die Fehler seltener.
Liegt es an der Länge des Programms?

Code: Alles auswählen

from serial import Serial
import datetime
 
def main():
    try:
        dateiname = "Messung"+'{:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now())+'.txt'
        with Serial('/dev/tty.usbserial-FTHEUS5P', 500000) as port:
            for daten in port:
                liste = daten.decode('utf-8').split(",")
                if(len(liste)==289):
                    i = 0
                    while i<=287:
                        liste[i]=int(liste[i])-6040
                        i=i+1
                    with open(dateiname, 'a', encoding='utf-8') as out_file:
                        out_file.write('{:%Y-%m-%d %H:%M:%S}'.format(datetime.datetime.now())+'  ||  ')
                        out_file.write(daten.decode('utf-8'))
                        out_file.write('\n\n')
                    print(liste[:287])

    except KeyboardInterrupt:
        out_file.close()
        port.close()
        print("Strg+c: Programm wurde beendet!")
 
 
if __name__ == '__main__':
    main()
BlackJack

@Smasch: Was sind denn das für Daten die da gesendet werden? Offensichtlich kommen neben den Messwerten ja auch Zeilen die Dich nicht interessieren. Wie sind die kodiert? Wirklich UTF-8?
Smasch
User
Beiträge: 9
Registriert: Dienstag 24. Januar 2017, 17:41

[codebox=c file=Unbenannt.c]unsigned int data[288];

void printdata(){
for (int i = 0; i < 288; i++){
printf("%d,",data);
}
printf("\n");
}[/code]

Das kommt vom Mikrocontroller.
BlackJack

@Smasch: Dann können die Bytes die da angemeckert werden nicht kommen und ich verstehe auch nicht warum Du nach dem `split()` auf die Anzahl der Elemente prüfst. Es sei denn man muss mit korrupten Daten rechnen, dann ist dieser Anzahltest aber nicht robust genug, da würde man eine Prüfsumme verwenden. Ausserdem würde ich die Kodierung auf ASCII einschränken, denn UTF-8 wird hier ja gar nicht benötigt.
Smasch
User
Beiträge: 9
Registriert: Dienstag 24. Januar 2017, 17:41

@BlackJack vielen Dank für deine Antworten!

Der uC schickt in einer Endlosschleife, wenn er eingeschaltet wird, die 288 Daten und das "\n".
Mit dem Programm auf dem PC fange ich manchmal mittendrin in einem Sendevorgang an zu lesen und ich möchte vermeiden, dass ich nur halbe Messungen bekomme. Ich möchte nur eine volle Messung mit 288 Werten zulassen.

Was wäre denn eine Möglichkeit meinen Test robuster zu gestalten? Bzw. wie verwende ich eine Prüfsumme?
Antworten