<
#! / usr / bin / env python3
--------------------------------------------- ---------------------------
"DIE BIERWARENLIZENZ" (Revision 42):
phk@FreeBSD.ORG hat diese Datei geschrieben. Solange Sie diesen Hinweis behalten
kann mit diesem Zeug machen, was immer du willst. Wenn wir uns eines Tages treffen und Sie denken
dieses Zeug ist es wert, du kannst mir dafür ein Bier kaufen. Poul-Henning Kamp
--------------------------------------------- ---------------------------
Geändert für Domotics und Einzelanfragen.
Modifiziert von Ronald van der Meer, Frank Reijn und Paul Bonnemaijers für die
Kamstrup Multical 402
Modifiziert von Tim van Werkhoven 20201112 für den generischen Gebrauch (zB mqtt / influxdb).
Auch überflüssige Zählerablesung aus dem Skript beschnitten, um die Batterie des Zählers zu schonen
life (vorherige Version hat alle 30 Variablen gelesen und nicht verwendete Daten verworfen)
Verwendung: file <ComPort>
aus future import print_function
Sie benötigen pySerial
Seriennummer importieren
Mathe importieren
sys importieren
Datum / Uhrzeit importieren
Import - Anfragen
Paho importieren . mqtt . Kunde als Paho
urllib importieren
importiere urllib.request
Codecs importieren
Variablen
reader = codecs . getreader ( "utf-8" )
Debug = 1
multical_var = { # Dezimalzahl im Befehl für Kamstrup Multical
0x003C : " Wärmeenergie (E1)" , # 60
0x0050 : "Power" , # 80
0x0056 : "Temp1" , # 86
0x0057 : "Temp2" , # 87
0x0059 : "Tempdiff" , # 89
0x004A : "Flow" , # 74
0x0044 : "Volume" , # 68
0x008D : "MinFlow_M" , # 141
0x008B : "MaxFlow_M" , # 139
0x008C : "MinFlowDate_M" , # 140
0x008A : "MaxFlowDate_M" , # 138
0x0091 : "MinPower_M" , # 145
0x008F : "MaxPower_M" , # 143
0x0095 : "AvgTemp1_M" , # 149
0x0096 : "AvgTemp2_M" , # 150
0x0090 : "MinPowerDate_M" , # 144
0x008E : "MaxPowerDate_M" , # 142
0x007E : "MinFlow_Y" , # 126
0x007C : "MaxFlow_Y" , # 124
0x007D : "MinFlowDate_Y" , # 125
0x007B : "MaxFlowDate_Y" , # 123
0x0082 : "MinPower_Y" , # 130
0x0080 : "MaxPower_Y" , # 128
0x0092 : "AvgTemp1_Y" , # 146
0x0093 : "AvgTemp2_Y" , # 147
0x0081 : "MinPowerDate_Y" , # 129
0x007F : "MaxPowerDate_Y" , # 127
0x0061 : "Temp1xm3" , # 97
0x006E : "Temp2xm3" , # 110
0x0071 : "Infoevent" , # 113
0x03EC : "HourCounter" , # 1004
}}
multical_var_si = { # Dezimalzahl im Befehl für Kamstrup Multical
0x003C : 1E + 0 , # Quelldaten bereits in Joule - " Wärmeenergie (E1)", # 60
0x0050 : 1E-3 , # Quelldaten in MilliWatt - "Power", # 80
0x0056 : 1E-9 , # Quelldaten in nanoCelcius - "Temp1", # 86
0x0057 : 1E-9 , # Quelldaten in nanoCelcius - "Temp2", # 87
0x0059 : 1E-9 , # Quelldaten in nanoCelcius - "Tempdiff", # 89
0x004A : 1E-12 / 3600 , # Quelldaten Nanoliter / Stunde - "Flow", # 74
0x0044 : 1E + 0 , # Quelldaten TBD - "Volume", # 68
0x008D : 1E + 0 , # Quelldaten TBD - "MinFlow_M", # 141
0x008B : 1E + 0 , # Quelldaten TBD - "MaxFlow_M", # 139
0x008C : 1E + 0 , # Quelldaten TBD - "MinFlowDate_M", # 140
0x008A : 1E + 0 , # Quelldaten TBD - "MaxFlowDate_M", # 138
0x0091 : 1E + 0 , # Quelldaten TBD - "MinPower_M", # 145
0x008F : 1E + 0 , # Quelldaten TBD - "MaxPower_M", # 143
0x0095 : 1E + 0 , # Quelldaten TBD - "AvgTemp1_M", # 149
0x0096 : 1E + 0 , # Quelldaten TBD - "AvgTemp2_M", # 150
0x0090 : 1E + 0 , # Quelldaten TBD - "MinPowerDate_M", # 144
0x008E : 1E + 0 , # Quelldaten TBD - "MaxPowerDate_M", # 142
0x007E : 1E + 0 , # Quelldaten TBD - "MinFlow_Y", # 126
0x007C : 1E + 0 , # Quelldaten TBD - "MaxFlow_Y", # 124
0x007D : 1E + 0 , # Quelldaten TBD - "MinFlowDate_Y", # 125
0x007B : 1E + 0 , # Quelldaten TBD - "MaxFlowDate_Y", # 123
0x0082 : 1E + 0 , # Quelldaten TBD - "MinPower_Y", # 130
0x0080 : 1E + 0 , # Quelldaten TBD - "MaxPower_Y", # 128
0x0092 : 1E + 0 , # Quelldaten TBD - "AvgTemp1_Y", # 146
0x0093 : 1E + 0 , # Quelldaten TBD - "AvgTemp2_Y", # 147
0x0081 : 1E + 0 , # Quelldaten TBD - "MinPowerDate_Y", # 129
0x007F : 1E + 0 , # Quelldaten TBD - "MaxPowerDate_Y", # 127
0x0061 : 1E + 0 , # Quelldaten TBD - "Temp1xm3", # 97
0x006E : 1E + 0 , # Quelldaten TBD - "Temp2xm3", # 110
0x0071 : 1E + 0 , # Quelldaten TBD - "Infoevent", # 113
0x03EC : 1E + 0 , # Quelldaten TBD - "HourCounter", # 1004
}}
#################################################### #######################
Einheiten, bereitgestellt von Erik Jensen
Einheiten = {
0 : '' , 1 : 'Wh' , 2 : 'kWh' , 3 : 'MWh' , 4 : 'GWh' , 5 : 'j' , 6 : 'kj' , 7 : 'Mj' ,
8 : 'Gj' , 9 : 'Cal' , 10 : 'kCal' , 11 : 'Mcal' , 12 : 'Gcal' , 13 : 'varh' ,
14 : 'kvarh' , 15 : 'Mvarh' , 16 : 'Gvarh' , 17 : 'VAh' , 18 : 'kVAh' ,
19 : 'MVAh' , 20 : 'GVAh' , 21 : 'kW' , 22 : 'kW' , 23 : 'MW' , 24 : 'GW' ,
25 : 'kvar' , 26 : 'kvar' , 27 : 'Mvar' , 28 : 'Gvar' , 29 : 'VA' , 30 : 'kVA' ,
31 : 'MVA' , 32 : 'GVA' , 33 : 'V' , 34 : 'A' , 35 : 'kV' , 36 : 'kA' , 37 : 'C' ,
38 : 'K' , 39 : 'l' , 40 : 'm3' , 41 : 'l / h' , 42 : 'm3 / h' , 43 : 'm3xC' ,
44 : 'ton' , 45 : 'ton / h' , 46 : 'h' , 47 : 'hh: mm: ss' , 48 : 'yy: mm: dd' ,
49 : 'yyyy: mm: dd' , 50 : 'mm: dd' , 51 : '' , 52 : 'Bar' , 53 : 'RTC' ,
54 : 'ASCII' , 55 : 'm3 x 10' , 56 : 'ton x 10' , 57 : 'GJ x 10' ,
58 : 'Minuten' , 59 : 'Bitfeld' , 60 : 's' , 61 : 'ms' , 62 : 'Tage' ,
63 : 'RTC-Q' , 64 : 'Datetime'
}}
#################################################### #######################
Kamstrup verwendet das "echte" CCITT CRC-16
def crc_1021 ( Nachricht 
poly = 0x1021
reg = 0x0000
für Byte in Nachricht :
Maske = 0x80
while ( Maske > 0 
reg << = 1
wenn Byte & Maske :
reg | = 1
Maske >> = 1
if reg & 0x10000 :
reg & = 0xffff
reg ^ = poly
Rückgabe reg
#################################################### #######################
Bytewerte, die vor der Übertragung maskiert werden müssen
entkommt = {
0x06 : Wahr ,
0x0d : Wahr ,
0x1b : Wahr ,
0x40 : Wahr ,
0x80 : Richtig ,
}}
#################################################### #######################
Und es geht los....
Klasse Kamstrup ( Objekt 
def __init__ ( self , serial_port ):
Selbst . debug_fd = open ( "/ tmp / _kamstrup" , "a" )
Selbst . debug_fd . Schreiben ( " \ n \ n Start \ n " )
Selbst . debug_id = Keine
Selbst . ser = serial . Seriennummer (
port = serial_port ,
Baudrate = 1200 ,
Zeitüberschreitung = 5,0 ,
Bytesize = seriell . EIGHTBITS ,
Parität = seriell . PARITY_NONE ,
Stopbits = seriell . STOPBITS_TWO )
xonxoff = 0,
rtscts = 0)
timeout = 20
def debug ( self , dir , b ):
für i in b :
wenn dir ! = Selbst . debug_id :
wenn selbst . debug_id ! = Keine :
Selbst . debug_fd . schreiben ( " \ n " )
Selbst . debug_fd . schreibe ( dir + " \ t " )
Selbst . debug_id = dir
Selbst . debug_fd . schreibe ( "% 02x" % i )
Selbst . debug_fd . Flush ()
def debug_msg ( self , msg ):
wenn selbst . debug_id ! = Keine :
Selbst . debug_fd . schreiben ( " \ n " )
Selbst . debug_id = "Msg"
Selbst . debug_fd . schreiben ( "Msg \ t " + msg )
Selbst . debug_fd . Flush ()
def wr ( self , b ):
b = Bytearray ( b )
Selbst . Debug ( "Wr" , b );
Selbst . ser . schreibe ( b )
def rd ( Selbst ):
a = Selbst . ser . lesen ( 1 )
wenn len ( a ) == 0 :
Selbst . debug_msg ( "Rx Timeout" )
Rückgabe Keine
b = Bytearray ( a ) [ 0 ]
Selbst . Debug ( "Rd" , Bytearray (( b ,)));
Rückkehr b
def send ( self , pfx , msg ):
b = Bytearray ( msg )
b . anhängen ( 0 )
b . anhängen ( 0 )
c = crc_1021 ( b )
b [ - 2 ] = c >> 8
b [ - 1 ] = c & 0xff
c = bytearray ()
c . anhängen ( pfx )
für i in b :
wenn ich in entkommt :
c . anhängen ( 0x1b )
c . anhängen ( i ^ 0xff )
sonst :
c . anhängen ( i )
c . anhängen ( 0x0d )
Selbst . wr ( c )
def recv ( Selbst ):
b = bytearray ()
während wahr :
d = Selbst . rd ()
wenn d == Keine :
Rückgabe Keine
wenn d == 0x40 :
b = bytearray ()
b . anhängen ( d )
wenn d == 0x0d :
brechen
c = bytearray ()
i = 1 ;
während i < len ( b ) - 1 :
wenn b [ i ] == 0x1b :
v = b [ i + 1 ] ^ 0xff
wenn v nicht in entkommt :
Selbst . debug_msg (
"Fehlende Flucht% 02x" % v )
c . anhängen ( v )
i + = 2
sonst :
c . anhängen ( b [ i ])
i + = 1
wenn crc_1021 ( c ):
Selbst . debug_msg ( "CRC-Fehler" )
return c [: - 2 ]
def readvar ( self , nbr ):
# Ich wäre nicht überrascht, wenn Sie mehr verlangen könnten als
# eine Variable zu der Zeit, vorausgesetzt, die Länge ist
# in der Antwort codiert. Habe es nicht versucht.
Selbst . send ( 0x80 , ( 0x3f , 0x10 , 0x01 , nbr >> 8 , nbr & 0xff ))
b = Selbst . recv ()
if b == Keine :
return ( Keine , Keine )
wenn b [ 0 ] ! = 0x3f oder b [ 1 ] ! = 0x10 :
return ( Keine , Keine )
wenn b [ 2 ] ! = nbr >> 8 oder b [ 3 ] ! = nbr & 0xff :
return ( Keine , Keine )
wenn b [ 4 ] in Einheiten :
u = Einheiten [ b [ 4 ]]
sonst :
u = Keine
# Dekodiere die Mantisse
x = 0
für i im Bereich ( 0 , b [ 5 ]):
x << = 8
x | = b [ i + 7 ]
# Dekodiere den Exponenten
i = b [ 6 ] & 0x3f
wenn b [ 6 ] & 0x40 :
i = - i
i = math . pow ( 10 , i )
wenn b [ 6 ] & 0x80 :
i = - i
x * = i
wenn falsch :
# Drucken debuggen
s = ""
für i in b [: 4 ]:
s + = "% 02x" % i
s + = "|"
für i in b [ 4 : 7 ]:
s + = "% 02x" % i
s + = "|"
für i in b [ 7 :]:
s + = "% 02x" % i
print ( s , "=" , x , Einheiten [ b [ 4 ]])
return ( x , u )
def influxdb_update ( Wert , prot = 'http' , ip = '127.0.0.1' , port = '8086' , db = "smarthome" , querybase = "Energie, Menge = Wärme, Quelle = multical, Typ = Verbrauchswert =" 
"" "
Drücken Sie das Update mit zweiter Genauigkeit auf influxdb
"" "
# Der Wert ist in GJ, wir konvertieren nach Joule, um SI in influxdb zu erhalten
value_joule = value * 1000000000
# So etwas wie req_url = "http: // localhost: 8086 / write? Db = smarthometest & präzise = s"
req_url = "{}: // {}: {} / write? db = {} & präzise = s" . Format ( prot , ip , port , db )
# So etwas wie post_data = "Energie, Typ = Wärme, Gerät = Landisgyr-Wert = 10"
# Alternativ wie post_data = "energy landisgyr = 10"
post_data = "{} {: d}" . Format ( Abfragebasis , int ( value_joule ))
wenn Debug > 0 :
print ( "Daten '{}' an influxdb senden" . format ( post_data ))
versuchen Sie :
httpresponse = Anfragen . post ( req_url , data = post_data , verify = False , timeout = 5 )
außer Ausnahme als inst :
print ( "Zählerstand konnte nicht aktualisiert werden: {}" . format ( inst ))
bestehen
def mqtt_update ( Nutzlast , IP , Port , Benutzer , Passwort , Thema 
"" "
In mqtt veröffentlichen
http://www.steves-internet-guide.com/publishing-messages-mqtt-client/
https://pypi.org/project/paho-mqtt/#publishing
"" "
# Broker = "192.168.1.184"
# port = 1883
client1 = paho . Client ( client_id = "multical" )
client1 . username_pw_set ( user , passwd )
versuchen Sie :
client1 . verbinden ( ip , int ( port ))
außer :
print ( 'Verbindung zum mqtt-Broker konnte nicht hergestellt werden' )
versuchen Sie :
ret = client1 . veröffentlichen ( Thema , Nutzlast )
außer :
print ( 'mqtt-Wert konnte nicht veröffentlicht werden' )
if name == "main" :
Importzeit
versuchen Sie :
comport = sys . argv [ 1 ]
außer IndexError :
print ( "Gerät erforderlich. Beispiel: / dev / ttyUSB0" )
sys . exit ()
# Das vorherige Skript hatte mehrere Argumente und wurde für unterschiedliche Zwecke auskommentiert
#
#command = int (sys.argv [2], 0)
versuchen Sie :
index = str ( sys . argv [ 2 ])
außer IndexError :
print ( "Multical Befehle erforderlich." )
sys . exit ()
index = index . split ( ',' )
wenn Debug > 0 :
print ( "Parameter angegeben:" )
für i im Index :
print ( "+" + i )
foo = kamstrup ( comport )
heat_timestamp = datetime . Datum / Uhrzeit . strftime ( datetime . datetime . today (), "% Y-% m-% d% H:% M:% S" )
für i im Index :
ii = int ( i )
multical_var [ ii ]
x , u = foo . readvar ( ii )
# In SI-Einheiten umrechnen
xsi = x * multical_var_si [ ii ]
print ( "{}, {}, {}" . Format ( multical_var [ ii ], xsi , u ))
# influxdb_update (xsi)
# mqtt_update (Nutzlast, IP, Port, Benutzer, Passwort, Thema)
/>