RS232/ RS485 Daten kommen nur alle 10 Sekunden korrekt an

Python auf Einplatinencomputer wie Raspberry Pi, Banana Pi / Python für Micro-Controller
Antworten
Tola-Emma
User
Beiträge: 8
Registriert: Freitag 21. Februar 2025, 17:17

Hallo Ihr Lieben,
ich bin total verzweifelt, da ich mit meiner Schnittstelle nicht weiterkomme.
So jetzt zu meinem Projekt:
Ich würde gerne mein Wohnmobilsteuereinheit gegen eine Raspberry Pi tauschen und von dieser alle Daten wie Batterieanzeige, Tankinhalte, Beleuchtung schalten,... übernehmen. Im Netz habe ich hierzu einen Beitrag ( https://github.com/class142/ne-rs485) gefunden. Also ran ans Werk, Raspberry Pi 3+ hatte ich noch und eine RS485/Can Schnittstellenkarte (https://www.waveshare.com/wiki/RS485_CAN_HAT) besorgt. Danach den Python-Code etwas umgeschrieben und ausprobiert. Leider kann ich die Daten aus dem Wohnmobil nur auslesen wenn ich einen Time-Sleep von mindesten 10 Sekunden einbaue, sonst kommt nur der erste Teil des Telegrammes an.

Code: Alles auswählen

import serial
import serial.rs485
import time
import RPi.GPIO as GPIO
import binascii
from checksum_calculator import *

EN_485 = 4
GPIO.setmode(GPIO.BCM)
GPIO.setup(EN_485,GPIO.OUT)
GPIO.output(EN_485,GPIO.HIGH)
GPIO.set_asyncgen_hooks = 0 # 1(alterWert) 

data0 = bytearray (b'\xff\x00\x00\x00\xff')
data1 = bytearray (b'\xff\x40\x00\x00\x3f')
data2 = bytearray (b'\xff\x01\x00\x00\x00')
data3 = bytearray (b'\xff\x02\x00\x00\x01')
data4 = bytearray (b'\xff\x04\x00\x00\x03')
data5 = bytearray (b'\xff\x00\x00\xc0\xff') #init
data6 = bytearray (b'\xff\x40\x00\xc0\xbf') #init
data7 = bytearray (b'\xff\x01\x00\xc0\xc0') #innenlicht
data8 = bytearray (b'\xff\x02\x00\xc0\xc1') #aussenlicht
data9 = bytearray (b'\xff\x04\x00\xc0\xc3') #pumpe

ser = serial.Serial()

def initSerial():
    global ser
    ser.baudrate = 38400
    ser.port = "/dev/ttyS0"
    ser.timeout = 1
    ser.stopbits = serial.STOPBITS_ONE
    ser.bytesize = 8
    ser.parity = serial.PARITY_NONE
    ser.rtscts = True
    ser.xonXoff = 1
    
def main():
    initSerial()
    global ser
    ser.open()
    while True:
        GPIO.output(EN_485, GPIO.HIGH)
        time.sleep(1)
        ser.write(data0)
        time.sleep(0)
        GPIO.output(EN_485, GPIO.LOW)
        str = ser.readline()
        if str:
            print (str)
            byte_Frischwassertank = (str[5:6])
            byte_Grauwassertank1 = (str[6:7])
            byte_Grauwassertank2 = (str[7:8])
            byte_Fahrzeugbatterie = (str[12:13])
            int_Fahrzeugbatterie = int.from_bytes(byte_Fahrzeugbatterie, "big")
            byte_Bordbatterie = (str[13:14])
            int_Bordbatterie = int.from_bytes(byte_Bordbatterie, "big")
            byte_Anzeige1 = (str[15:16])
            byte_Anzeige2 = (str[16:17])
            byte_checksummme_Empfang = (str[5:20])
            #print(( 'checksumme      :'),(is_checksum8_mod256(byte_checksummme_Empfang.hex())))
            checksummeOK = (is_checksum8_mod256(byte_checksummme_Empfang.hex()))
            if checksummeOK:
                #Berechnung Füllstand Frischwassertank
                if byte_Frischwassertank == (b'\x00'):
                    print('Frischwassertank : Leer')
                elif byte_Frischwassertank == (b'\x01'):
                    print('Frischwassertank : 1/3 voll')
                elif byte_Frischwassertank == (b'\x02'):
                    print('Frischwassertank : 2/3 voll')
                elif byte_Frischwassertank == (b'\x07'):
                    print('Frischwassertank : voll')
                else:
                     print('Frischwassertank : Fehler')
                #Berechnung Füllstand Grauwassertankwassertank
                if byte_Grauwassertank1 == (b'\x20'):
                    print('Grauwassertank   : Leer')
                elif byte_Grauwassertank1 == (b'\x21'):
                    print('Grauwassertank   : 1/3 voll')
                elif byte_Grauwassertank1 == (b'\x22'):
                    print('Grauwassertank   : 2/3 voll')
                elif byte_Grauwassertank1 == (b'\x27'):
                    print('Grauwassertank   : voll')
                else:
                     print('Grauwassertank  : Fehler')
                #Berechung Batterieanzeige Bordbatterie
                print(('Boardbatterie    :'),round(((int_Bordbatterie)/12.2), 1),('Vdc'))
                #Berechung Batterieanzeige BFahrzeugbatterie
                print(('Fahrzeugbatterie :'),round(((int_Fahrzeugbatterie)/12.2), 1),('Vdc'))
                #Berechnung Innenlicht
                if byte_Anzeige1 == (b'\x01') or byte_Anzeige1 == (b'\x03') or byte_Anzeige1 == (b'\x05') or byte_Anzeige1 == (b'\x07'):
                    print('Innenlicht       : An')
                else:
                    print('Innenlicht       : Aus')
                #Berechnung Aussenlicht
                if byte_Anzeige1 == (b'\x02') or byte_Anzeige1 == (b'\x03') or byte_Anzeige1 == (b'\x06') or byte_Anzeige1 == (b'\x07'):
                    print('Aussenlicht      : An')
                else:
                    print('Aussenlicht      : Aus')
                #Berechnung Wasserpumpe
                if byte_Anzeige1 == (b'\x04') or byte_Anzeige1 == (b'\x05') or byte_Anzeige1 == (b'\x06') or byte_Anzeige1 == (b'\x07'):
                    print('Wasserpumpe      : An')
                else:
                    print('Wasserpumpe      : Aus')
                #Berechnung Statusanzeigebit 1
                if byte_Anzeige2 == (b'\x01') or byte_Anzeige2 == (b'\x03') or byte_Anzeige2 == (b'\x05') or byte_Anzeige2 == (b'\x07'):
                    print('Statusbit1       : An')
                else:
                    print('Statusbit1       : Aus')
                #Berechnung Statusanzeigebit 2
                if byte_Anzeige2 == (b'\x02') or byte_Anzeige2 == (b'\x03') or byte_Anzeige2 == (b'\x06') or byte_Anzeige2 == (b'\x07'):
                    print('Statusbit2       : An')
                else:
                    print('Statusbit2       : Aus')
                #Berechnung Sicherung defekt
                if byte_Anzeige2 == (b'\x04') or byte_Anzeige2 == (b'\x05') or byte_Anzeige2 == (b'\x06') or byte_Anzeige2 == (b'\x07'):
                    print('Sicherungen      : Defekt')
                else:
                    print('Sicherungen      : OK')
            print(( 'checksumme       :'),(is_checksum8_mod256(byte_checksummme_Empfang.hex())))

        time.sleep(0.3)

if __name__ == "__main__":
    main() 

Und die Anwort vom Wohnmobil kommt:

Code: Alles auswählen

b'\xff\xff\xff\x01\xff\x00 \x00\x10\x00\x00\x00\x00\\\xff\x10\x00\x00\x00\x9b'
Frischwassertank : Leer
Grauwassertank   : Leer
Boardbatterie    : 7.5 Vdc
Fahrzeugbatterie : 0.0 Vdc
Innenlicht       : Aus
Aussenlicht      : Aus
Wasserpumpe      : Aus
Statusbit1       : Aus
Statusbit2       : Aus
Sicherungen      : OK
checksumme       : True
Wenn ich die time.sleep auf 1 Sekunde ändere kommt folgende Anwort:

Code: Alles auswählen

b'\xff\xff\xff\x01\xff'
checksumme       : False
b'\xff\xff\xff\x01\xff'
checksumme       : False
b'\xff\xff\xff\x01\xff'
checksumme       : False
b'\xff\xff\xff\x01\xff'
checksumme       : False
b'\xff\xff\xff\x01\xff'
checksumme       : False
b'\xff\xff\xff\x01\xff'
checksumme       : False
b'\xff\xff\xff\x01\xff'
checksumme       : False

Ich hoffe ihr habt eine Lösung für mich.

Lieben Gruß
Tola-Emma
_____________________________________________________________________________________________________
Benutzeravatar
__blackjack__
User
Beiträge: 13997
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Tola-Emma: Anmerkungen zum Quelltext: `binascii` und `serial.rs485` werden importiert, aber nirgends verwendet.

Sternchen-Importe sind Böse™. Das macht Programme unnötig unübersichtlicher und fehleranfälliger und es besteht die Gefahr von Namenskollisionen.

``as`` beim Import ist zum umbenennen da, `GPIO` wird aber gar nicht umbenannt.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. GPIO-Konfiguration und `ser` haben da nichts zu suchen. ``global`` hat in einem sauberen Programm nichts zu suchen. Funktionen (und Methoden) bekommen alles was sie ausser Konstanten benötigen, als Argument(e) übergeben.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (PascalCase). Man nummeriert keine Namen. Dann will man entweder sinnvollere Namen, oder gar keine EInzelnamen/Werte sondern eine Datenstruktur. Oft eine Liste. Bei den nummerierten `data`-Werten aber sinnvolle Namen. Wobei nur `data0` überhaupt benutzt wird. Es macht auch nicht wirklich Sinn, dass es ein `bytearray` ist, denn es wird nicht verändert. Was es als Konstante auch gar nicht sollte.

Was soll denn diese Zuweisung an `GPIO.set_asyncgen_hooks`? Kann es sein dass da eine ”K.I.” ein bisschen freigedreht hat‽

Wenn man GPIOs verwendet, sollte man die am Programmende mit `GPIO.cleanup()` auch wieder ordentlich aufräumen.

`initSerial()` und `open()` zu trennen macht hier keinen Sinn, man könnte hier einfach ein initialisiertes und geöffnetes `Serial`-Objekt erstellen. Zudem sollte man mit ``with`` sicherstellen, dass das auch sauber wieder geschlossen wird. ``ser.xonXoff = 1`` hat keinen Effekt, weil es das Attribut so nicht gibt. Das müsste komplett kleingeschrieben werden. `timeout` macht eigentlich nur Sinn wenn der Code danach auch wirklich damit umgehen kann, da ist aber gar keine Behandlung für diesen Fall.

Beim schreiben würde ich sicherstellen, dass alle Daten auch wirklich geschrieben wurden und nicht noch etwas in einem Puffer zwischengespeichert ist.

`readline()` ist ziemlich sicher falsch wenn da Binärdaten kommen. Ein Zeilenende kann ja auch mitten *in* einer Nachricht vorkommen. `str` ist nicht nur der Name einer eingebauten Funktion, sondern inhaltlich auch sehr irreführend für einen Wert der gar keine Zeichenkette, sondern ein `bytes`-Objekt ist.

In dem Code sind viel zu viele unsinnige Klammern um Einzelwerte.

Die Prüfsumme wird als erstes geprüft, *bevor* man anfängt die einzelnen Werte aus der Nachricht zu holen.

`int.from_bytes()` wird immer nur auf *einzelnen Bytes* aufgerufen, das macht überhaupt keinen Sinn. Und auch die ganzen `byte_*`-Werte machen als `bytes`-Objekte mit immer genau einer Länge von 1 keinen Sinn.

`byte_Grauwassertank2` wird nirgends verwendet.

Bei den Wasserstandsanzeigen wiederholt sich Code den man in eine Funktion heraus ziehen sollte.

`round()` ist dazu da, wenn man mit gerundeten Werten weiterrechnen will. Für die Anzeige benutzt man entsprechende Formatangaben beim umwandeln/einsetzen in eine Zeichenkette.

Wenn man auf einzelne Bits testen möchte testet man auf einzelne Bits und vergleicht nicht mit allen möglichen Werte in denen das Bit gesetzt ist.

`is_checksum8_mod256()` sollte ein `bytes`-Objekt als Argument nehmen und keine Zeichenkette, denn da wird ein Bytes-Objekt in eine Zeichenkette umgewandelt, die ja in der Funktion gleich wieder in ein `bytes`-Objekt umgewandelt wird. Das macht keinen Sinn.

Zwischenstand (ungetestet):

Code: Alles auswählen

import time

import serial
from checksum_calculator import is_checksum8_mod256
from RPi import GPIO


EN_485 = 4
REQUEST = b"\xff\x00\x00\x00\xff"


def hole_text_zu_fuellstandswert(roh_werte, wert):
    for value, text in zip(
        roh_werte, ["leer", "1/3 voll", "2/3 voll", "voll"]
    ):
        if wert == value:
            return text

    return "Fehler"


def main():
    try:
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(EN_485, GPIO.OUT, initial=GPIO.HIGH)

        with serial.Serial("/dev/ttyS0", 38400, rtscts=True) as connection:
            while True:
                GPIO.output(EN_485, GPIO.HIGH)
                time.sleep(1)
                connection.write(REQUEST)
                connection.flush()
                time.sleep(0)
                GPIO.output(EN_485, GPIO.LOW)
                data = connection.read(20)
                #
                # TODO Die ersten vier Bytes werden komplett ignoriert. Sollte
                #      man da nicht eventuell was prüfen weil die einen
                #      bestimmten Wert haben müssen?
                #
                if is_checksum8_mod256(data[5:20]):
                    print(data)
                    (
                        frischwassertank_fuellstand,
                        grauwassertank_a_fuellstand,
                    ) = data[5:7]

                    fahrzeugbatteriespannung, bordbatteriespannung = data[
                        12:14
                    ]
                    anzeige_a, anzeige_b = data[15:17]

                    print(
                        "Frischwassertank :",
                        hole_text_zu_fuellstandswert(
                            [0x00, 0x01, 0x02, 0x07],
                            frischwassertank_fuellstand,
                        ),
                    )
                    print(
                        "Grauwassertank :",
                        hole_text_zu_fuellstandswert(
                            [0x20, 0x21, 0x22, 0x27],
                            grauwassertank_a_fuellstand,
                        ),
                    )

                    print(
                        f"Boardbatterie    :"
                        f" {bordbatteriespannung / 12.2:.1f} Vdc"
                    )
                    print(
                        f"Fahrzeugbatterie :"
                        f" {fahrzeugbatteriespannung / 12.2:.1f} Vdc"
                    )

                    for i, text in enumerate(
                        ["Innenlicht", "Aussenlicht", "Wasserpumpe"]
                    ):
                        print(
                            f"{text:<17}:"
                            f" {'An' if anzeige_a & (1 << i) else 'Aus'}"
                        )

                    for i, text in enumerate(["Statusbit1", "Statusbit2"]):
                        print(
                            f"{text:<17}:"
                            f" {'An' if anzeige_b & (1 << i) else 'Aus'}"
                        )

                    text = "Sicherungen"
                    print(
                        f"{text:<17}:"
                        f" {'Defekt' if anzeige_b & (1 << 2) else 'OK'}"
                    )

                time.sleep(0.3)

    finally:
        GPIO.cleanup()


if __name__ == "__main__":
    main()
“The best book on programming for the layman is »Alice in Wonderland«; but that's because it's the best book on anything for the layman.” — Alan J. Perlis
Tola-Emma
User
Beiträge: 8
Registriert: Freitag 21. Februar 2025, 17:17

Wow!!!
das nenne ich mal Hilfe!!!
Dafür schon mal ein großes Lob.
Ich habe mir die Programmteile zusammengesucht und durch testen dieses zum laufen bekommen. In der Materie der Python-Programmierung bin ich noch ganz am Anfang.
Die '#' habe ich benutzt um nicht immer wieder alles neu zu schreiben.
Data0 - Data9 sind die verschiedenen Anforderungen ( Licht an/aus, ...)die ich nur zum testen beötigt habe.
Byte Grauwasser ist nur als Option mit im Antwortstring enthalten und dashalb mit aufgenommen.

Nun habe ich aber noch eine Frage.
Wie frage ich einzelne Bit ab, wie z.B. bei der Anzeige der Pumpe

Code: Alles auswählen

b'\xff\x00\x00\x00\xff\x00 \x00\x10\x00\x00\x00\x00\x8e\xff\x04\x00\x00\x00\xc1'
Frischwassertank : Leer
Grauwassertank   : Leer
Boardbatterie    : 11.6 Vdc
Fahrzeugbatterie : 0.0 Vdc
Innenlicht       : Aus
Aussenlicht      : Aus
Wasserpumpe      : An
Statusbit1       : Aus
Statusbit2       : Aus
Sicherungen      : OK
checksumme       : True
Im String der Wert '\04' verändert den Zustand auf Wasserpumpe : An.
Steht allerdings der Wert '\14' dort geht der Zustand der Wasserpumpe auf :Aus, obwohl das 3.Bit (4) immer noch auf True steht.

Code: Alles auswählen

b'\xff\x00\x00\x00\xff\x00 \x00\x10\x00\x00\x00\x00\x8e\xff\x14\x00\x00\x00\xd1'
Frischwassertank : Leer
Grauwassertank   : Leer
Boardbatterie    : 11.6 Vdc
Fahrzeugbatterie : 0.0 Vdc
Innenlicht       : Aus
Aussenlicht      : Aus
Wasserpumpe      : Aus
Statusbit1       : Aus
Statusbit2       : Aus
Sicherungen      : OK
checksumme       : True
Mein ursprüngliches Problem mit den Daten, kommt sehr wahrscheinlich von der Spannung auf der A/B Leitung vom RS485 BUS.
Bist du dort auch so ein Experte

Lieben Gruß
Tola-Emma
Antworten