Lesen vom ttyAMA0 mit pyserial

  • Hallo liebe Forum,

    ich habe einem HAT (kBerry) für die Raspi zum verbinden mit einem Gebäudeautomatisierungsbus (KNX).

    Ich kann nun mit meinem Programm (in C++ geschrieben) in den Bus und irgendwas in der Anlage steuern (unwichtig für meine Problematik).

    Nun will ich auch lesen und dafür wollte ich Python benutzten.

    Die benötigte Einstellungen für die ttyAMA0 (raspi 3b+) sind folgende:

    Code
    $ stty -F /dev/ttyAMA0 19200 cs8 parenb -parodd -icrnl -opost -isig -icanon -echo

    Was in den Bus passiert kann ich ganz zuverlässig damit sehen:

    Code
    $ sudo socat -x -u /dev/ttyAMA0,raw,echo=0,crnl PTY,link=/dev/ttyV1,raw,echo=1,crnl

    Nun möchte ich aber die gleiche output aber mit python lesen und die Daten verarbeiten.

    In Fragen kamen das Modul 'Subprocess' und damit socat ausführen oder pyserial zu benutzen.

    Da ich denke das sauberste wäre das zweite habe ich das geschrieben:

    Das gibt mir das zurück:

    aber ich erwarte was socat mich liefert:

    Und damit meine ich die raw werte bzw. Hex bytes (Zeitstempel usw. spielen nun keine Rolle).

    Ich erwarte ein Dataframe, die 0x68 als startbyte hat und 0x16 als stopbyte (wie bei socat output).

    Pyserial liest das erste Byte richtig (0x68) aber danach stimmt das Frame nicht mehr (0x0C 0x0C 0x68 ..... 0x16 sollte folgen).


    Kann mir jemand helfen?

    Sollte ich in die Argumente von Serial() noch was schreiben, damit analog zu meine tty Einstellungen ist?


    Vielen Dank in voraus.


    LG,


    Simón


    P.S. Dass, das pyserial output 1 byte pro linie ausgibt und socat Zeilenweise, spielt erstmal keine Rolle.

    Grüße aus Berlin,


    Golpe





  • Hallo

    ich muss jetzt mal fragen, sind das immer bestimmte Pakete, die eindeutig durch eine "Begin" und "End" Zeichen eindeutig zu identifizieren sind ?

    Du kannst mit .Readline in der Class GPIOIO bestimmen, wann eine Folgeaufzeichnung startet und wann diese beendet ist.

    Je nach Version würde ich einen Object-inspector einsetzen um das Problem einzugrenzen.

    Gesunde Grüße

    Andy

  • In welche modul bzw. Python Paket ist diese Klasse gpioio?

    Ja, die Pakete folgen die Muster `0x68 .......frame_lange_variiert.......0x16`.

    Zwischen startbyte 68 und stopbyte 16 will ich später ein paar bytes rausfiltern, die mir eine Zieladresse und einen Wert (payload) geben.

    Das Ziel des ganzen ist das Speichern und evtll. weiterleiten des letzten Status der verschiedene Adressen in der Busanlage.


    Danke! :)


    LG,


    Simón

    Grüße aus Berlin,


    Golpe





  • Golpe Du hast da was mit der Reihenfolge verwechselt. Statt die Verbindung aufzubauen und dann in einer Schleife Bytes zu lesen, hast Du eine Schleife die für jedes Byte die Verbindung aufbaut, ein Byte liest, und die Verbindung wieder schliesst. Würde mich nicht wundern wenn dabei Daten verloren gehen.

    “I will not sell my kidney on eBay

    I will not sell my kidney on eBay

    I will not sell my kidney on eBay …” — Bart Simpson

  • Hallo


    die Basis Class ist je nach dem ;)
    Wenn du GPIOZero <= 1.5.1 noch in der alten RPI.GPIO zu finden , in den neueren Versionen findest du das jedoch über OS / Serial ( nicht Pyserial ) und dann PyGPIO und PIn Factory


    Gesunde Grüße

    Andy

  • Hi
    Das wäre nicht konform mit den Daten Paketen !
    Egal ob er das mit einer Schleife und CPU last aufbaut, oder ob er sammelt und dann nach seinen Start und End Zeichen filtert !?

    Gut du hast recht, dass er keine Liste hat, wo alles gesammelt wird bis das "End" Zeichen kommt.

    Nur ist es bequemer die .readline zu manipulieren ( vererbt ) als sich jetzt hier über Class und Methode zu unterhalten !


    Golpe Du hast da was mit der Reihenfolge verwechselt. Statt die Verbindung aufzubauen und dann in einer Schleife Bytes zu lesen, hast Du eine Schleife die für jedes Byte die Verbindung aufbaut, ein Byte liest, und die Verbindung wieder schliesst. Würde mich nicht wundern wenn dabei Daten verloren gehen.

  • Hallo,


    Andy ich will dir nicht zu nahe treten, aber ich glaube du solltest die Beiträge von __blackjack__ nochmals lesen.


    __blackjack__ kann man solche Verbindungen wie folgt auslesen?


    Code
    with serial.Serial(DEVICE, BAUDRATE, serial.EIGHTBITS, serial.PARITY_EVEN) as connection:
        for byte in connection.read():
            print(hex_decode(byte))


    Ich habe leider keine Möglichkeit/Ahnung, wie/ob ich das für mich selbst zum testen nachstellen könnte.


    Danke und Grüße

    Dennis

    ... ob's hinterm Horizont wirklich so weit runter geht oder ob die Welt vielleicht doch gar keine Scheibe ist?

  • Herr Eulenspiegel


    With ist nicht in und mit der Class Serial vererbbar !

    Denn dann muss man etwas mehr machen !

    Dazu muss man lesen "Ich habe leider keine Möglichkeit/Ahnung, wie/ob ich das für mich selbst zum testen nachstellen könnte."

  • Dennis89 Aus einer Funktion oder Methode die bei jedem Aufruf ein weiteres Element liefert, kann man mit `iter()` einen Iterator erstellen und den dann mit ``for`` verwenden:

    Python
        with serial.Serial(
            DEVICE, BAUDRATE, serial.EIGHTBITS, serial.PARITY_EVEN
        ) as connection:
            for byte in iter(connection.read, b""):
                print(hex_decode(byte))

    “I will not sell my kidney on eBay

    I will not sell my kidney on eBay

    I will not sell my kidney on eBay …” — Bart Simpson

  • Hallo ihr alle,

    hier den code, falls ihr es teilweise gebrauchen konnt:

    Grüße aus Berlin,


    Golpe





  • With ist nicht in und mit der Class Serial vererbbar !

    Doch, ist es:


    Du musst nur eine aktuelle Version verwenden.

    Code
    pip3 install pyserial --upgrade


    Es gibt einige Bibliotheken, die sich über die Jahre geändert haben.

  • Hallo.


    Golpe deine Klasse ist nur da um Konstanten zu definieren? Dann könntest du die doch weglassen und die Konstanten auf der Modulebene definieren oder übersehe ich etwas?


    DeaD_EyE bemüh dich nicht all zu sehr, der User Andy wurde aus gutem Grund gesperrt. Aber dennoch Danke für die Richtigstellung :)


    Grüße

    Dennis

    ... ob's hinterm Horizont wirklich so weit runter geht oder ob die Welt vielleicht doch gar keine Scheibe ist?

  • Golpe: Neben der Anmerkung zu der Klasse die keine ist:


    `init_dataframe()` macht hier nicht wirklich Sinn. Zumal Wörterbücher die immer einen festen Satz von Schlüsseln haben, in der Regel ein falscher Ansatz sind. Das ist üblich bei (de)serialisieren von Daten, aber innerhalb von einem Programm ist so etwas eigentlich fast immer eine Klasse.


    Die `hex_to_string()`-Funktion ist überflüssig. `Serial.read()` ohne Argument liefert *ein* Byte als `bytes`-Objekt, und die haben eine `hex()`-Methode:

    Python
    In [1]: bytes([255]).hex()                                                      
    Out[1]: 'ff'
    
    In [2]: bytes([22]).hex()                                                       
    Out[2]: '16'

    `FIRST_BYTE` sollte besser `FIRST_BYTE_INDEX` heissen, weil die anderen *BYTE-Konstanten ja den Wert und nicht den Index meinen. Wobei ich so eine Konstante für den Index 0 an dem der erste Wert steht, ein bisschen komisch finde. So wirklich trägt das nicht zur Lesbarkeit/zum Verständnis bei.


    ``while``-Schleifen bei denen man für den Schleifentest vorher Dummywerte definieren muss, sind eigentlich ``while True:``-Schleifen wo die Bedingung *in* der Schleife stehen sollte und dann dort mit einem ``break`` verlassen wird.


    Die Logik und das da dann auch noch ein Teil in `check_startbyte()` ausgelagert wird, was nicht nur etwas prüft, sondern auch noch `frame` ändern kann, ist total schräg. Statt da über zwei Funktionen verteilt immer Werte in `frame` zu schreiben die man da gar nicht haben will und dann wieder raus zu löschen, sollte man die überhaupt gar nicht erst in `frame` schreiben. Und der Test ist auch nicht auf den aktuellen Wert, ob man den speichern möchte, sondern auf den vorherigen Wert ob man den nach dem man ihn gespeichert hat, wieder raus löschen will. Und der Test wird für jedes Byte *im* Frame gemacht, obwohl man da eigentlich schon weiss, das am Anfang *nicht* der Startwert steht.


    Wenn man das einem Menschen beschreiben würde, dann wäre das einfachste ja: Lese solange bis der Startwert gefunden wurde, dann lese solange und speichere in `frame` bis der Endwert gefunden wurde. Das sollte sich am Code auch genau so ablesen lassen.


    Nächster Punkt ist die repräsentation von Bytes. Warum als Zeichenketten? Python hat Bytes als eigenen Datentyp, einmal `bytes` für unveränderbare Byteketten, und `bytearray` wenn man die Werte verändern oder erweitern möchte.


    Zwischenstand:

    Allerdings kann man sich die beiden ``while``-Schleifen sparen, weil `Serial`-Objekte eine `read_until()`-Methode haben, womit die Hauptschleife zu drei Zeilen zusammenschrumpft:

    Python
    def main():
        with serial.Serial(
            SERIAL_DEVICE, SERIAL_BAUDRATE, SERIAL_CHARACTER_SIZE, SERIAL_PARITY
        ) as connection:
            while True:
                connection.read_until(FRAME_START_BYTE)
                frame = connection.read_until(FRAME_STOP_BYTE)
                print(frame)

    “I will not sell my kidney on eBay

    I will not sell my kidney on eBay

    I will not sell my kidney on eBay …” — Bart Simpson

  • __blackjack__ , das mit der python Bytes hatte ich schon probiert.

    Wenn ich dein code ausführe bekomme ich so eine Ausgabe:

    da fehlt mir den Startbyte und kommen auch andere werte, die ich nicht erwarte wie ')' oder '0ch'

    Grüße aus Berlin,


    Golpe





  • Golpe Wenn das Startbyte da drin sein soll, dann starte halt nicht mit einem leeren Bytearray sondern mit einem wo das Startbyte bereits drin steht.


    Du bekommst da keine anderen Werte sondern die Zeichenkettendarstellung von `bytes`-Objekten. Bytewerte im ASCII-Bereich werden als ASCII dargestellt. Alles andere als \x?? mit ?? als Hexadezimalziffern. Das 0ch ist also eigentlich \x0ch und das sind zwei Bytes: \x0c und h, also der Bytewert 104 (68₁₆) = ASCII h und der ASCII-Wert von ) ist 41 (29₁₆):

    “I will not sell my kidney on eBay

    I will not sell my kidney on eBay

    I will not sell my kidney on eBay …” — Bart Simpson