jeelinklog.py 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. #!/usr/bin/python3 -u
  2. # -*- coding: utf-8 -*-
  3. #
  4. import serial
  5. import time
  6. from time import localtime, strftime
  7. from datetime import datetime
  8. import configparser
  9. from influxdb import InfluxDBClient
  10. import os
  11. import sys
  12. import paho.mqtt.client as mqtt
  13. import yaml
  14. import json
  15. #import math
  16. #import numpy as np
  17. #import httplib
  18. # Change working dir to the same dir as this script
  19. os.chdir(sys.path[0])
  20. config = configparser.ConfigParser()
  21. config.read('jeelinklog.ini')
  22. serialport = config['jeelink'].get('serialport')
  23. serialbaud = int(config['jeelink'].get('baudrate'))
  24. mqtt_topic_prefix = config['mqtt'].get('topic_prefix')
  25. topic_prefix_outside_temphum = config['mqtt'].get('topic_prefix_outside_temphum')
  26. mqtt_topic_atemp = config['mqtt'].get('topic_outside_temp')
  27. mqtt_topic_ahum = config['mqtt'].get('topic_outside_hum')
  28. mqtt_topic_domoticz_in = "domoticz/in"
  29. mqtt_subtopic_notify = config['mqtt'].get('subtopic_notify')
  30. logfile_new_sensors = config['main'].get('logfile_new_sensors')
  31. path_new_sensors_folder = config['main'].get('path_new_sensors_folder')
  32. log_path = config['main'].get('log_path')
  33. if not os.path.exists(log_path):
  34. os.makedirs(log_path)
  35. verbosemode = False
  36. #sensors_conf_file = "/home/pi/jeelink_sensors.csv"
  37. sensordata_maxage = int(config['sensors'].get('data_maxage'))
  38. #minUpdateInterval = 60
  39. #aTempHumPublishInterval = 60
  40. #override_updateinterval_on_change = False
  41. #atemp_sensor_idx = 94
  42. #atemp_sensor_idx_2 = 113
  43. average_value_steps = int(config['sensors'].get('average_value_steps'))
  44. if average_value_steps == 0:
  45. average_value_steps = 1
  46. if len(sys.argv) > 1 and str(sys.argv[1]) == "-v":
  47. verbosemode = True
  48. def touch(fname, times=None):
  49. with open(fname, 'a'):
  50. os.utime(fname, times)
  51. def on_connect(client, userdata, flags, rc):
  52. if verbosemode:
  53. print("MQTT connected with result code " + str(rc) + "\n")
  54. #client.subscribe("wetter/atemp")
  55. def on_disconnect(client, userdata, rc):
  56. if rc != 0:
  57. print("Unexpected MQTT disconnection. Will auto-reconnect\n")
  58. #def on_message(client, userdata, msg):
  59. # #print(msg.topic + " " + str(msg.payload))
  60. # global atemp
  61. # atemp = msg.payload
  62. # dont edit below
  63. #starting values only..
  64. ##atemp = 61
  65. ##ahum = 101
  66. ##atemp1 = 61
  67. ##ahum1 = 101
  68. ##atemp2 = 61
  69. ##ahum2 = 101
  70. ##atemp_last = 61
  71. ##ahum_last = 101
  72. checkLastUpdateInterval = 60
  73. checkLastUpdateInterval_lastRun = 0
  74. lastPublishTime = 0
  75. lastStoreTime = 0
  76. sensors = dict()
  77. sensors_id_to_name = dict()
  78. #sensors_idx = dict()
  79. sensors_lastTemp = dict()
  80. sensors_lastHum = dict()
  81. sensors_lastUpdate = dict()
  82. sensors_lastReceivedValue_temp = dict()
  83. sensors_lastReceivedTime = dict()
  84. sensors_lastValues_temp = dict()
  85. sensors_lastValues_hum = dict()
  86. sensors_lastAvgValue_temp = dict()
  87. sensors_lastAvgValue_hum = dict()
  88. sensors_lastValues_lastIndex = dict()
  89. #sensors_unavailable = dict()
  90. sensors_batteryState = dict()
  91. sensors_new_alreadyNotified = dict()
  92. sensors_outside_sensors = []
  93. outSens_lastRun = 0
  94. outSens_interval = 120
  95. influx_default_fieldname_temperature = 'Temperature'
  96. influx_default_fieldname_humidity = 'Humidity'
  97. influx_default_datatype_temperature = 'float'
  98. influx_default_datatype_temperature = 'int'
  99. sensors_yaml = yaml.load(open(config['main'].get('sensors_config_yml')), Loader=yaml.FullLoader)
  100. def list_duplicates(seq):
  101. seen = set()
  102. seen_add = seen.add
  103. # adds all elements it doesn't know yet to seen and all other to seen_twice
  104. seen_twice = set( x for x in seq if x in seen or seen_add(x) )
  105. # turn the set into a list (as requested)
  106. return list( seen_twice )
  107. if verbosemode:
  108. print("JeeLink2MQTT by Flo Kra")
  109. print("=======================================================================")
  110. print("loading InfluxDB configuration...")
  111. influxdb_yaml = yaml.load(open(config['main'].get('influx_config_yml')), Loader=yaml.SafeLoader)
  112. if verbosemode:
  113. print("InfluxDB Instances:")
  114. print(json.dumps(influxdb_yaml, indent=4))
  115. influxclient = dict()
  116. for instance in influxdb_yaml:
  117. i_host = influxdb_yaml[instance].get('host', None)
  118. i_port = int(influxdb_yaml[instance].get('port', 8086))
  119. i_username = influxdb_yaml[instance].get('username', None)
  120. i_password = influxdb_yaml[instance].get('password', None)
  121. i_database = influxdb_yaml[instance].get('database', None)
  122. if i_host != None and i_database != None:
  123. if i_username != None and i_password != None:
  124. influxclient[instance] = InfluxDBClient(i_host, i_port, i_username, i_password, i_database)
  125. else:
  126. influxclient[instance] = InfluxDBClient(i_host, i_port, i_database)
  127. if verbosemode:
  128. print("loading sensors configuration: ")
  129. print(json.dumps(sensors_yaml, indent=3))
  130. for key in sensors_yaml:
  131. #print(key, '->', sensors_yaml[key])
  132. if verbosemode: print("Sensor name:", key)
  133. sensorName = key
  134. if sensors_yaml[key].get('LaCrosseID'):
  135. if verbosemode: print("LaCrosseID:", sensors_yaml[key].get('LaCrosseID'))
  136. sensorId = sensors_yaml[key].get('LaCrosseID')
  137. sensors_id_to_name[sensorId] = sensorName
  138. if sensors_yaml[key].get('DomoticzIdx'):
  139. if verbosemode: print("DomoticzIdx:", sensors_yaml[key].get('DomoticzIdx'))
  140. #sensors_idx[sensorId] = sensors_yaml[key].get('DomoticzIdx')
  141. if sensors_yaml[key].get('Topic_Temp'):
  142. if verbosemode: print("Topic_Temp:", sensors_yaml[key].get('Topic_Temp'))
  143. if sensors_yaml[key].get('Topic_Hum'):
  144. if verbosemode: print("Topic_Hum:", sensors_yaml[key].get('Topic_Hum'))
  145. if sensors_yaml[key].get('InfluxDB_Instance'):
  146. if verbosemode:
  147. print("InfluxDB_Instance:", sensors_yaml[key].get('InfluxDB_Instance'))
  148. if sensors_yaml[key].get('InfluxDB_Instance') not in influxdb_yaml:
  149. print("Error: invalid InfluxDB instance '" + sensors_yaml[key].get('InfluxDB_Instance') + "' configured for sensor '" + sensorName + "'")
  150. if sensors_yaml[key].get('isOutsideTempSensor'):
  151. if verbosemode: print("isOutsideTempSensor:", sensors_yaml[key].get('isOutsideTempSensor'))
  152. sensors_outside_sensors.append(sensorId)
  153. else:
  154. print("WARNING: Sensor " + key + " has no LaCrosseID!")
  155. if verbosemode: print()
  156. #print(json.dumps(sensor))
  157. #print("sensors_id_to_name =",sensors_id_to_name)
  158. #print("outside sensors: ", sensors_outside_sensors)
  159. if verbosemode:
  160. print("\n")
  161. mqttc = mqtt.Client()
  162. mqttc.on_connect = on_connect
  163. mqttc.on_disconnect = on_disconnect
  164. ##mqttc.on_message = on_message
  165. if config['mqtt'].get('user') != "" and config['mqtt'].get('password') != "":
  166. mqttc.username_pw_set(config['mqtt'].get('user'), config['mqtt'].get('password'))
  167. mqttc.connect(config['mqtt'].get('server'), config['mqtt'].getint('port'), 60)
  168. mqttc.loop_start()
  169. #mqttc.loop_forever()
  170. ser = serial.Serial(port=serialport,
  171. baudrate = serialbaud,
  172. parity=serial.PARITY_NONE,
  173. stopbits=serial.STOPBITS_ONE,
  174. bytesize=serial.EIGHTBITS,
  175. timeout=1)
  176. checkLastUpdateInterval_lastRun = time.time() # first check after timeout expired
  177. try:
  178. while True:
  179. msg_was_sent = 0
  180. #clear serial buffer to remove junk and noise
  181. ser.flushInput()
  182. #read buffer until cr/lf
  183. serLine = ser.readline().strip()
  184. # catch exception on invalid char coming in: UnicodeDecodeError: 'ascii' codec can't decode byte 0xf4 in position 6: ordinal not in range(128)
  185. try:
  186. serLine = serLine.decode('ascii')
  187. except:
  188. serLine = False
  189. if(serLine):
  190. if serLine.find('OK 9') != -1:
  191. if verbosemode:
  192. print(serLine + " = LaCrosse sensor")
  193. # uns interessieren nur reinkommende Zeilen die mit "OK 9 " beginnen
  194. # 0 1 2 3 4 5 6
  195. # OK 9 ID XXX XXX XXX XXX
  196. # | | | | | | |
  197. # | | | | | | --- Humidity incl. WeakBatteryFlag
  198. # | | | | | |------ Temp * 10 + 1000 LSB
  199. # | | | | |---------- Temp * 10 + 1000 MSB
  200. # | | | |-------------- Sensor type (1 or 2) +128 if NewBatteryFlag
  201. # | | |----------------- Sensor ID
  202. # | |------------------- fix "9"
  203. # |---------------------- fix "OK"
  204. serLineParts = serLine.split(' ')
  205. #print("Len: " + str(len(serLineParts)))
  206. if(len(serLineParts) == 7): # check correct size of incoming data
  207. #addr = serLineParts[2]
  208. #addr = "{0:x}".format(int(serLineParts[2]))
  209. #addr = hex((int(serLineParts[2])))
  210. addr = int(serLineParts[2])
  211. addrhex = "{0:x}".format(int(serLineParts[2]))
  212. lastUpdate = sensors_lastUpdate.get(addr, None)
  213. lastTemp = sensors_lastTemp.get(addr, None)
  214. lastHum = sensors_lastHum.get(addr, None)
  215. currentsensor_name = sensors_id_to_name.get(addr, None)
  216. # extract sensor data received from JeeLink
  217. if int(serLineParts[3]) >= 128:
  218. batt_new = 1
  219. type = int(serLineParts[3]) - 128
  220. else:
  221. batt_new = 0
  222. type = int(serLineParts[3])
  223. temp = (int(serLineParts[4])*256 + int(serLineParts[5]) - 1000)/10.0
  224. if int(serLineParts[6]) >= 128:
  225. batt_low = 1
  226. hum = int(serLineParts[6]) - 128
  227. else:
  228. batt_low = 0
  229. hum = int(serLineParts[6])
  230. if hum > 100:
  231. hum = 100
  232. if batt_new == 1:
  233. sensors_batteryState[addr] = 2
  234. elif batt_low == 1:
  235. sensors_batteryState[addr] = 1
  236. else:
  237. sensors_batteryState[addr] = 0
  238. lastValues_lastIndex = sensors_lastValues_lastIndex.get(addr, None)
  239. if sensors_lastValues_temp.get(addr, None) == None:
  240. sensors_lastValues_temp[addr] = [None] * average_value_steps
  241. if sensors_lastValues_hum.get(addr, None) == None:
  242. sensors_lastValues_hum[addr] = [None] * average_value_steps
  243. data_okay = False
  244. if sensors_lastReceivedValue_temp.get(addr, None) == None:
  245. # this is the first time we receive from that sensor in that session
  246. if verbosemode: print("first received from sensor",str(addr))
  247. sensors_lastReceivedValue_temp[addr] = temp
  248. sensors_lastReceivedTime[addr] = int(time.time())
  249. #lastValues_lastIndex = 0
  250. else:
  251. lastValue = sensors_lastReceivedValue_temp.get(addr, None)
  252. max_off_value = float(config['sensors'].get('max_off_value'))
  253. ignore_off_value_timeout = int(config['sensors'].get('ignore_off_value_timeout'))
  254. if lastValue != None and ((temp >= (lastValue - max_off_value) and temp <= (lastValue + max_off_value)) or ((int(time.time()) - sensors_lastReceivedTime.get(addr)) > ignore_off_value_timeout)): # discard off values
  255. sensors_lastReceivedValue_temp[addr] = temp
  256. sensors_lastReceivedTime[addr] = int(time.time())
  257. #print("Last Value=",lastValue,"currValue=",temp)
  258. data_okay = True
  259. else:
  260. if verbosemode: print("skipped sensor reading - Last Value=",lastValue,"currValue=",temp)
  261. if data_okay:
  262. if lastValues_lastIndex == None:
  263. lastValues_lastIndex = 0
  264. elif lastValues_lastIndex == (average_value_steps - 1):
  265. lastValues_lastIndex = 0
  266. else:
  267. lastValues_lastIndex += 1
  268. if verbosemode: print("lastValues_lastIndex =", lastValues_lastIndex)
  269. sensors_lastValues_lastIndex[addr] = lastValues_lastIndex
  270. sensors_lastValues_temp[addr][lastValues_lastIndex] = temp
  271. sensors_lastValues_hum[addr][lastValues_lastIndex] = hum
  272. sensors_lastUpdate[addr] = int(time.time())
  273. if verbosemode: print("sensors_lastValues_temp =", sensors_lastValues_temp[addr])
  274. if currentsensor_name is None:
  275. if batt_new == 1 and not sensors_new_alreadyNotified.get('addr', False):
  276. notifystr = "NEW sensor with ID " + str(addr)
  277. mqttc.publish(mqtt_topic_prefix+"/" + mqtt_subtopic_notify, notifystr, qos=0, retain=False)
  278. if not os.path.exists(log_path+'/new'):
  279. os.makedirs(log_path+'/new')
  280. fname = log_path + '/new/' + str(addr)
  281. if not os.path.exists(fname):
  282. try:
  283. touch(fname)
  284. except:
  285. pass
  286. try:
  287. f = open(log_path+'/'+logfile_new_sensors, 'a')
  288. f.write(str(datetime.now())+": "+notifystr+"\n")
  289. f.close()
  290. except:
  291. # guat dann hoit ned...
  292. pass
  293. sensors_new_alreadyNotified[addr] = True
  294. else:
  295. if not os.path.exists(log_path+'/unknown'):
  296. os.makedirs(log_path+'/unknown')
  297. fname = log_path + '/unknown/' + str(addr)
  298. if not os.path.exists(fname):
  299. try:
  300. touch(fname)
  301. except:
  302. pass
  303. if verbosemode:
  304. print("unknown sensor ID " + str(addr))
  305. if verbosemode:
  306. 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))
  307. print()
  308. #if senddata:
  309. # sensors_lastUpdate[str(addr)] = int(time.time())
  310. # sensors_lastTemp[str(addr)] = temp
  311. # sensors_lastHum[str(addr)] = hum
  312. #
  313. # isAtemp = False
  314. # if int(currentsensor_idx) == atemp_sensor_idx:
  315. # atemp1 = temp
  316. # ahum1 = hum
  317. # isAtemp = True
  318. # elif int(currentsensor_idx) == atemp_sensor_idx_2:
  319. # atemp2 = temp
  320. # ahum2 = hum
  321. # isAtemp = True
  322. #
  323. # if isAtemp:
  324. # if atemp1 <= atemp2:
  325. # atemp = atemp1
  326. # ahum = ahum1
  327. # else:
  328. # atemp = atemp2
  329. # ahum = ahum2
  330. #
  331. # if atemp < 61 and ahum < 101:
  332. # if atemp != atemp_last or ahum != ahum_last or ((time.time() - atemphum_lastUpdate) > aTempHumPublishInterval):
  333. # atemphum_lastUpdate = time.time()
  334. # atemp_last = atemp
  335. # ahum_last = ahum
  336. # mqttc.publish(mqtt_topic_atemp, str(atemp), qos=0, retain=True)
  337. # mqttc.publish(mqtt_topic_ahum, str(ahum), qos=0, retain=True)
  338. # mqttc.publish(mqtt_topic_atemphum_lastUpdate, strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False)
  339. #
  340. # domoticz_json = "{\"idx\":" + str(currentsensor_idx) + ",\"nvalue\":0,\"svalue\":\"" + str(temp) + ";" + str(hum) + ";1\"}"
  341. # #if verbosemode:
  342. # # print(domoticz_json)
  343. # mqttc.publish(mqtt_topic_domoticz_in, domoticz_json, qos=0, retain=False)
  344. #
  345. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/temperature", str(temp), qos=0, retain=False)
  346. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/humidity", str(hum), qos=0, retain=False)
  347. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/battery", str(batterystate), qos=0, retain=False)
  348. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/lastUpdate", strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False)
  349. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/availability", "available", qos=0, retain=False)
  350. #
  351. # lacrosse_json = "{\"temperature\":" + str(temp) + ", \"humidity\":" + str(hum) + ", \"battery\":\"" + str(batterystate) + "\"}"
  352. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/json", lacrosse_json, qos=0, retain=False)
  353. #
  354. # tmptext = str(temp) + "° " + str(hum) + "%"
  355. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/TempHumText", tmptext, qos=0, retain=False)
  356. #
  357. # if verbosemode:
  358. # print("MQTT published")
  359. #
  360. # try:
  361. # touch("/tmp/jeelink2mqtt_running")
  362. # except:
  363. # # guat dann ned...
  364. # pass
  365. #
  366. #else:
  367. # if verbosemode:
  368. # if currentsensor_name is None:
  369. # print("MQTT published")
  370. # else:
  371. # print("MQTT publishing surpressed (interval not expired)")
  372. #
  373. #
  374. #if verbosemode:
  375. # print("\n")
  376. else:
  377. if verbosemode: print("ignored invalid sized data")
  378. pass
  379. # publish on MQTT on set interval
  380. publishNow = False
  381. if (int(time.time()) - lastPublishTime) > int(config['sensors'].get('publish_interval')):
  382. if lastPublishTime == 0:
  383. lastPublishTime = int(time.time())
  384. else:
  385. publishNow = True
  386. lastPublishTime = int(time.time())
  387. #for sensor in
  388. #sensors_lastReceivedTime[str(addr)]
  389. # store to InfluxDB on set interval
  390. storeNow = False
  391. if (int(time.time()) - lastStoreTime) > int(config['sensors'].get('store_interval')):
  392. if lastStoreTime == 0:
  393. lastStoreTime = int(time.time())
  394. else:
  395. storeNow = True
  396. lastStoreTime = int(time.time())
  397. if publishNow or storeNow:
  398. #print("available sensor data: ", sensors_lastUpdate)
  399. for id in sensors_lastUpdate:
  400. if (time.time() - sensors_lastUpdate[id]) < sensordata_maxage:
  401. s_name = sensors_id_to_name.get(id, None)
  402. if verbosemode: print("current data available for:", id, s_name)
  403. sensorDataComplete = True
  404. sum_temp=0
  405. for val in sensors_lastValues_temp[id]:
  406. if val is None:
  407. sensorDataComplete = False
  408. else:
  409. sum_temp = sum_temp + val
  410. sum_hum=0
  411. for val in sensors_lastValues_hum[id]:
  412. if val is None:
  413. sensorDataComplete = False
  414. else:
  415. sum_hum = sum_hum + val
  416. if sensorDataComplete:
  417. s_currAvgTemp = round(sum_temp / len(sensors_lastValues_temp[id]), 1)
  418. s_currAvgHum = int(sum_hum / len(sensors_lastValues_hum[id]))
  419. sensors_lastAvgValue_temp[id] = s_currAvgTemp
  420. sensors_lastAvgValue_hum[id] = s_currAvgHum
  421. if verbosemode: print("s_currAvgTemp =", s_currAvgTemp, "s_currAvgHum =", s_currAvgHum)
  422. else:
  423. if verbosemode: print("s_currAvgTemp/s_currAvgHum: not yet enough readings available")
  424. if sensorDataComplete:
  425. # as Home Assistant MQTT binary sensor can only work with 2 states (unless using value templates):
  426. # -> removed "NEW" state string from /battery topic and added /batteryNew topic
  427. if sensors_batteryState.get(id,None) == 2:
  428. s_battNew = True
  429. s_battState = "OK"
  430. elif sensors_batteryState.get(id, None) == 1:
  431. s_battNew = False
  432. s_battState = "LOW"
  433. elif sensors_batteryState.get(id, None) == 0:
  434. s_battNew = False
  435. s_battState = "OK"
  436. if s_battNew:
  437. s_battNew_str = "YES"
  438. else:
  439. s_battNew_str = "NO"
  440. if s_name is not None:
  441. s_domIdx = sensors_yaml[s_name].get('DomoticzIdx', None)
  442. s_topic_temp = sensors_yaml[s_name].get('Topic_Temp', None)
  443. s_topic_hum = sensors_yaml[s_name].get('Topic_Hum', None)
  444. s_influxInstance = sensors_yaml[s_name].get('InfluxDB_Instance', None)
  445. s_isOutsideTempSensor = sensors_yaml[s_name].get('isOutsideTempSensor', None)
  446. if s_domIdx is not None and publishNow:
  447. domoticz_json = "{\"idx\":" + str(s_domIdx) + ",\"nvalue\":0,\"svalue\":\"" + str(s_currAvgTemp) + ";" + str(s_currAvgHum) + ";1\"}"
  448. if verbosemode:
  449. print("Domoticz JSON:", domoticz_json)
  450. mqttc.publish(mqtt_topic_domoticz_in, domoticz_json, qos=0, retain=False)
  451. if s_topic_temp is not None and len(s_topic_temp)>5 and publishNow:
  452. if verbosemode: print("publishing temp on ", s_topic_temp)
  453. mqttc.publish(s_topic_temp, str(s_currAvgTemp), qos=0, retain=False)
  454. if s_topic_hum is not None and len(s_topic_hum)>5 and publishNow:
  455. if verbosemode: print("publishing hum on ", s_topic_temp)
  456. mqttc.publish(s_topic_hum, str(s_currAvgHum), qos=0, retain=False)
  457. if publishNow:
  458. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/temperature", str(s_currAvgTemp), qos=0, retain=False)
  459. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/humidity", str(s_currAvgHum), qos=0, retain=False)
  460. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/battery", s_battState, qos=0, retain=False)
  461. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/batteryNew", s_battNew_str, qos=0, retain=False)
  462. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/availability", "available", qos=0, retain=False)
  463. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/lastUpdate", strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False)
  464. lacrosse_json = "{\"temperature\":" + str(s_currAvgTemp) + ", \"humidity\":" + str(s_currAvgHum) + ", \"battery\":\"" + str(s_battState) + "\"}"
  465. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/json", lacrosse_json, qos=0, retain=False)
  466. tmptext = str(s_currAvgTemp) + "° " + str(s_currAvgHum) + "%"
  467. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/TempHumText", tmptext, qos=0, retain=False)
  468. if s_influxInstance is not None and storeNow:
  469. ### write to InfluxDB here
  470. if s_influxInstance in influxdb_yaml:
  471. influx_measurement = influxdb_yaml[s_influxInstance].get('measurement', None)
  472. t_utc = datetime.utcnow()
  473. t_str = t_utc.isoformat() + 'Z'
  474. if influx_measurement is not None:
  475. influx_fieldnames = influxdb_yaml[s_influxInstance].get('fieldnames', None)
  476. if influx_fieldnames == None:
  477. influx_fieldname_temperature = influx_default_fieldname_temperature
  478. influx_fieldname_humidity = influx_default_fieldname_humidity
  479. else:
  480. if influxdb_yaml[s_influxInstance]['fieldnames'].get('temperature', None) != None:
  481. influx_fieldname_temperature = influxdb_yaml[s_influxInstance]['fieldnames'].get('temperature')
  482. else:
  483. influx_fieldname_temperature = influx_default_fieldname_temperature
  484. if influxdb_yaml[s_influxInstance]['fieldnames'].get('humidity', None) != None:
  485. influx_fieldname_humidity = influxdb_yaml[s_influxInstance]['fieldnames'].get('humidity')
  486. else:
  487. influx_fieldname_humidity = influx_default_fieldname_humidity
  488. influx_datatypes = influxdb_yaml[s_influxInstance].get('datatypes', None)
  489. if influx_datatypes == None:
  490. influx_datatype_temperature = influx_default_datatype_temperature
  491. influx_datatype_humidity = influx_default_datatype_humidity
  492. else:
  493. if influxdb_yaml[s_influxInstance]['datatypes'].get('temperature', None) != None:
  494. tmpdt = influxdb_yaml[s_influxInstance]['datatypes'].get('temperature')
  495. if tmpdt == 'float' or tmpdt == 'int':
  496. influx_datatype_temperature = tmpdt
  497. else:
  498. influx_datatype_temperature = influx_default_datatype_temperature
  499. else:
  500. influx_datatype_temperature = influx_default_datatype_temperature
  501. if influxdb_yaml[s_influxInstance]['datatypes'].get('humidity', None) != None:
  502. tmpdt = influxdb_yaml[s_influxInstance]['datatypes'].get('humidity')
  503. if tmpdt == 'float' or tmpdt == 'int':
  504. influx_datatype_humidity = tmpdt
  505. else:
  506. influx_datatype_humidity = influx_default_datatype_humidity
  507. else:
  508. influx_datatype_humidity = influx_default_datatype_humidity
  509. if influx_datatype_temperature == 'int':
  510. influx_value_temp = int(s_currAvgTemp)
  511. else:
  512. influx_value_temp = float(s_currAvgTemp)
  513. if influx_datatype_humidity == 'int':
  514. influx_value_hum = int(s_currAvgHum)
  515. else:
  516. influx_value_hum = float(s_currAvgHum)
  517. influx_json = [
  518. {
  519. 'measurement': influx_measurement,
  520. 'tags': {
  521. 'sensor': s_name
  522. },
  523. 'time': t_str,
  524. 'fields': {
  525. influx_fieldname_temperature: influx_value_temp,
  526. influx_fieldname_humidity: influx_value_hum
  527. }
  528. }
  529. ]
  530. try:
  531. if verbosemode: print("write to InfluxDB...")
  532. influxclient[s_influxInstance].write_points(influx_json)
  533. if verbosemode: print("DONE!")
  534. except Exception as e:
  535. print("Error writing to InfluxDB")
  536. print(e)
  537. else:
  538. if verbosemode:
  539. print("Error: invalid InfluxDB instance '" + s_influxInstance + "' configured for sensor '" + s_name + "'")
  540. else: # this is an unknown sensor
  541. if publishNow:
  542. if s_battNew:
  543. mqttc.publish(mqtt_topic_prefix+"/NewUnknownSensor/"+str(id)+"/temperature", str(s_currAvgTemp), qos=0, retain=False)
  544. mqttc.publish(mqtt_topic_prefix+"/NewUnknownSensor/"+str(id)+"/humidity", str(s_currAvgHum), qos=0, retain=False)
  545. mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(id)+"/batteryNew", s_battNew_str, qos=0, retain=False)
  546. else:
  547. mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(id)+"/temperature", str(s_currAvgTemp), qos=0, retain=False)
  548. mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(id)+"/humidity", str(s_currAvgHum), qos=0, retain=False)
  549. mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(id)+"/battery", s_battState, qos=0, retain=False)
  550. if verbosemode: print()
  551. # outside sensors
  552. if (int(time.time()) - outSens_lastRun) > outSens_interval:
  553. outSens_lastRun = int(time.time())
  554. sum_out_sensors_temp = 0
  555. sum_out_sensors_temp_min = 100
  556. sum_out_sensors_temp_max = -50
  557. sum_out_sensors_hum = 0
  558. sum_out_sensors_hum_min = 100
  559. sum_out_sensors_hum_max = -50
  560. count_used_out_sensors = 0
  561. for id in sensors_outside_sensors:
  562. lupd = sensors_lastUpdate.get(id, None)
  563. tdiff = 0
  564. if lupd is not None:
  565. tdiff = int(time.time()) - lupd
  566. if lupd is not None and (tdiff < sensordata_maxage):
  567. tmpval_t = sensors_lastAvgValue_temp.get(id, None)
  568. tmpval_h = sensors_lastAvgValue_hum.get(id, None)
  569. if tmpval_t is not None and tmpval_h is not None:
  570. sum_out_sensors_temp = sum_out_sensors_temp + tmpval_t
  571. sum_out_sensors_hum = sum_out_sensors_hum + tmpval_h
  572. if tmpval_t < sum_out_sensors_temp_min:
  573. sum_out_sensors_temp_min = tmpval_t
  574. if tmpval_t < sum_out_sensors_hum_min:
  575. sum_out_sensors_hum_min = tmpval_h
  576. if tmpval_t > sum_out_sensors_temp_max:
  577. sum_out_sensors_temp_max = tmpval_t
  578. if tmpval_t > sum_out_sensors_hum_max:
  579. sum_out_sensors_hum_max = tmpval_h
  580. count_used_out_sensors += 1
  581. lacrosse_json = None
  582. if count_used_out_sensors > 0:
  583. out_temp_avg = round(sum_out_sensors_temp / count_used_out_sensors, 1)
  584. out_hum_avg = int(round(sum_out_sensors_hum / count_used_out_sensors))
  585. mqttc.publish(mqtt_topic_atemp, str(out_temp_avg), qos=0, retain=True)
  586. mqttc.publish(mqtt_topic_ahum, str(out_hum_avg), qos=0, retain=True)
  587. mqttc.publish(topic_prefix_outside_temphum + '/temperature', str(out_temp_avg), qos=0, retain=True)
  588. mqttc.publish(topic_prefix_outside_temphum + '/humidity', str(out_hum_avg), qos=0, retain=True)
  589. mqttc.publish(topic_prefix_outside_temphum + '/lastUpdate', strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=True)
  590. tmptext = str(out_temp_avg) + "° " + str(out_hum_avg) + "%"
  591. mqttc.publish(topic_prefix_outside_temphum + "/TempHumText", tmptext, qos=0, retain=False)
  592. lacrosse_json = "{\"temperature\":" + str(out_temp_avg) + ", \"humidity\":" + str(out_hum_avg) + "\", \"usedSensors\":" + str(count_used_out_sensors) + "}"
  593. min = 100
  594. max = 100
  595. if count_used_out_sensors > 1:
  596. mqttc.publish(topic_prefix_outside_temphum + '/usedSensors', str(count_used_out_sensors), qos=0, retain=True)
  597. lacrosse_json = "{\"temperature\":" + str(out_temp_avg) + ", \"humidity\":" + str(out_hum_avg) + ", \"usedSensors\":" + str(count_used_out_sensors)
  598. if sum_out_sensors_temp_min < 100:
  599. mqttc.publish(topic_prefix_outside_temphum + '/temp_min', str(sum_out_sensors_temp_min), qos=0, retain=False)
  600. lacrosse_json = lacrosse_json + ", \"temp_min\":" + str(sum_out_sensors_temp_min)
  601. if sum_out_sensors_temp_max > -50:
  602. mqttc.publish(topic_prefix_outside_temphum + '/temp_max', str(sum_out_sensors_temp_max), qos=0, retain=False)
  603. lacrosse_json = lacrosse_json + ", \"temp_max\":" + str(sum_out_sensors_temp_max)
  604. if sum_out_sensors_hum_min < 100:
  605. mqttc.publish(topic_prefix_outside_temphum + '/hum_min', str(sum_out_sensors_hum_min), qos=0, retain=False)
  606. lacrosse_json = lacrosse_json + ", \"hum_min\":" + str(sum_out_sensors_hum_min)
  607. if sum_out_sensors_hum_max > -50:
  608. mqttc.publish(topic_prefix_outside_temphum + '/hum_max', str(sum_out_sensors_hum_max), qos=0, retain=False)
  609. lacrosse_json = lacrosse_json + ", \"hum_max\":" + str(sum_out_sensors_hum_max)
  610. lacrosse_json = lacrosse_json + "}"
  611. if lacrosse_json is not None:
  612. mqttc.publish(topic_prefix_outside_temphum + "/json", lacrosse_json, qos=0, retain=False)
  613. # handle outdated sensor values once a minute
  614. if (int(time.time()) - checkLastUpdateInterval_lastRun) > checkLastUpdateInterval:
  615. checkLastUpdateInterval_lastRun = int(time.time())
  616. #print("check lastUpdate")
  617. for key in sensors_yaml:
  618. #print(key, '->', sensors_yaml[key])
  619. #print("Sensor name:", key)
  620. sensorId = sensors_yaml[key].get('LaCrosseID', None)
  621. if sensorId is not None:
  622. lupd = sensors_lastUpdate.get(sensorId, None)
  623. tdiff = 0
  624. if lupd is not None:
  625. tdiff = int(time.time()) - lupd
  626. if lupd is None or (tdiff > sensordata_maxage):
  627. mqttc.publish(mqtt_topic_prefix+"/"+ key +"/availability", "unavailable", qos=0, retain=False)
  628. notifystr = "received no data from sensor '" + key + "' with ID " + str(sensorId) + " for " + str(tdiff) + "s"
  629. mqttc.publish(mqtt_topic_prefix+"/" + mqtt_subtopic_notify, notifystr, qos=0, retain=False)
  630. except KeyboardInterrupt:
  631. print('\n')
  632. exit()