jeelinklog.py 41 KB

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