Browse Source

- Python 3 compatible
- handle outdated sensor values (with availability topic for HASS)
- also send unknown (new) sensors via MQTT with their ID

FloKra 4 years ago
parent
commit
f8b472b91e
2 changed files with 119 additions and 72 deletions
  1. 115 68
      src/jeelink2mqtt.py
  2. 4 4
      src/jeelink_sensors.csv

+ 115 - 68
src/jeelink2mqtt.py

@@ -1,8 +1,14 @@
-#!/usr/bin/python -u
+#!/usr/bin/python3 -u
 # -*- coding: utf-8 -*-
 #
+
 import serial
+#from serial import Serial
+
+#from serial import Serial
 import time
+from time import localtime, strftime   
+
 import os
 import paho.mqtt.client as mqtt
 #import json
@@ -12,8 +18,10 @@ import paho.mqtt.client as mqtt
 
 mqtt_server = "mqtt.lan"
 mqtt_port = 1883
-mqtt_user = "sagi"
-mqtt_password = "dirsichernet!"
+mqtt_user = "script"
+mqtt_password = "rlAzqusqfbAy"
+
+sensordata_maxage = 300
 
 verbosemode = False
 
@@ -22,12 +30,13 @@ def touch(fname, times=None):
         os.utime(fname, times)
 
 def on_connect(client, userdata, flags, rc):
-    print("MQTT connected with result code " + str(rc))
+    if verbosemode:
+        print("MQTT connected with result code " + str(rc))
     #client.subscribe("wetter/atemp")
 
 def on_disconnect(client, userdata, rc):
     if rc != 0:
-        print "Unexpected MQTT disconnection. Will auto-reconnect"
+        print("Unexpected MQTT disconnection. Will auto-reconnect")
         
 #def on_message(client, userdata, msg):
 #    #print(msg.topic + " " + str(msg.payload))
@@ -40,8 +49,16 @@ mqtt_topic_prefix = "LaCrosse"
 override_updateinterval_on_change = False
 atemp_sensor_idx = 94
 
+checkLastUpdateInterval = 60
+checkLastUpdateInterval_lastRun = 0
+
 sensors = {}
 sensors_idx = {}
+sensors_lastTemp = {}
+sensors_lastHum = {}
+sensors_lastUpdate = {}
+sensors_unavailable = {}
+
 with open("/home/pi/jeelink_sensors.csv", "r") as sensorscsv:
     for line in sensorscsv:
         if line.find('ID,DomoticzIdx,Name') == -1:
@@ -56,6 +73,10 @@ with open("/home/pi/jeelink_sensors.csv", "r") as sensorscsv:
             
             sensors[str(sensorId)] = str(sensorName)
             sensors_idx[str(sensorId)] = str(domoticzIdx)
+            sensors_lastUpdate[str(sensorId)] = 0
+            sensors_unavailable[str(sensorId)] = 1 #will be overwritten when first value is received
+            if verbosemode:
+                print("Sensor " + sensorId + " = '" + sensorName + "'")
 
             
 mqttc = mqtt.Client()
@@ -80,12 +101,11 @@ ser = serial.Serial(port='/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AL01MYTF-if
 #sensors_idx = {'4':'1','16':'94','60':'113','50':'4','39':'88','55':'6','40':'3'}
 
 if verbosemode:
-    print sensors
-    print sensors_idx
+    print(sensors)
+    print(sensors_idx)
+    
 
-sensors_lastTemp = {}
-sensors_lastHum = {}
-sensors_lastUpdate = {}
+checkLastUpdateInterval_lastRun = time.time() # first check after 1 min
     
 try:
     while True:
@@ -95,17 +115,16 @@ try:
         ser.flushInput()
         
         #read buffer until cr/lf
-        serLine = ser.readline()
+        serLine = ser.readline().strip()
+        serLine = serLine.decode('ascii')
 
         if(serLine):
-            #print (repr(serLine))    #Echo the serial buffer bytes up to the CRLF back to screen
-            serLine = serLine.strip('\r')
-            serLine = serLine.strip('\n')
-            
             if verbosemode:
-                print serLine
-            
+                print(serLine)
             if serLine.find('OK 9') != -1:
+                if verbosemode:
+                    print("is LaCrosse sensor")
+
                 # uns interessieren nur reinkommende Zeilen die mit "OK 9 " beginnen
                 
                 # 0  1 2  3   4   5   6
@@ -119,32 +138,20 @@ try:
                 # |  |------------------- fix "9"
                 # |---------------------- fix "OK"
                 
-                bytes = serLine.split(' ')
-                
-                #addr = bytes[2]
-                #addr = "{0:x}".format(int(bytes[2]))
-                #addr = hex((int(bytes[2])))
-                addr = int(bytes[2])
-                addrhex = "{0:x}".format(int(bytes[2]))
+                serLineParts = serLine.split(' ')
                 
-                currentsensor_name = sensors.get(str(addr), None)
-                #if currentsensor_name == 0: 
-                if currentsensor_name is None: 
-                    fname = '/home/pi/logs/jeelink_unknown_sensor_' + str(addr)
-                    try:
-                        touch(fname)
-                    except:
-                        # guat dann hoit ned...
-                        pass
-                    if verbosemode:
-                        print "unknown sensor ID " + str(addr)
-                        temp = (int(bytes[4])*256 + int(bytes[5]) - 1000)/10.0
-                        print "Temp: " + str(temp)
+                #addr = serLineParts[2]
+                #addr = "{0:x}".format(int(serLineParts[2]))
+                #addr = hex((int(serLineParts[2])))
+                addr = int(serLineParts[2])
+                addrhex = "{0:x}".format(int(serLineParts[2]))
+                    
                 
                 lastUpdate = sensors_lastUpdate.get(str(addr), None)
                 lastTemp = sensors_lastTemp.get(str(addr), None)
                 lastHum = sensors_lastHum.get(str(addr), None)
                 currentsensor_idx = sensors_idx.get(str(addr),None)
+                currentsensor_name = sensors.get(str(addr), None)
                 
                 senddata = False
                 if currentsensor_idx is not None:
@@ -157,34 +164,60 @@ try:
                         timediff = int(time.time()) - lastUpdate
                         if timediff >= minUpdateInterval:
                             senddata = True
-                            #print "do!"
+                        elif sensors_unavailable[str(addr)] == 1:
+                            senddata = True
                     else:
                         senddata = True
                 
-                if senddata:
-                    if int(bytes[3]) >= 128: 
-                        batt_new = 1
-                        type = int(bytes[3]) - 128
-                    else:
-                        batt_new = 0
-                        type = int(bytes[3])
-                    
-                    temp = (int(bytes[4])*256 + int(bytes[5]) - 1000)/10.0
-                    
-                    if int(bytes[6]) >= 128: 
-                        batt_low = 1
-                        hum = int(bytes[6]) - 128
-                    else:
-                        batt_low = 0
-                        hum = int(bytes[6])
-                        if hum > 100: 
-                            hum = 100
-                    
-                    if batt_low == 0:
-                        batterystate = "ok"
+                    sensors_unavailable[str(addr)] = 0
+                    #print(sensors_unavailable)
+                
+                if int(serLineParts[3]) >= 128: 
+                    batt_new = 1
+                    type = int(serLineParts[3]) - 128
+                else:
+                    batt_new = 0
+                    type = int(serLineParts[3])
+                
+                temp = (int(serLineParts[4])*256 + int(serLineParts[5]) - 1000)/10.0
+                
+                if int(serLineParts[6]) >= 128: 
+                    batt_low = 1
+                    hum = int(serLineParts[6]) - 128
+                else:
+                    batt_low = 0
+                    hum = int(serLineParts[6])
+                    if hum > 100: 
+                        hum = 100
+                
+                if batt_low == 0:
+                    batterystate = "ok"
+                else:
+                    batterystate = "low"
+                
+                
+                if currentsensor_name is None: 
+                    if batt_new == 1:
+                        fname = '/home/pi/logs/jeelink_unknown_new_sensor_' + str(addr)
                     else:
-                        batterystate = "low"
-                    
+                        fname = '/home/pi/logs/jeelink_unknown_sensor_' + str(addr)
+                        
+                    if not os.path.isfile(fname):
+                        try:
+                            touch(fname)
+                        except:
+                            # guat dann hoit ned...
+                            pass
+                    temp = (int(serLineParts[4])*256 + int(serLineParts[5]) - 1000)/10.0
+                    if verbosemode:
+                        print("unknown sensor ID " + str(addr))
+                        print("Temp: " + str(temp))
+                    mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(addr)+"/temperature", str(temp), qos=2, retain=False)
+                    mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(addr)+"/humidity", str(hum), qos=2, retain=False)
+                    mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(addr)+"/battNew", str(batt_new), qos=2, retain=False)
+                
+                
+                if senddata:
                     sensors_lastUpdate[str(addr)] = int(time.time())
                     sensors_lastTemp[str(addr)] = str(temp)
                     sensors_lastHum[str(addr)] = str(hum)
@@ -195,24 +228,38 @@ try:
                     
                     domoticz_json = "{\"idx\":" + str(currentsensor_idx) + ",\"nvalue\":0,\"svalue\":\"" + str(temp) + ";" + str(hum) + ";1\"}"
                     if verbosemode:
-                        print domoticz_json
+                        print(domoticz_json)
                     mqttc.publish("domoticz/in", domoticz_json, qos=2, retain=False)
                     
-                    mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/temperature", str(temp), qos=2, retain=True)
-                    mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/humidity", str(hum), qos=2, retain=True)
-                    mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/battery", str(batterystate), qos=2, retain=True)
+                    mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/temperature", str(temp), qos=2, retain=False)
+                    mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/humidity", str(hum), qos=2, retain=False)
+                    mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/battery", str(batterystate), qos=2, retain=False)
+                    mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/lastUpdate", strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=2, retain=False)
+                    mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/availability", "available", qos=2, retain=False)
                     
                     lacrosse_json = "{\"temperature\":" + str(temp) + ", \"humidity\":" + str(hum) + ", \"battery\":\"" + str(batterystate) + "\"}"
-                    mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/json", lacrosse_json, qos=2, retain=True)
+                    mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/json", lacrosse_json, qos=2, retain=False)
                     
                     if verbosemode:
-                        print "addr: " + str(addr) + " = 0x" + str(addrhex) + "   batt_new: " + str(batt_new) + "   type: " + str(type) + "   batt_low: " + str(batt_low) + "   temp: " + str(temp) + "   hum: " + str(hum) + "   Name: " + str(currentsensor_name)
+                        print("addr: " + str(addr) + " = 0x" + str(addrhex) + "   batt_new: " + str(batt_new) + "   type: " + str(type) + "   batt_low: " + str(batt_low) + "   temp: " + str(temp) + "   hum: " + str(hum) + "   Name: " + str(currentsensor_name))
+                        print("\n")
                 
                     try:
                         touch("/tmp/jeelink2mqtt_running")
                     except:
                         # guat dann ned...
                         pass
+                        
+        # handle outdated sensor values once a minute
+        if (time.time() - checkLastUpdateInterval_lastRun) > checkLastUpdateInterval:
+            checkLastUpdateInterval_lastRun = time.time()
+            #print("check lastUpdate")
+            for key in sensors_lastUpdate:
+                #print(key, '->', sensors_lastUpdate[key], '->', sensors[key])
+                if (time.time() - sensors_lastUpdate[key]) > sensordata_maxage:
+                    print(sensors[key], ' outd ->')
+                    sensors_unavailable[key] = 1
+                    mqttc.publish(mqtt_topic_prefix+"/"+str(sensors[key])+"/availability", "unavailable", qos=2, retain=False)
 
-except KeyboardInterrupt, e:
+except KeyboardInterrupt:
     print('\n')

+ 4 - 4
src/jeelink_sensors.csv

@@ -1,8 +1,8 @@
 ID,DomoticzIdx,Name
-4,1,T5-Arbeitszimmer
+48,1,T5-Arbeitszimmer
 16,94,Garten
 60,113,Parkplatz
-50,4,T5-Bad
-39,88,T5-Balkon
+19,4,T5-Bad
+22,88,T5-Balkon
 55,6,T5-Kueche
-40,3,T5-Schlafzimmer
+40,264,Stiegenhaus