jeelinklog.py 38 KB

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