Auslesen eines Elektronischen Stromzählers mit einem Raspi

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
Sporty1200
User
Beiträge: 4
Registriert: Sonntag 25. August 2013, 13:51

Hallo zusammen

ich bin neuling in der python Programmierung und interesiere mich sehr dafür besser zu werden
und hoffe ihr könnt mir etwas auf die sprünge helfen

ich besitze 2 elektronische Stromzähler mit optischer schnittstelle
das gesendete Protokoll entspricht der SML (smart meter language).
Gesendet wird der datensatz vom Zähler ca. alle 3 sec.

auslesen möchte ich es mit einem Raspberry PI Modell B
mit einem W&T rs232 auf USB wandler

ich hab folgende Probleme:

mit einem Terminal Programm (Hterm) auf meinem Laptop hab ich den datensatz schon gelesen es sind 396 zeichen
in hex kodiert (0-255)
mit einem kleinen python code hab ich versuch den zähler auszulesen dabei wandelt es direct jedes zeichen was unter 128 ist automatisch in einen Ascii zeichensatz um und "inWaiting" liefert nur 321 zeichen unabhängig ob über Raspi oder PC :K

und falls einer einen tip hat wie ich aus einem teilbereich aus einem byte array wieder einen zähler wert machen kann wäre ich sehr dankbar.

Ich hab in büchern und internet kaum hilfreiches gefunden zu diesem problem.

ich hoffe ich hab es einigermassen darstellen können.

anbei mein testcode der auch nur bedingt funtioniert

Code: Alles auswählen

import serial
import time
import sys ,os

try :
    ser = serial.Serial ("/dev/ttyUSB0",9600,timeout=10000)     # open first serial port
    print (ser.portstr)         # check which port was really used
    ser.setDTR = 1
#    ser.setRTS = 1

except :
    print ("gibt es nicht")

byte_wartend = 0

while True:

    S = bytes
    
    while ser.inWaiting():

        if ser.inWaiting() == byte_wartend & ser.inWaiting()!= 0:
            print (ser.inWaiting())
            #S = ser.readline()
            print(ser.readline())
            byte_wartend = 0
           
    
        else:
            byte_wartend = ser.inWaiting()
            time.sleep(0.5)
    time.sleep(0.05)
            
BlackJack

@Sporty1200: Python beziehungsweise das `serial`-Modul konvertieren nichts automatisch von Hexadezimal nach Binär, das macht wohl eher Dein Terminalprogramm.

Wenn das tatsächlich beliebige Binärdaten sein können, kannst Du auch nicht `readline()` verwenden, denn das schaut nach einer Zeilenende-Kennzeichnung, die nur bei Textdaten Sinn macht und in Binärdaten im Grunde überall zufällig vorkommen kann wo eben zufällig ein entsprechender Bytewert steht.

Also muss erst einmal geklärt werden was da tatsächlich gesendet wird, Binärdaten oder Text.

Der Leseteil ist übrigens eigenartig. Das innere ``if`` (mit der bitweisen Und-Verknüpfung) ist ganz fürchterlich verwirrend. Für logische Verknüfungen gibt es das ``and``-Schlüsselwort und man muss auch mitbedenken, dass `ser.inWaiting()` in diesem Ausdruck zweimal aufgerufen wird, dabei aber jedes mal ein anderes Ergebnis liefern kann. Und beim `print()` in dem ``if``-Zweig könnte es schon wieder ein anderes Ergebnis liefern, also ist das nur bedingt in der Form sinnvoll.

`S` entspricht nicht der Namenskonvention aus dem Style Guide for Python Code und warum die `bytes()`-Funktion daran gebunden wird, erschliesst sich mir auch nicht wirklich.

Edit: Das Protokoll heisst übrigens Smart *Message* Language. Du hast Dir also vorgenommen ein Binärformat mit verschiedenen Nachrichtentypen auseinander zu nehmen. Plane dafür ein wenig Zeit ein, das ist für den Anfang nämlich nicht das leichteste. Vor allem wenn man mit einer High-Level-Programmiersprache arbeitet, wo man diese Ebene nicht (mehr) braucht um die Programmiersprache zu verstehen.
Sporty1200
User
Beiträge: 4
Registriert: Sonntag 25. August 2013, 13:51

Das Protokoll heisst übrigens Smart *Message* Language.
jap du Hast recht. und danke schonmal für die schnelle Rückmeldung.

der code ist mist! war auch nur gebastel weil ich seit 2 wochen nicht weiterkomme
(googlen, foren und bücherkaufen hatte nur bedingt geholfen).
der code hatte auch nicht den anspruch so später eingesetzt zu werden.
das mit der ascii ausgabe hatte wohl was mit der ausgabe beim "Print"befehl aufgrunde der ausgabe mit b'\xx\xx...' zutuhen

Hier mal die ausgabe an meinem Laptop mit Hterm:

1B 1B 1B 1B 01 01 01 01 76 05 02 BF BE C8 62 00 62 00 72 63 01 01 76 01 01 05 00 EA 94 EE 09 08 05 35 34 2D 4C 92 AD 01 01 63 5A DE 00 76 05 02 BF BE C9 62 00 62 00 72 63 07 01 77 01 09 08 05 35 34 2D 4C 92 AD 07 01 00 62 0A FF FF 72 62 01 65 02 80 C7 0E 7B 77 07 81 81 C7 82 03 FF 01 01 01 01 04 49 53 4B 01 77 07 01 00 00 00 09 FF 01 01 01 01 09 08 05 35 34 2D 4C 92 AD 01 77 07 01 00 01 08 00 FF 65 00 00 01 82 01 62 1E 52 FF 59 00 00 00 00 01 9E BA 41 01 77 07 01 00 01 08 01 FF 01 01 62 1E 52 FF 59 00 00 00 00 01 9E BA 41 01 77 07 01 00 01 08 02 FF 01 01 62 1E 52 FF 59 00 00 00 00 00 00 00 00 01 77 07 01 00 0F 07 00 FF 01 01 62 1B 52 00 65 00 00 00 8C 01 77 07 01 00 15 07 00 FF 01 01 62 1B 52 00 65 00 00 00 01 01 77 07 01 00 29 07 00 FF 01 01 62 1B 52 00 65 00 00 00 89 01 77 07 01 00 3D 07 00 FF 01 01 62 1B 52 00 65 00 00 00 01 01 77 07 01 00 60 05 05 FF 01 01 01 01 65 00 00 01 82 01 77 07 81 81 C7 82 05 FF 01 01 01 01 83 02 2A 10 19 95 CE 5A 87 1E 0C C6 70 20 E4 97 B5 6B 3C A6 8D 4C 7B A1 80 16 DE 8C 6E E0 96 49 AB 4C DF 81 89 F6 B1 AD 2E 29 F0 A4 C6 D2 D4 6C CC 5B 01 01 01 63 2E 37 00 76 05 02 BF BE CA 62 00 62 00 72 63 02 01 71 01 63 29 2C 00 1B 1B 1B 1B 1A 00 19 42

Startsequenz 1B 1B 1B 1B 01 01 01 01 (quelle Wikipedia)
Endsequenz 1B 1B 1B 1B 1A <xx> <yy> <zz>
<xx> : Anzahl der Füllbytes
<yy> <zz> : CRC über die Date
i


Hier die ausgabe mit meinem Python code
321
b'\xff\xffrb\x01e\x02\xfd\xd1\xda{w\x07\x81\x81\xc7\x82\x03\xff\x01\x01\x01\x01\x04ISK\x01w\x07\x01\x00\x00\x00\t\xff\x01\x01\x01\x01\t\x08\x0554-L\x92\xad\x01w\x07\x01\x00\x01\x08\x00\xffe\x00\x00\x01\x82\x01b\x1eR\xffY\x00\x00\x00\x00\x01\xdc(\xf6\x01w\x07\x01\x00\x01\x08\x01\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x01\xdc(\xf6\x01w\x07\x01\x00\x01\x08\x02\xff\x01\x01b\x1eR\xffY\x00\x00\x00\x00\x00\x00\x00\x00\x01w\x07\x01\x00\x0f\x07\x00\xff\x01\x01b\x1bR\x00e\x00\x00\x00\x90\x01w\x07\x01\x00\x15\x07\x00\xff\x01\x01b\x1bR\x00e\x00\x00\x00\x04\x01w\x07\x01\x00)\x07\x00\xff\x01\x01b\x1bR\x00e\x00\x00\x00\x89\x01w\x07\x01\x00=\x07\x00\xff\x01\x01b\x1bR\x00e\x00\x00\x00\x01\x01w\x07\x01\x00`\x05\x05\xff\x01\x01\x01\x01e\x00\x00\x01\x82\x01w\x07\x81\x81\xc7\x82\x05\xff\x01\x01\x01\x01\x83\x02*\x10\x19\x95\xceZ\x87\x1e\x0c\xc6p \xe4\x97\xb5k<\xa6\x8dL{\xa1\x80\x16\xde\x8cn\xe0\x96I\xabL\xdf\x81\x89\xf6\xb1\xad.)\xf0\xa4\xc6\xd2\xd4l\xcc[\x01\x01\x01c\xad\x1e\x00v\x05\x03@@\xf7b\x00b\x00rc\x02\x01q\x01c#\xc3\x00\x1b\x1b\x1b\x1b\x1a\x00\x03E\x1b\x1b\x1b\x1b\x01\x01\x01\x01v\x05\x03@@\xf8b\x00b\x00rc\x01\x01v\x01\x01\x05\x01\x15j\xfe\t\x08\x0554-L\x92\xad\x01\x01c\xa4\xc5\x00v\x05\x03@@\xf9b\x00b\x00rc\x07\x01w\x01\t\x08\x0554-L\x92\xad\x07\x01\x00b\n'


falls einer ne idee hat für hilfe wäre ich echt dankbar
danke schon mal
BlackJack

@Sporty1200: Auf der Wikipediaseite ist die SML-Spezifikation als PDF verlinkt. Ich habe die mal kurz überflogen. Du müsstest das was da drin steht sozusagen von „hinten” implementieren. Also zuerst das Protokoll. Hoffentlich das in der Version 1, denn das ist einfacher. Ansonsten muss man ein paar Escape-Sequenzen mehr interpretieren.

Einen grundlegenden Fehler bei Deinem Code habe ich Dir ja schon genannt: das `readline()`. Dein Ergebnis hört mit einem b'\n' auf, also einem Zeilenende-Zeichen. Das ist aber nicht das Ende des SML-Files, sondern irgendwo mitten drin. Da es sich um Binärdaten handelt, kann dieser Bytewert ja zum Beispiel auch als Teil einer binär kodierten Zahl vorkommen.

Du musst Code zum Empfangen von Daten schreiben, der das als Datenstrom auffasst und die Escape-Sequenzen interpretieren kann, um den Anfang und das Ende eines SML-Files erkennen zu können und alle Bytes eines solchen Files liefern kann, damit man die im nächsten Arbeitsschritt dann parsen kann. Dazu würde ich Funktionen zum parsen der ganzen Einzelteile schreiben, so dass man am Ende eine Funktion hat, welche ein komplettes SML-File in eine allgemeine Datenstruktur parsen kann. Typen und Längen sowohl für einfache als auch für zusammengesetzte Datentypen sind in dem Datenformat kodiert. Das ist also zumindest was Struktur und Typen betrifft erst einmal selbstbeschreibend.
Sporty1200
User
Beiträge: 4
Registriert: Sonntag 25. August 2013, 13:51

@BlackJack
Danke erstmal hab jetzt was gefunden im Bezug auf Datenstrom in der Unterscheidung von Text und binär. Da hab ich jetzt erstmal einen Ansatz mit

Code: Alles auswählen

modul io
Werde die Tage ausprobieren ob ich da was hinkriege

Ich danke dir
BlackJack

@Sporty1200: Ich sehe nicht wo man da das `io`-Modul braucht. Du bekommst von `Serial` Binärdaten, und das ist ja auch richtig so, denn es handelt sich nicht um ein Textprotokoll. Man könnte einen `io.TextIOWrapper()` verwenden wenn man ein Binärdatei-Objekt hat das kodierten Text „spricht”, aber die Zeichenketten im SML-File sind in Binärdaten eingebettet und können zudem noch in verschiedenen Kodierungen vorliegen.
Sporty1200
User
Beiträge: 4
Registriert: Sonntag 25. August 2013, 13:51

hi ich bin etwas Weiter gekommen
der code ist alles andere als sauber und fertig
mach aber bis jetzt zwei dinge ok
1. datensatz auslesen und zählerstand im datensatz finden
2. eine .txt erstellen mit den zählerständen

danke an BlackJack

Vier dinge muss ich noch hinkriegen

1. auslesen des zählers zu bestimmten zeiten (z.B.4x am Tag) (bis jetzt noch nicht hinbekommen)
2. einmal die woche eine Email mit der .txt versenden (dürfte kein grösseres Problem sein)
4. Code aufräumen und sauber schreien (das wird noch kniffelig)

anbei mein code bis jetzt

Code: Alles auswählen

import serial
import time


datensatzlaenge = 8
Zsposition = 144


Zielordner = "Testdatei.txt"

ser = serial.Serial ("/dev/ttyUSB0",9600,timeout=10000)     # open first serial port
ser.close()

def PortON():
    ser.open()    

def PortOFF():
    ser.close()

def Auslesen():
    
    datensatz_auslesen = False
    anfangsuchen = True
    i = 0
    j = 0
    while ser.inWaiting() <800:
        time.sleep(0.5)
    s = ser.read(ser.inWaiting())

    while anfangsuchen: #suchen des Datensatzanfang's 
        if (s[i:i+8])== b'\x1b\x1b\x1b\x1b\x01\x01\x01\x01':
            anfangsuchen = False
            print ("Der Anfang liegt bei",i)
            datensatz_auslesen = True
        elif i > 500:
            anfangsuchen = False
        else:
            i = i +1

    while datensatz_auslesen:
        if j == 0:
            S = (s[i+Zsposition+j])
            j = j+1
        else:
            S = (S*256) +(s[i+Zsposition+j]) #  den wert *256 nemen um es eine stelle Weiterzu setzen 
            j = j+1
        if j >= datensatzlaenge:
            datensatz_auslesen = False
            S = (float(S)/10000) #teilen für die Kommastelle
            Zaehlerstand =  str(S)+";"+'kwh'+";"+str(time.asctime())+"\n"
            print (Zaehlerstand)
            fobj = open(Zielordner,"a")
            fobj.write(str(Zaehlerstand))
            fobj.close()
         
# Main    
while True:
    PortON()
    Auslesen()
    PortOFF()
    time.sleep (50)
Antworten