jeelinklog.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771
  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 = ""
  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. #addr = serLineParts[2]
  206. #addr = "{0:x}".format(int(serLineParts[2]))
  207. #addr = hex((int(serLineParts[2])))
  208. addr = int(serLineParts[2])
  209. addrhex = "{0:x}".format(int(serLineParts[2]))
  210. lastUpdate = sensors_lastUpdate.get(addr, None)
  211. lastTemp = sensors_lastTemp.get(addr, None)
  212. lastHum = sensors_lastHum.get(addr, None)
  213. currentsensor_name = sensors_id_to_name.get(addr, None)
  214. # extract sensor data received from JeeLink
  215. if int(serLineParts[3]) >= 128:
  216. batt_new = 1
  217. type = int(serLineParts[3]) - 128
  218. else:
  219. batt_new = 0
  220. type = int(serLineParts[3])
  221. temp = (int(serLineParts[4])*256 + int(serLineParts[5]) - 1000)/10.0
  222. if int(serLineParts[6]) >= 128:
  223. batt_low = 1
  224. hum = int(serLineParts[6]) - 128
  225. else:
  226. batt_low = 0
  227. hum = int(serLineParts[6])
  228. if hum > 100:
  229. hum = 100
  230. if batt_new == 1:
  231. sensors_batteryState[addr] = 2
  232. elif batt_low == 1:
  233. sensors_batteryState[addr] = 1
  234. else:
  235. sensors_batteryState[addr] = 0
  236. lastValues_lastIndex = sensors_lastValues_lastIndex.get(addr, None)
  237. if sensors_lastValues_temp.get(addr, None) == None:
  238. sensors_lastValues_temp[addr] = [None] * average_value_steps
  239. if sensors_lastValues_hum.get(addr, None) == None:
  240. sensors_lastValues_hum[addr] = [None] * average_value_steps
  241. data_okay = False
  242. if sensors_lastReceivedValue_temp.get(addr, None) == None:
  243. # this is the first time we receive from that sensor in that session
  244. if verbosemode: print("first received from sensor",str(addr))
  245. sensors_lastReceivedValue_temp[addr] = temp
  246. sensors_lastReceivedTime[addr] = int(time.time())
  247. #lastValues_lastIndex = 0
  248. else:
  249. lastValue = sensors_lastReceivedValue_temp.get(addr, None)
  250. max_off_value = float(config['sensors'].get('max_off_value'))
  251. ignore_off_value_timeout = int(config['sensors'].get('ignore_off_value_timeout'))
  252. 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
  253. sensors_lastReceivedValue_temp[addr] = temp
  254. sensors_lastReceivedTime[addr] = int(time.time())
  255. #print("Last Value=",lastValue,"currValue=",temp)
  256. data_okay = True
  257. else:
  258. if verbosemode: print("skipped sensor reading - Last Value=",lastValue,"currValue=",temp)
  259. if data_okay:
  260. if lastValues_lastIndex == None:
  261. lastValues_lastIndex = 0
  262. elif lastValues_lastIndex == (average_value_steps - 1):
  263. lastValues_lastIndex = 0
  264. else:
  265. lastValues_lastIndex += 1
  266. if verbosemode: print("lastValues_lastIndex =", lastValues_lastIndex)
  267. sensors_lastValues_lastIndex[addr] = lastValues_lastIndex
  268. sensors_lastValues_temp[addr][lastValues_lastIndex] = temp
  269. sensors_lastValues_hum[addr][lastValues_lastIndex] = hum
  270. sensors_lastUpdate[addr] = int(time.time())
  271. if verbosemode: print("sensors_lastValues_temp =", sensors_lastValues_temp[addr])
  272. if currentsensor_name is None:
  273. if batt_new == 1 and not sensors_new_alreadyNotified.get('addr', False):
  274. notifystr = "NEW sensor with ID " + str(addr)
  275. mqttc.publish(mqtt_topic_prefix+"/" + mqtt_subtopic_notify, notifystr, qos=0, retain=False)
  276. if not os.path.exists(log_path+'/new'):
  277. os.makedirs(log_path+'/new')
  278. fname = log_path + '/new/' + str(addr)
  279. if not os.path.exists(fname):
  280. try:
  281. touch(fname)
  282. except:
  283. pass
  284. try:
  285. f = open(log_path+'/'+logfile_new_sensors, 'a')
  286. f.write(str(datetime.now())+": "+notifystr+"\n")
  287. f.close()
  288. except:
  289. # guat dann hoit ned...
  290. pass
  291. sensors_new_alreadyNotified[addr] = True
  292. else:
  293. if not os.path.exists(log_path+'/unknown'):
  294. os.makedirs(log_path+'/unknown')
  295. fname = log_path + '/unknown/' + str(addr)
  296. if not os.path.exists(fname):
  297. try:
  298. touch(fname)
  299. except:
  300. pass
  301. if verbosemode:
  302. print("unknown sensor ID " + str(addr))
  303. if verbosemode:
  304. 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))
  305. print()
  306. #if senddata:
  307. # sensors_lastUpdate[str(addr)] = int(time.time())
  308. # sensors_lastTemp[str(addr)] = temp
  309. # sensors_lastHum[str(addr)] = hum
  310. #
  311. # isAtemp = False
  312. # if int(currentsensor_idx) == atemp_sensor_idx:
  313. # atemp1 = temp
  314. # ahum1 = hum
  315. # isAtemp = True
  316. # elif int(currentsensor_idx) == atemp_sensor_idx_2:
  317. # atemp2 = temp
  318. # ahum2 = hum
  319. # isAtemp = True
  320. #
  321. # if isAtemp:
  322. # if atemp1 <= atemp2:
  323. # atemp = atemp1
  324. # ahum = ahum1
  325. # else:
  326. # atemp = atemp2
  327. # ahum = ahum2
  328. #
  329. # if atemp < 61 and ahum < 101:
  330. # if atemp != atemp_last or ahum != ahum_last or ((time.time() - atemphum_lastUpdate) > aTempHumPublishInterval):
  331. # atemphum_lastUpdate = time.time()
  332. # atemp_last = atemp
  333. # ahum_last = ahum
  334. # mqttc.publish(mqtt_topic_atemp, str(atemp), qos=0, retain=True)
  335. # mqttc.publish(mqtt_topic_ahum, str(ahum), qos=0, retain=True)
  336. # mqttc.publish(mqtt_topic_atemphum_lastUpdate, strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False)
  337. #
  338. # domoticz_json = "{\"idx\":" + str(currentsensor_idx) + ",\"nvalue\":0,\"svalue\":\"" + str(temp) + ";" + str(hum) + ";1\"}"
  339. # #if verbosemode:
  340. # # print(domoticz_json)
  341. # mqttc.publish(mqtt_topic_domoticz_in, domoticz_json, qos=0, retain=False)
  342. #
  343. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/temperature", str(temp), qos=0, retain=False)
  344. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/humidity", str(hum), qos=0, retain=False)
  345. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/battery", str(batterystate), qos=0, retain=False)
  346. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/lastUpdate", strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False)
  347. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/availability", "available", qos=0, retain=False)
  348. #
  349. # lacrosse_json = "{\"temperature\":" + str(temp) + ", \"humidity\":" + str(hum) + ", \"battery\":\"" + str(batterystate) + "\"}"
  350. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/json", lacrosse_json, qos=0, retain=False)
  351. #
  352. # tmptext = str(temp) + "° " + str(hum) + "%"
  353. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/TempHumText", tmptext, qos=0, retain=False)
  354. #
  355. # if verbosemode:
  356. # print("MQTT published")
  357. #
  358. # try:
  359. # touch("/tmp/jeelink2mqtt_running")
  360. # except:
  361. # # guat dann ned...
  362. # pass
  363. #
  364. #else:
  365. # if verbosemode:
  366. # if currentsensor_name is None:
  367. # print("MQTT published")
  368. # else:
  369. # print("MQTT publishing surpressed (interval not expired)")
  370. #
  371. #
  372. #if verbosemode:
  373. # print("\n")
  374. # publish on MQTT on set interval
  375. publishNow = False
  376. if (int(time.time()) - lastPublishTime) > int(config['sensors'].get('publish_interval')):
  377. if lastPublishTime == 0:
  378. lastPublishTime = int(time.time())
  379. else:
  380. publishNow = True
  381. lastPublishTime = int(time.time())
  382. #for sensor in
  383. #sensors_lastReceivedTime[str(addr)]
  384. # store to InfluxDB on set interval
  385. storeNow = False
  386. if (int(time.time()) - lastStoreTime) > int(config['sensors'].get('store_interval')):
  387. if lastStoreTime == 0:
  388. lastStoreTime = int(time.time())
  389. else:
  390. storeNow = True
  391. lastStoreTime = int(time.time())
  392. if publishNow or storeNow:
  393. #print("available sensor data: ", sensors_lastUpdate)
  394. for id in sensors_lastUpdate:
  395. if (time.time() - sensors_lastUpdate[id]) < sensordata_maxage:
  396. s_name = sensors_id_to_name.get(id, None)
  397. if verbosemode: print("current data available for:", id, s_name)
  398. sensorDataComplete = True
  399. sum_temp=0
  400. for val in sensors_lastValues_temp[id]:
  401. if val is None:
  402. sensorDataComplete = False
  403. else:
  404. sum_temp = sum_temp + val
  405. sum_hum=0
  406. for val in sensors_lastValues_hum[id]:
  407. if val is None:
  408. sensorDataComplete = False
  409. else:
  410. sum_hum = sum_hum + val
  411. if sensorDataComplete:
  412. s_currAvgTemp = round(sum_temp / len(sensors_lastValues_temp[id]), 1)
  413. s_currAvgHum = int(sum_hum / len(sensors_lastValues_hum[id]))
  414. sensors_lastAvgValue_temp[id] = s_currAvgTemp
  415. sensors_lastAvgValue_hum[id] = s_currAvgHum
  416. if verbosemode: print("s_currAvgTemp =", s_currAvgTemp, "s_currAvgHum =", s_currAvgHum)
  417. else:
  418. if verbosemode: print("s_currAvgTemp/s_currAvgHum: not yet enough readings available")
  419. if sensorDataComplete:
  420. if sensors_batteryState.get(id,None) == 2:
  421. s_battNew = True
  422. s_battState = "NEW"
  423. elif sensors_batteryState.get(id, None) == 1:
  424. s_battNew = False
  425. s_battState = "LOW"
  426. elif sensors_batteryState.get(id, None) == 0:
  427. s_battNew = False
  428. s_battState = "OK"
  429. if s_name is not None:
  430. s_domIdx = sensors_yaml[s_name].get('DomoticzIdx', None)
  431. s_topic_temp = sensors_yaml[s_name].get('Topic_Temp', None)
  432. s_topic_hum = sensors_yaml[s_name].get('Topic_Hum', None)
  433. s_influxInstance = sensors_yaml[s_name].get('InfluxDB_Instance', None)
  434. s_isOutsideTempSensor = sensors_yaml[s_name].get('isOutsideTempSensor', None)
  435. if s_domIdx is not None and publishNow:
  436. domoticz_json = "{\"idx\":" + str(s_domIdx) + ",\"nvalue\":0,\"svalue\":\"" + str(s_currAvgTemp) + ";" + str(s_currAvgHum) + ";1\"}"
  437. if verbosemode:
  438. print("Domoticz JSON:", domoticz_json)
  439. mqttc.publish(mqtt_topic_domoticz_in, domoticz_json, qos=0, retain=False)
  440. if s_topic_temp is not None and len(s_topic_temp)>5 and publishNow:
  441. if verbosemode: print("publishing temp on ", s_topic_temp)
  442. mqttc.publish(s_topic_temp, str(s_currAvgTemp), qos=0, retain=False)
  443. if s_topic_hum is not None and len(s_topic_hum)>5 and publishNow:
  444. if verbosemode: print("publishing hum on ", s_topic_temp)
  445. mqttc.publish(s_topic_hum, str(s_currAvgHum), qos=0, retain=False)
  446. if publishNow:
  447. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/temperature", str(s_currAvgTemp), qos=0, retain=False)
  448. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/humidity", str(s_currAvgHum), qos=0, retain=False)
  449. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/battery", s_battState, qos=0, retain=False)
  450. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/availability", "available", qos=0, retain=False)
  451. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/lastUpdate", strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False)
  452. lacrosse_json = "{\"temperature\":" + str(s_currAvgTemp) + ", \"humidity\":" + str(s_currAvgHum) + ", \"battery\":\"" + str(s_battState) + "\"}"
  453. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/json", lacrosse_json, qos=0, retain=False)
  454. tmptext = str(s_currAvgTemp) + "° " + str(s_currAvgHum) + "%"
  455. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/TempHumText", tmptext, qos=0, retain=False)
  456. if s_influxInstance is not None and storeNow:
  457. ### write to InfluxDB here
  458. if s_influxInstance in influxdb_yaml:
  459. influx_measurement = influxdb_yaml[s_influxInstance].get('measurement', None)
  460. t_utc = datetime.utcnow()
  461. t_str = t_utc.isoformat() + 'Z'
  462. if influx_measurement is not None:
  463. influx_fieldnames = influxdb_yaml[s_influxInstance].get('fieldnames', None)
  464. if influx_fieldnames == None:
  465. influx_fieldname_temperature = influx_default_fieldname_temperature
  466. influx_fieldname_humidity = influx_default_fieldname_humidity
  467. else:
  468. if influxdb_yaml[s_influxInstance]['fieldnames'].get('temperature', None) != None:
  469. influx_fieldname_temperature = influxdb_yaml[s_influxInstance]['fieldnames'].get('temperature')
  470. else:
  471. influx_fieldname_temperature = influx_default_fieldname_temperature
  472. if influxdb_yaml[s_influxInstance]['fieldnames'].get('humidity', None) != None:
  473. influx_fieldname_humidity = influxdb_yaml[s_influxInstance]['fieldnames'].get('humidity')
  474. else:
  475. influx_fieldname_humidity = influx_default_fieldname_humidity
  476. influx_datatypes = influxdb_yaml[s_influxInstance].get('datatypes', None)
  477. if influx_datatypes == None:
  478. influx_datatype_temperature = influx_default_datatype_temperature
  479. influx_datatype_humidity = influx_default_datatype_humidity
  480. else:
  481. if influxdb_yaml[s_influxInstance]['datatypes'].get('temperature', None) != None:
  482. tmpdt = influxdb_yaml[s_influxInstance]['datatypes'].get('temperature')
  483. if tmpdt == 'float' or tmpdt == 'int':
  484. influx_datatype_temperature = tmpdt
  485. else:
  486. influx_datatype_temperature = influx_default_datatype_temperature
  487. else:
  488. influx_datatype_temperature = influx_default_datatype_temperature
  489. if influxdb_yaml[s_influxInstance]['datatypes'].get('humidity', None) != None:
  490. tmpdt = influxdb_yaml[s_influxInstance]['datatypes'].get('humidity')
  491. if tmpdt == 'float' or tmpdt == 'int':
  492. influx_datatype_humidity = tmpdt
  493. else:
  494. influx_datatype_humidity = influx_default_datatype_humidity
  495. else:
  496. influx_datatype_humidity = influx_default_datatype_humidity
  497. if influx_datatype_temperature == 'int':
  498. influx_value_temp = int(s_currAvgTemp)
  499. else:
  500. influx_value_temp = float(s_currAvgTemp)
  501. if influx_datatype_humidity == 'int':
  502. influx_value_hum = int(s_currAvgHum)
  503. else:
  504. influx_value_hum = float(s_currAvgHum)
  505. influx_json = [
  506. {
  507. 'measurement': influx_measurement,
  508. 'tags': {
  509. 'sensor': s_name
  510. },
  511. 'time': t_str,
  512. 'fields': {
  513. influx_fieldname_temperature: influx_value_temp,
  514. influx_fieldname_humidity: influx_value_hum
  515. }
  516. }
  517. ]
  518. try:
  519. if verbosemode: print("write to InfluxDB...")
  520. influxclient[s_influxInstance].write_points(influx_json)
  521. if verbosemode: print("DONE!")
  522. except Exception as e:
  523. print("Error writing to InfluxDB")
  524. print(e)
  525. else:
  526. if verbosemode:
  527. print("Error: invalid InfluxDB instance '" + s_influxInstance + "' configured for sensor '" + s_name + "'")
  528. else: # this is an unknown sensor
  529. if publishNow:
  530. if s_battNew:
  531. mqttc.publish(mqtt_topic_prefix+"/NewUnknownSensor/"+str(id)+"/temperature", str(s_currAvgTemp), qos=0, retain=False)
  532. mqttc.publish(mqtt_topic_prefix+"/NewUnknownSensor/"+str(id)+"/humidity", str(s_currAvgHum), qos=0, retain=False)
  533. else:
  534. mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(id)+"/temperature", str(s_currAvgTemp), qos=0, retain=False)
  535. mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(id)+"/humidity", str(s_currAvgHum), qos=0, retain=False)
  536. mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(id)+"/battery", s_battState, qos=0, retain=False)
  537. if verbosemode: print()
  538. # outside sensors
  539. if (int(time.time()) - outSens_lastRun) > outSens_interval:
  540. outSens_lastRun = int(time.time())
  541. sum_out_sensors_temp = 0
  542. sum_out_sensors_temp_min = 100
  543. sum_out_sensors_temp_max = -50
  544. sum_out_sensors_hum = 0
  545. sum_out_sensors_hum_min = 100
  546. sum_out_sensors_hum_max = -50
  547. count_used_out_sensors = 0
  548. for id in sensors_outside_sensors:
  549. lupd = sensors_lastUpdate.get(id, None)
  550. tdiff = 0
  551. if lupd is not None:
  552. tdiff = int(time.time()) - lupd
  553. if lupd is not None and (tdiff < sensordata_maxage):
  554. tmpval_t = sensors_lastAvgValue_temp.get(id, None)
  555. tmpval_h = sensors_lastAvgValue_hum.get(id, None)
  556. if tmpval_t is not None and tmpval_h is not None:
  557. sum_out_sensors_temp = sum_out_sensors_temp + tmpval_t
  558. sum_out_sensors_hum = sum_out_sensors_hum + tmpval_h
  559. if tmpval_t < sum_out_sensors_temp_min:
  560. sum_out_sensors_temp_min = tmpval_t
  561. if tmpval_t < sum_out_sensors_hum_min:
  562. sum_out_sensors_hum_min = tmpval_h
  563. if tmpval_t > sum_out_sensors_temp_max:
  564. sum_out_sensors_temp_max = tmpval_t
  565. if tmpval_t > sum_out_sensors_hum_max:
  566. sum_out_sensors_hum_max = tmpval_h
  567. count_used_out_sensors += 1
  568. lacrosse_json = None
  569. if count_used_out_sensors > 0:
  570. out_temp_avg = round(sum_out_sensors_temp / count_used_out_sensors, 1)
  571. out_hum_avg = int(round(sum_out_sensors_hum / count_used_out_sensors))
  572. mqttc.publish(mqtt_topic_atemp, str(out_temp_avg), qos=0, retain=True)
  573. mqttc.publish(mqtt_topic_ahum, str(out_hum_avg), qos=0, retain=True)
  574. mqttc.publish(topic_prefix_outside_temphum + '/temperature', str(out_temp_avg), qos=0, retain=True)
  575. mqttc.publish(topic_prefix_outside_temphum + '/humidity', str(out_hum_avg), qos=0, retain=True)
  576. mqttc.publish(topic_prefix_outside_temphum + '/lastUpdate', strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=True)
  577. tmptext = str(out_temp_avg) + "° " + str(out_hum_avg) + "%"
  578. mqttc.publish(topic_prefix_outside_temphum + "/TempHumText", tmptext, qos=0, retain=False)
  579. lacrosse_json = "{\"temperature\":" + str(out_temp_avg) + ", \"humidity\":" + str(out_hum_avg) + "\", \"usedSensors\":" + str(count_used_out_sensors) + "}"
  580. min = 100
  581. max = 100
  582. if count_used_out_sensors > 1:
  583. mqttc.publish(topic_prefix_outside_temphum + '/usedSensors', str(count_used_out_sensors), qos=0, retain=True)
  584. lacrosse_json = "{\"temperature\":" + str(out_temp_avg) + ", \"humidity\":" + str(out_hum_avg) + ", \"usedSensors\":" + str(count_used_out_sensors)
  585. if sum_out_sensors_temp_min < 100:
  586. mqttc.publish(topic_prefix_outside_temphum + '/temp_min', str(sum_out_sensors_temp_min), qos=0, retain=False)
  587. lacrosse_json = lacrosse_json + ", \"temp_min\":" + str(sum_out_sensors_temp_min)
  588. if sum_out_sensors_temp_max > -50:
  589. mqttc.publish(topic_prefix_outside_temphum + '/temp_max', str(sum_out_sensors_temp_max), qos=0, retain=False)
  590. lacrosse_json = lacrosse_json + ", \"temp_max\":" + str(sum_out_sensors_temp_max)
  591. if sum_out_sensors_hum_min < 100:
  592. mqttc.publish(topic_prefix_outside_temphum + '/hum_min', str(sum_out_sensors_hum_min), qos=0, retain=False)
  593. lacrosse_json = lacrosse_json + ", \"hum_min\":" + str(sum_out_sensors_hum_min)
  594. if sum_out_sensors_hum_max > -50:
  595. mqttc.publish(topic_prefix_outside_temphum + '/hum_max', str(sum_out_sensors_hum_max), qos=0, retain=False)
  596. lacrosse_json = lacrosse_json + ", \"hum_max\":" + str(sum_out_sensors_hum_max)
  597. lacrosse_json = lacrosse_json + "}"
  598. if lacrosse_json is not None:
  599. mqttc.publish(topic_prefix_outside_temphum + "/json", lacrosse_json, qos=0, retain=False)
  600. # handle outdated sensor values once a minute
  601. if (int(time.time()) - checkLastUpdateInterval_lastRun) > checkLastUpdateInterval:
  602. checkLastUpdateInterval_lastRun = int(time.time())
  603. #print("check lastUpdate")
  604. for key in sensors_yaml:
  605. #print(key, '->', sensors_yaml[key])
  606. #print("Sensor name:", key)
  607. sensorId = sensors_yaml[key].get('LaCrosseID', None)
  608. if sensorId is not None:
  609. lupd = sensors_lastUpdate.get(sensorId, None)
  610. tdiff = 0
  611. if lupd is not None:
  612. tdiff = int(time.time()) - lupd
  613. if lupd is None or (tdiff > sensordata_maxage):
  614. mqttc.publish(mqtt_topic_prefix+"/"+ key +"/availability", "unavailable", qos=0, retain=False)
  615. notifystr = "received no data from sensor '" + key + "' with ID " + str(sensorId) + " for " + str(tdiff) + "s"
  616. mqttc.publish(mqtt_topic_prefix+"/" + mqtt_subtopic_notify, notifystr, qos=0, retain=False)
  617. except KeyboardInterrupt:
  618. print('\n')
  619. exit()