Byte aus ser. Schnittstelle lesen und in String schreiben

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
dan.mue
User
Beiträge: 6
Registriert: Freitag 26. April 2013, 10:09

Hallo,

zuerst möchte ich sagen das ich Python-Neuling bin.

Zyklisch wird an ein Gerät ein "set+0x0D" Befehl zu Status abfrage gesendet. Dieser antwortet mit einem unbestimmt langen String entsprechend dem Status.
Nun möchte ich einzelne Bytes aus der seriellen Schnittstelle lesen und in einen String speichern bis Carriage Return empfangen wird.
Dann soll der ganze String ausgegeben werden. Die einzelnen Bytes auslesen klappt, aber nicht das Speichern in den String.

Code: Alles auswählen

import serial
import time

ser = serial.Serial("/dev/ttyAMA0", 115200, timeout=10)
if ser.isOpen()== False:
    ser.open()
while 1:
    print"SET an BT senden"
    ser.write("set" + chr(0x0d))
    while ser.inWaiting() > 0:
        byte = ser.read(1)
        if byte != chr(0x0d):
            s = s + byte
        elif byte == chr(0x0d):
            print s
            #Hier sollte s wieder gelöscht werden
    time.sleep(9)
Zuletzt geändert von Anonymous am Freitag 26. April 2013, 10:54, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
Sirius3
User
Beiträge: 18335
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo dan.mue,

»serial.Serial« hat wie andere Filelike-Objekte die Methode »readline« um eine Zeile zu lesen, oder ganz einfach »next(ser)«.
dan.mue
User
Beiträge: 6
Registriert: Freitag 26. April 2013, 10:09

Danke schön, hat auf Anhieb Anhieb geklappt.

Wenn die readline-Methode aufgerufen wird, wartet dann das Programm bis eine Zeile empfangen wurde?
BlackJack

@dan.mue: Noch ein paar Anmerkungen zum Quelltext:

Vergleiche mit literalen Wahrheitswerten sind überflüssig. `isOpen()` liefert ja schon `True` oder `False` und ein Vergleich mit `False` liefert auch wieder einen Wahrheitswert. Wenn man auf das Gegenteil des Wertes testen möchte, dann gibt es den ``not``-Operator, also ``if not ser.isOpen():``. Allerdings ist dieser Test überflüssig, denn an der Stelle ist der Rückgabewert von `isOpen()` *immer* `True`.

Da es einen eigenen Typ für Wahrheitswerte gibt, sollte man für eine Endlosschleife `True` statt 1 nehmen, denn man möchte dort ja einen Wahrheitswert und keine Zahl haben.

Statt ``chr(0x0d)`` könnte man das auch direkt in als Zeichenkette mit einer Escape-Sequenz schreiben: ``'\x0d'``.

Dein Code kann so offensichtlich nicht funktionieren weil `s` verwendet wird, bevor ein Wert an den Namen gebunden wurde.

Wenn man in einem ``elif`` genau die entgegengesetze Bedinung wie im ``if`` prüft, dann will man ein ``else`` stattdessen.

Letztlich würde ich aber sagen das Vorgehen ist nicht robust, da es nicht damit klar kommt, wenn der Sender aus irgend welchen Gründen mal nicht so schnell senden sollte wie der Empfänger die Daten verarbeitet und dadurch mitten in der Antwort `inWaiting()` 0 zurück gibt, obwohl die Antwort noch gar nicht komplett ist.

Die gesetzte Zeitschwelle von 10 Sekunden wird nirgends berücksichtigt oder verwendet. Bei `readline()` würde eine entsprechende Ausnahme ausgelöst wenn die Zeile innerhalb von 10 Sekunden nicht komplett empfangen wurde.
dan.mue
User
Beiträge: 6
Registriert: Freitag 26. April 2013, 10:09

Danke für dein Hilfe BlackJack,

wie Anfangs schon beschrieben wird zyklisch ein "set\x0d" Befehl an ein Gerät gesendet. Als Antwort sendet dieser mehrere Zeilen, wobei Anzahl der Zeilen sowie die Länge unbekannt sind. Die Zeilen werden lediglich mit "CR" beendet.

Beispiel:
"Kanal1: Sensor1<CR>Temperatur=35Grad<CR>"
oder
"Kanal1: Sensor1<CR>Temperatur=35Grad<CR>Kanal2: Taster Tuer<CR>Status=geschlossen<CR>

Es gibt aber kein kein Übertragungsende-Zeichen.

Code: Alles auswählen

while True:
    print"SET an BT senden"
    ser.write("set\x0D")
    time.sleep(1)
    while ser.inWaiting()>0:
        s = ser.readline()
        print s
Was mir jetzt nicht gefällt, dass die Ausgabe 1 Leerzeile schreibt. - Liegt wohl an print und <CR> im String
dan.mue
User
Beiträge: 6
Registriert: Freitag 26. April 2013, 10:09

OK

Code: Alles auswählen

pint s,
ist die Lösung
BlackJack

@dan.mue: Das halte ich immer noch für nicht robust, immer noch aus dem selben Grund. Falls die andere Seite der seriellen Kommunikation nicht damit zurecht kommt wenn ein neuer 'set'-Befehl gesendet wird bevor alle Sensordaten empfangen wurden, dann wäre das ganze Protokoll noch ungünstiger. Ich würde nach das schreiben auch einen `flush()`-Aufruf setzen, damit man keine Probleme bekommt, wenn gepuffert wird.
dan.mue
User
Beiträge: 6
Registriert: Freitag 26. April 2013, 10:09

so besser:

Code: Alles auswählen

#!/usr/bin/python

import serial
import time

ser = serial.Serial("/dev/ttyAMA0", 115200, timeout=10)
scanperiode = 10
scantime = time.time()
ser.flushInput()
while True:
    #Alle 10 Sekunden wird der Status abgerufen
    if (time.time()-scanperiode) > scantime:
        print"SET an BT senden"
        ser.write("set\x0D")
        scantime=time.time()
    if ser.inWaiting()>0:
        s = ser.readline()
        #Hier werden zukuenftig alle Empfangenen Antworten und Events ausgewertet
        print s,
Denn das Gerät kann auch Events ohne Anfragen senden, wenn z.B. ein Alarm ausgelöst wird
Zuletzt geändert von Anonymous am Freitag 26. April 2013, 14:10, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
BlackJack

@dan.mue: Wenn das so komplett asynchron ist, würde ich einfach mit dem `threading`-Modul einen zweiten Thread starten und dort hin entweder das schreiben oder das lesen auslagern. Das `flush()`\en meinte ich beim Schreiben des 'set'-Befehls. Sonst sammeln die sich wenn man Pech hat in einem Puffer statt sofort zum seriell angeschlossenen Gerät geschickt zu werden. Ungetestet:

Code: Alles auswählen

import time
from threading import Thread
from serial import Serial


def send_set_commands(serial, interval):
    while True:
        serial.write('set\x0d')
        serial.flush()
        time.sleep(interval)


def main():
    serial = Serial('/dev/ttyAMA0', 115200, timeout=10)
    send_thread = Thread(target=send_set_commands, args=[serial, 10])
    send_thread.start()
    for line in serial:
        print line,


if __name__ == '__main__':
    main()
Falls lesen und schreiben auf der seriellen Schnittstelle nicht „thread safe” sein sollte, muss man etwas mehr tun, um den Zugriff sicher zu machen.
dan.mue
User
Beiträge: 6
Registriert: Freitag 26. April 2013, 10:09

Hallo BlackJack,

danke für deine Hilfe, aber wie kann ich den Thread beenden, nachdem das Programm beendet wurde??
BlackJack

@dan.mue: Wenn man einen „daemon”-Thread daraus macht, dann ended er automatisch wenn der Hauptthread endet:

Code: Alles auswählen

    send_thread.setDaemon(True)
Antworten