jeelink2mqtt.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. #!/usr/bin/python3 -u
  2. # -*- coding: utf-8 -*-
  3. #
  4. import serial
  5. #from serial import Serial
  6. #from serial import Serial
  7. import time
  8. from time import localtime, strftime
  9. import os
  10. import paho.mqtt.client as mqtt
  11. #import json
  12. #import math
  13. #import numpy as np
  14. #import httplib
  15. mqtt_server = "mqtt.lan"
  16. mqtt_port = 1883
  17. mqtt_user = "script"
  18. mqtt_password = "rlAzqusqfbAy"
  19. sensordata_maxage = 300
  20. verbosemode = False
  21. def touch(fname, times=None):
  22. with open(fname, 'a'):
  23. os.utime(fname, times)
  24. def on_connect(client, userdata, flags, rc):
  25. if verbosemode:
  26. print("MQTT connected with result code " + str(rc))
  27. #client.subscribe("wetter/atemp")
  28. def on_disconnect(client, userdata, rc):
  29. if rc != 0:
  30. print("Unexpected MQTT disconnection. Will auto-reconnect")
  31. #def on_message(client, userdata, msg):
  32. # #print(msg.topic + " " + str(msg.payload))
  33. # global atemp
  34. # atemp = msg.payload
  35. minUpdateInterval = 60
  36. mqtt_topic_prefix = "LaCrosse"
  37. override_updateinterval_on_change = False
  38. atemp_sensor_idx = 94
  39. checkLastUpdateInterval = 60
  40. checkLastUpdateInterval_lastRun = 0
  41. sensors = {}
  42. sensors_idx = {}
  43. sensors_lastTemp = {}
  44. sensors_lastHum = {}
  45. sensors_lastUpdate = {}
  46. sensors_unavailable = {}
  47. with open("/home/pi/jeelink_sensors.csv", "r") as sensorscsv:
  48. for line in sensorscsv:
  49. if line.find('ID,DomoticzIdx,Name') == -1:
  50. # nur Zeilen die nicht der header sind sind interessant
  51. line = line.strip('\r')
  52. line = line.strip('\n')
  53. parts = line.split(',')
  54. sensorId = parts[0]
  55. domoticzIdx = parts[1]
  56. sensorName = parts[2]
  57. sensors[str(sensorId)] = str(sensorName)
  58. sensors_idx[str(sensorId)] = str(domoticzIdx)
  59. sensors_lastUpdate[str(sensorId)] = 0
  60. sensors_unavailable[str(sensorId)] = 1 #will be overwritten when first value is received
  61. if verbosemode:
  62. print("Sensor " + sensorId + " = '" + sensorName + "'")
  63. mqttc = mqtt.Client()
  64. mqttc.on_connect = on_connect
  65. mqttc.on_disconnect = on_disconnect
  66. ##mqttc.on_message = on_message
  67. mqttc.username_pw_set(mqtt_user, mqtt_password)
  68. mqttc.connect(mqtt_server, mqtt_port, 60)
  69. mqttc.loop_start()
  70. #mqttc.loop_forever()
  71. ser = serial.Serial(port='/dev/serial/by-id/usb-FTDI_FT232R_USB_UART_AL01MYTF-if00-port0',
  72. baudrate = 57600,
  73. parity=serial.PARITY_NONE,
  74. stopbits=serial.STOPBITS_ONE,
  75. bytesize=serial.EIGHTBITS,
  76. timeout=1)
  77. #sensors = {'4':'Arbeitszimmer','16':'AussenGarten','60':'AussenParkplatz','50':'Bad','39':'Balkon','55':'Kueche','40':'Schlafzimmer'}
  78. #sensors_idx = {'4':'1','16':'94','60':'113','50':'4','39':'88','55':'6','40':'3'}
  79. if verbosemode:
  80. print(sensors)
  81. print(sensors_idx)
  82. checkLastUpdateInterval_lastRun = time.time() # first check after 1 min
  83. try:
  84. while True:
  85. msg_was_sent = 0
  86. #clear serial buffer to remove junk and noise
  87. ser.flushInput()
  88. #read buffer until cr/lf
  89. serLine = ser.readline().strip()
  90. serLine = serLine.decode('ascii')
  91. if(serLine):
  92. if verbosemode:
  93. print(serLine)
  94. if serLine.find('OK 9') != -1:
  95. if verbosemode:
  96. print("is LaCrosse sensor")
  97. # uns interessieren nur reinkommende Zeilen die mit "OK 9 " beginnen
  98. # 0 1 2 3 4 5 6
  99. # OK 9 ID XXX XXX XXX XXX
  100. # | | | | | | |
  101. # | | | | | | --- Humidity incl. WeakBatteryFlag
  102. # | | | | | |------ Temp * 10 + 1000 LSB
  103. # | | | | |---------- Temp * 10 + 1000 MSB
  104. # | | | |-------------- Sensor type (1 or 2) +128 if NewBatteryFlag
  105. # | | |----------------- Sensor ID
  106. # | |------------------- fix "9"
  107. # |---------------------- fix "OK"
  108. serLineParts = serLine.split(' ')
  109. #addr = serLineParts[2]
  110. #addr = "{0:x}".format(int(serLineParts[2]))
  111. #addr = hex((int(serLineParts[2])))
  112. addr = int(serLineParts[2])
  113. addrhex = "{0:x}".format(int(serLineParts[2]))
  114. lastUpdate = sensors_lastUpdate.get(str(addr), None)
  115. lastTemp = sensors_lastTemp.get(str(addr), None)
  116. lastHum = sensors_lastHum.get(str(addr), None)
  117. currentsensor_idx = sensors_idx.get(str(addr),None)
  118. currentsensor_name = sensors.get(str(addr), None)
  119. senddata = False
  120. if currentsensor_idx is not None:
  121. if override_updateinterval_on_change:
  122. if lastTemp != str(temp) or lastHum != str(hum):
  123. senddata = True
  124. #print "hier! " + str(temp) + " != " + str(lastTemp) + " " + str(hum) + " != " + str(lastHum)
  125. if lastUpdate is not None:
  126. timediff = int(time.time()) - lastUpdate
  127. if timediff >= minUpdateInterval:
  128. senddata = True
  129. elif sensors_unavailable[str(addr)] == 1:
  130. senddata = True
  131. else:
  132. senddata = True
  133. sensors_unavailable[str(addr)] = 0
  134. #print(sensors_unavailable)
  135. if int(serLineParts[3]) >= 128:
  136. batt_new = 1
  137. type = int(serLineParts[3]) - 128
  138. else:
  139. batt_new = 0
  140. type = int(serLineParts[3])
  141. temp = (int(serLineParts[4])*256 + int(serLineParts[5]) - 1000)/10.0
  142. if int(serLineParts[6]) >= 128:
  143. batt_low = 1
  144. hum = int(serLineParts[6]) - 128
  145. else:
  146. batt_low = 0
  147. hum = int(serLineParts[6])
  148. if hum > 100:
  149. hum = 100
  150. if batt_low == 0:
  151. batterystate = "ok"
  152. else:
  153. batterystate = "low"
  154. if currentsensor_name is None:
  155. if batt_new == 1:
  156. fname = '/home/pi/logs/jeelink_unknown_new_sensor_' + str(addr)
  157. else:
  158. fname = '/home/pi/logs/jeelink_unknown_sensor_' + str(addr)
  159. if not os.path.isfile(fname):
  160. try:
  161. touch(fname)
  162. except:
  163. # guat dann hoit ned...
  164. pass
  165. temp = (int(serLineParts[4])*256 + int(serLineParts[5]) - 1000)/10.0
  166. if verbosemode:
  167. print("unknown sensor ID " + str(addr))
  168. print("Temp: " + str(temp))
  169. mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(addr)+"/temperature", str(temp), qos=2, retain=False)
  170. mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(addr)+"/humidity", str(hum), qos=2, retain=False)
  171. mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(addr)+"/battNew", str(batt_new), qos=2, retain=False)
  172. if senddata:
  173. sensors_lastUpdate[str(addr)] = int(time.time())
  174. sensors_lastTemp[str(addr)] = str(temp)
  175. sensors_lastHum[str(addr)] = str(hum)
  176. if int(currentsensor_idx) == atemp_sensor_idx:
  177. mqttc.publish("wetter/atemp", str(temp), qos=2, retain=True)
  178. mqttc.publish("wetter/ahum", str(hum), qos=2, retain=True)
  179. domoticz_json = "{\"idx\":" + str(currentsensor_idx) + ",\"nvalue\":0,\"svalue\":\"" + str(temp) + ";" + str(hum) + ";1\"}"
  180. if verbosemode:
  181. print(domoticz_json)
  182. mqttc.publish("domoticz/in", domoticz_json, qos=2, retain=False)
  183. mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/temperature", str(temp), qos=2, retain=False)
  184. mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/humidity", str(hum), qos=2, retain=False)
  185. mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/battery", str(batterystate), qos=2, retain=False)
  186. mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/lastUpdate", strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=2, retain=False)
  187. mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/availability", "available", qos=2, retain=False)
  188. lacrosse_json = "{\"temperature\":" + str(temp) + ", \"humidity\":" + str(hum) + ", \"battery\":\"" + str(batterystate) + "\"}"
  189. mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/json", lacrosse_json, qos=2, retain=False)
  190. if verbosemode:
  191. 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))
  192. print("\n")
  193. try:
  194. touch("/tmp/jeelink2mqtt_running")
  195. except:
  196. # guat dann ned...
  197. pass
  198. # handle outdated sensor values once a minute
  199. if (time.time() - checkLastUpdateInterval_lastRun) > checkLastUpdateInterval:
  200. checkLastUpdateInterval_lastRun = time.time()
  201. #print("check lastUpdate")
  202. for key in sensors_lastUpdate:
  203. #print(key, '->', sensors_lastUpdate[key], '->', sensors[key])
  204. if (time.time() - sensors_lastUpdate[key]) > sensordata_maxage:
  205. print(sensors[key], ' outd ->')
  206. sensors_unavailable[key] = 1
  207. mqttc.publish(mqtt_topic_prefix+"/"+str(sensors[key])+"/availability", "unavailable", qos=2, retain=False)
  208. except KeyboardInterrupt:
  209. print('\n')