jeelinklog.py 65 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  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. from time import sleep
  18. #import math
  19. #import numpy as np
  20. #import httplib
  21. # Change working dir to the same dir as this script
  22. os.chdir(sys.path[0])
  23. # stop program if nothing is received from JeeLink for 5 min (will be restarted by systemd)
  24. serialReceived_maxAge = 300
  25. config = configparser.ConfigParser()
  26. config.read('jeelinklog.ini')
  27. serialport = config['jeelink'].get('serialport')
  28. serialbaud = int(config['jeelink'].get('baudrate'))
  29. if config['jeelink'].get('initTimeout') != None:
  30. serialInitTimeout = int(config['jeelink'].get('initTimeout'))
  31. else:
  32. serialInitTimeout = 2
  33. jeelinkInitCmd = config['jeelink'].get('initCmd')
  34. if jeelinkInitCmd == None:
  35. jeelinkInitCmd = "30t 3m v" # init cmd for TX29 AND TX35 sensors (17.241 and 9.579 kbps toggle every 30s)
  36. #jeelinkInitCmd = "0t 1m v" # init cmd for TX29 sensors only (17.241 kbps, no toggle)
  37. mqtt_topic_prefix = config['mqtt'].get('topic_prefix')
  38. topic_prefix_outside_temphum = config['mqtt'].get('topic_prefix_outside_temphum')
  39. mqtt_topic_atemp = config['mqtt'].get('topic_outside_temp')
  40. mqtt_topic_ahum = config['mqtt'].get('topic_outside_hum')
  41. mqtt_topic_ahum = config['mqtt'].get('topic_outside_dew')
  42. mqtt_topic_ahum = config['mqtt'].get('topic_outside_abshum')
  43. mqtt_topic_domoticz_in = "domoticz/in"
  44. mqtt_subtopic_notify = config['mqtt'].get('subtopic_notify')
  45. logfile_new_sensors = config['main'].get('logfile_new_sensors')
  46. path_new_sensors_folder = config['main'].get('path_new_sensors_folder')
  47. log_path = config['main'].get('log_path')
  48. if not os.path.exists(log_path):
  49. os.makedirs(log_path)
  50. verbosemode = False
  51. #sensors_conf_file = "/home/pi/jeelink_sensors.csv"
  52. sensordata_maxage = int(config['sensors'].get('data_maxage'))
  53. #minUpdateInterval = 60
  54. #aTempHumPublishInterval = 60
  55. #override_updateinterval_on_change = False
  56. #atemp_sensor_idx = 94
  57. #atemp_sensor_idx_2 = 113
  58. average_value_steps = int(config['sensors'].get('average_value_steps'))
  59. if average_value_steps == 0:
  60. average_value_steps = 1
  61. if len(sys.argv) > 1 and str(sys.argv[1]) == "-v":
  62. verbosemode = True
  63. def touch(fname, times=None):
  64. with open(fname, 'a'):
  65. os.utime(fname, times)
  66. def on_connect(client, userdata, flags, rc):
  67. if verbosemode:
  68. print("MQTT connected with result code " + str(rc) + "\n")
  69. #client.subscribe("wetter/atemp")
  70. def on_disconnect(client, userdata, rc):
  71. if rc != 0:
  72. print("Unexpected MQTT disconnection. Will auto-reconnect\n")
  73. #def on_message(client, userdata, msg):
  74. # #print(msg.topic + " " + str(msg.payload))
  75. # global atemp
  76. # atemp = msg.payload
  77. # dont edit below
  78. #starting values only..
  79. ##atemp = 61
  80. ##ahum = 101
  81. ##atemp1 = 61
  82. ##ahum1 = 101
  83. ##atemp2 = 61
  84. ##ahum2 = 101
  85. ##atemp_last = 61
  86. ##ahum_last = 101
  87. checkLastUpdateInterval = 60
  88. checkLastUpdateInterval_lastRun = 0
  89. lastPublishTime = 0
  90. lastStoreTime = 0
  91. sensors = dict()
  92. sensors_id_to_name = dict()
  93. #sensors_idx = dict()
  94. sensors_lastTemp = dict()
  95. sensors_lastHum = dict()
  96. sensors_lastUpdate = dict()
  97. sensors_lastReceivedValue_temp = dict()
  98. sensors_lastReceivedTime = dict()
  99. sensors_lastValues_temp = dict()
  100. sensors_lastValues_hum = dict()
  101. sensors_lastAvgValue_temp = dict()
  102. sensors_lastAvgValue_hum = dict()
  103. sensors_lastValues_lastIndex = dict()
  104. #sensors_unavailable = dict()
  105. sensors_batteryState = dict()
  106. sensors_new_alreadyNotified = dict()
  107. sensors_outside_sensors = []
  108. outSens_lastRun = 0
  109. outSens_store_lastRun = 0
  110. influx_default_fieldname_temperature = 'Temperature'
  111. influx_default_fieldname_humidity = 'Humidity'
  112. influx_default_fieldname_dewpoint = 'Dewpoint'
  113. influx_default_fieldname_abshum = 'AbsHumidity'
  114. influx_default_datatype_temperature = 'float'
  115. influx_default_datatype_humidity = 'int'
  116. influx_default_datatype_dewpoint = 'float'
  117. influx_default_datatype_abshum = 'float'
  118. sensors_yaml = yaml.load(open(config['main'].get('sensors_config_yml')), Loader=yaml.FullLoader)
  119. def list_duplicates(seq):
  120. seen = set()
  121. seen_add = seen.add
  122. # adds all elements it doesn't know yet to seen and all other to seen_twice
  123. seen_twice = set( x for x in seq if x in seen or seen_add(x) )
  124. # turn the set into a list (as requested)
  125. return list( seen_twice )
  126. # Dew point calculation based on formula and JS code found on: https://www.wetterochs.de/wetter/feuchte.html#f4
  127. # dew point from temperature, relative humidity
  128. # in °C
  129. def dewpoint(temp, relHum):
  130. if temp >= 0:
  131. a = 7.5
  132. b = 237.3
  133. else:
  134. a = 7.6
  135. b = 240.7
  136. c = math.log(vaporPressure(temp, relHum)/6.1078) * math.log10(math.e)
  137. return (b * c) / (a - c)
  138. def celsiusToKelvin(temp):
  139. return temp + 273.15
  140. def absoluteHumidity(relHum, temp):
  141. # AF = absolute Feuchte in g Wasserdampf pro m3 Luft
  142. # R* = 8314.3 J/(kmol*K) (universelle Gaskonstante)
  143. # mw = 18.016 kg/kmol (Molekulargewicht des Wasserdampfes)
  144. # r = relative Luftfeuchte
  145. # T = Temperatur in °C
  146. # TK = Temperatur in Kelvin (TK = T + 273.15)
  147. # TD = Taupunkttemperatur in °C
  148. #
  149. # mw/R* = 18.016 / 8314.3 = 0,0021668691290908
  150. #
  151. # AF(r,TK) = 10^5 * mw/R* * DD(T,r)/TK
  152. # unused 2nd formula: AF(TD,TK) = 10^5 * mw/R* * SDD(TD)/TK
  153. mw = 18.016
  154. R = 8314.3
  155. return 10**5 * mw/R * vaporPressure(temp, relHum) / celsiusToKelvin(temp)
  156. # Saettingungsdampfdruck / saturation vapour pressure
  157. # from temperature in hPa
  158. def saturationVaporPressure(temp):
  159. if temp >= 0:
  160. a = 7.5
  161. b = 237.3
  162. else:
  163. a = 7.6
  164. b = 240.7
  165. return 6.1078 * math.exp(((a*temp)/(b + temp))/ math.log10(math.e))
  166. # Dampfdruck / vapour pressure
  167. # from temperature, relative humidity in hPa
  168. def vaporPressure(temp, relHum):
  169. return relHum/100 * saturationVaporPressure(temp)
  170. if verbosemode:
  171. print("JeeLink2MQTT by Flo Kra")
  172. print("=======================================================================")
  173. print()
  174. print("JeeLink initCmd: " + jeelinkInitCmd)
  175. print()
  176. print()
  177. # load InfluxDB configuration
  178. influxdb_yaml = yaml.load(open(config['main'].get('influx_config_yml')), Loader=yaml.SafeLoader)
  179. if verbosemode:
  180. print("loading InfluxDB configuration...")
  181. print("InfluxDB Instances:")
  182. print(json.dumps(influxdb_yaml, indent=4))
  183. influxclient = dict()
  184. for instance in influxdb_yaml:
  185. i_host = influxdb_yaml[instance].get('host', None)
  186. i_port = int(influxdb_yaml[instance].get('port', 8086))
  187. i_username = influxdb_yaml[instance].get('username', None)
  188. i_password = influxdb_yaml[instance].get('password', None)
  189. i_database = influxdb_yaml[instance].get('database', None)
  190. if i_host != None and i_database != None:
  191. if i_username != None and i_password != None:
  192. influxclient[instance] = InfluxDBClient(i_host, i_port, i_username, i_password, i_database)
  193. else:
  194. influxclient[instance] = InfluxDBClient(i_host, i_port, i_database)
  195. if verbosemode:
  196. print()
  197. print()
  198. # load sensors configuration
  199. if verbosemode:
  200. print("loading sensors configuration: ")
  201. print(json.dumps(sensors_yaml, indent=3))
  202. print()
  203. for key in sensors_yaml:
  204. #print(key, '->', sensors_yaml[key])
  205. if verbosemode: print("Sensor name:", key)
  206. sensorName = key
  207. if sensors_yaml[key].get('LaCrosseID') >= 0:
  208. if verbosemode: print("LaCrosseID:", sensors_yaml[key].get('LaCrosseID'))
  209. sensorId = sensors_yaml[key].get('LaCrosseID')
  210. sensors_id_to_name[sensorId] = sensorName
  211. if sensors_yaml[key].get('DomoticzIdx'):
  212. if verbosemode: print("DomoticzIdx:", sensors_yaml[key].get('DomoticzIdx'))
  213. #sensors_idx[sensorId] = sensors_yaml[key].get('DomoticzIdx')
  214. if sensors_yaml[key].get('Topic_Temp'):
  215. if verbosemode: print("Topic_Temp:", sensors_yaml[key].get('Topic_Temp'))
  216. if sensors_yaml[key].get('Topic_Hum'):
  217. if verbosemode: print("Topic_Hum:", sensors_yaml[key].get('Topic_Hum'))
  218. if sensors_yaml[key].get('InfluxDB_Instance'):
  219. if verbosemode:
  220. print("InfluxDB_Instance:", sensors_yaml[key].get('InfluxDB_Instance'))
  221. if sensors_yaml[key].get('InfluxDB_Instance') not in influxdb_yaml:
  222. print("Error: invalid InfluxDB instance '" + sensors_yaml[key].get('InfluxDB_Instance') + "' configured for sensor '" + sensorName + "'")
  223. if sensors_yaml[key].get('isOutsideTempSensor'):
  224. if verbosemode: print("isOutsideTempSensor:", sensors_yaml[key].get('isOutsideTempSensor'))
  225. sensors_outside_sensors.append(sensorId)
  226. else:
  227. print("WARNING: Sensor " + key + " has no LaCrosseID!")
  228. if verbosemode:
  229. print()
  230. if verbosemode:
  231. print()
  232. print()
  233. #print(json.dumps(sensor))
  234. #print("sensors_id_to_name =",sensors_id_to_name)
  235. #print("outside sensors: ", sensors_outside_sensors)
  236. mqttc = mqtt.Client()
  237. mqttc.on_connect = on_connect
  238. mqttc.on_disconnect = on_disconnect
  239. ##mqttc.on_message = on_message
  240. if config['mqtt'].get('user') != "" and config['mqtt'].get('password') != "":
  241. mqttc.username_pw_set(config['mqtt'].get('user'), config['mqtt'].get('password'))
  242. mqttc.connect(config['mqtt'].get('server'), config['mqtt'].getint('port'), 60)
  243. mqttc.loop_start()
  244. #mqttc.loop_forever()
  245. ser = serial.Serial(port=serialport,
  246. baudrate = serialbaud,
  247. parity=serial.PARITY_NONE,
  248. stopbits=serial.STOPBITS_ONE,
  249. bytesize=serial.EIGHTBITS,
  250. timeout=1)
  251. checkLastUpdateInterval_lastRun = time.time() # first check after timeout expired
  252. # set serialReceivedLastTime to current time so that we can detect if nothing is received for some time
  253. serialReceivedLastTime = int(time.time())
  254. # init JeeLink device
  255. sleep(serialInitTimeout)
  256. ser.flushInput()
  257. ser.write(jeelinkInitCmd.encode('ascii')) # send init command to Jeelink
  258. #ser.write('v\r\n'.encode('ascii')) # get JeeLink version info
  259. sleep(0.2)
  260. try:
  261. while True:
  262. msg_was_sent = 0
  263. #clear serial buffer to remove junk and noise
  264. ###ser.flushInput()
  265. #read buffer until cr/lf
  266. serLine = ser.readline().strip()
  267. # catch exception on invalid char coming in: UnicodeDecodeError: 'ascii' codec can't decode byte 0xf4 in position 6: ordinal not in range(128)
  268. try:
  269. serLine = serLine.decode('ascii')
  270. except:
  271. serLine = False
  272. if(serLine):
  273. if serLine.find('LaCrosseITPlusReader') != -1:
  274. if verbosemode:
  275. print("Jeelink version/config: " + serLine)
  276. print()
  277. print()
  278. elif serLine.find('OK 9') != -1:
  279. # set serialReceivedLastTime to current time so that we can detect if nothing is received for some time
  280. serialReceivedLastTime = int(time.time())
  281. if verbosemode:
  282. print("RX: " + serLine + " => LaCrosse sensor")
  283. # uns interessieren nur reinkommende Zeilen die mit "OK 9 " beginnen
  284. # 0 1 2 3 4 5 6
  285. # OK 9 ID XXX XXX XXX XXX
  286. # | | | | | | |
  287. # | | | | | | --- Humidity incl. WeakBatteryFlag
  288. # | | | | | |------ Temp * 10 + 1000 LSB
  289. # | | | | |---------- Temp * 10 + 1000 MSB
  290. # | | | |-------------- Sensor type (1 or 2) +128 if NewBatteryFlag
  291. # | | |----------------- Sensor ID
  292. # | |------------------- fix "9"
  293. # |---------------------- fix "OK"
  294. serLineParts = serLine.split(' ')
  295. #print("Len: " + str(len(serLineParts)))
  296. if(len(serLineParts) == 7): # check correct size of incoming data
  297. #addr = serLineParts[2]
  298. #addr = "{0:x}".format(int(serLineParts[2]))
  299. #addr = hex((int(serLineParts[2])))
  300. addr = int(serLineParts[2])
  301. addrhex = "{0:x}".format(int(serLineParts[2]))
  302. lastUpdate = sensors_lastUpdate.get(addr, None)
  303. lastTemp = sensors_lastTemp.get(addr, None)
  304. lastHum = sensors_lastHum.get(addr, None)
  305. currentsensor_name = sensors_id_to_name.get(addr, None)
  306. # extract sensor data received from JeeLink
  307. if int(serLineParts[3]) >= 128:
  308. batt_new = 1
  309. type = int(serLineParts[3]) - 128
  310. else:
  311. batt_new = 0
  312. type = int(serLineParts[3])
  313. temp = (int(serLineParts[4])*256 + int(serLineParts[5]) - 1000)/10.0
  314. if int(serLineParts[6]) >= 128:
  315. batt_low = 1
  316. hum = int(serLineParts[6]) - 128
  317. else:
  318. batt_low = 0
  319. hum = int(serLineParts[6])
  320. if hum > 100:
  321. hum = 100
  322. if batt_new == 1:
  323. sensors_batteryState[addr] = 2
  324. elif batt_low == 1:
  325. sensors_batteryState[addr] = 1
  326. else:
  327. sensors_batteryState[addr] = 0
  328. lastValues_lastIndex = sensors_lastValues_lastIndex.get(addr, None)
  329. if sensors_lastValues_temp.get(addr, None) == None:
  330. sensors_lastValues_temp[addr] = [None] * average_value_steps
  331. if sensors_lastValues_hum.get(addr, None) == None:
  332. sensors_lastValues_hum[addr] = [None] * average_value_steps
  333. data_okay = False
  334. if sensors_lastReceivedValue_temp.get(addr, None) == None:
  335. # this is the first time we receive from that sensor in that session
  336. if verbosemode: print("first received from sensor",str(addr))
  337. sensors_lastReceivedValue_temp[addr] = temp
  338. sensors_lastReceivedTime[addr] = int(time.time())
  339. #lastValues_lastIndex = 0
  340. else:
  341. lastValue = sensors_lastReceivedValue_temp.get(addr, None)
  342. max_off_value = float(config['sensors'].get('max_off_value'))
  343. ignore_off_value_timeout = int(config['sensors'].get('ignore_off_value_timeout'))
  344. 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
  345. sensors_lastReceivedValue_temp[addr] = temp
  346. sensors_lastReceivedTime[addr] = int(time.time())
  347. #print("Last Value=",lastValue,"currValue=",temp)
  348. data_okay = True
  349. else:
  350. if verbosemode: print("skipped sensor reading - Last Value=",lastValue,"currValue=",temp)
  351. if data_okay:
  352. if lastValues_lastIndex == None:
  353. lastValues_lastIndex = 0
  354. elif lastValues_lastIndex == (average_value_steps - 1):
  355. lastValues_lastIndex = 0
  356. else:
  357. lastValues_lastIndex += 1
  358. if verbosemode: print("lastValues_lastIndex =", lastValues_lastIndex)
  359. sensors_lastValues_lastIndex[addr] = lastValues_lastIndex
  360. sensors_lastValues_temp[addr][lastValues_lastIndex] = temp
  361. sensors_lastValues_hum[addr][lastValues_lastIndex] = hum
  362. sensors_lastUpdate[addr] = int(time.time())
  363. if verbosemode: print("sensors_lastValues_temp =", sensors_lastValues_temp[addr])
  364. if currentsensor_name is None:
  365. if batt_new == 1 and not sensors_new_alreadyNotified.get('addr', False):
  366. notifystr = "NEW sensor with ID " + str(addr)
  367. mqttc.publish(mqtt_topic_prefix+"/" + mqtt_subtopic_notify, notifystr, qos=0, retain=False)
  368. if not os.path.exists(log_path+'/new'):
  369. os.makedirs(log_path+'/new')
  370. fname = log_path + '/new/' + str(addr)
  371. if not os.path.exists(fname):
  372. try:
  373. touch(fname)
  374. except:
  375. pass
  376. try:
  377. f = open(log_path+'/'+logfile_new_sensors, 'a')
  378. f.write(str(datetime.now())+": "+notifystr+"\n")
  379. f.close()
  380. except:
  381. # guat dann hoit ned...
  382. pass
  383. sensors_new_alreadyNotified[addr] = True
  384. else:
  385. if not os.path.exists(log_path+'/unknown'):
  386. os.makedirs(log_path+'/unknown')
  387. fname = log_path + '/unknown/' + str(addr)
  388. if not os.path.exists(fname):
  389. try:
  390. touch(fname)
  391. except:
  392. pass
  393. if verbosemode:
  394. print("unknown sensor ID " + str(addr))
  395. if verbosemode:
  396. print("LaCrosse-ID: " + str(addr) + " = 0x" + str(addrhex) + " Type: " + str(type) + " Batt_New: " + str(batt_new) + " Batt_Low: " + str(batt_low) + " Temp: " + str(temp) + " Hum: " + str(hum) + " Name: " + str(currentsensor_name))
  397. print()
  398. #if senddata:
  399. # sensors_lastUpdate[str(addr)] = int(time.time())
  400. # sensors_lastTemp[str(addr)] = temp
  401. # sensors_lastHum[str(addr)] = hum
  402. #
  403. # isAtemp = False
  404. # if int(currentsensor_idx) == atemp_sensor_idx:
  405. # atemp1 = temp
  406. # ahum1 = hum
  407. # isAtemp = True
  408. # elif int(currentsensor_idx) == atemp_sensor_idx_2:
  409. # atemp2 = temp
  410. # ahum2 = hum
  411. # isAtemp = True
  412. #
  413. # if isAtemp:
  414. # if atemp1 <= atemp2:
  415. # atemp = atemp1
  416. # ahum = ahum1
  417. # else:
  418. # atemp = atemp2
  419. # ahum = ahum2
  420. #
  421. # if atemp < 61 and ahum < 101:
  422. # if atemp != atemp_last or ahum != ahum_last or ((time.time() - atemphum_lastUpdate) > aTempHumPublishInterval):
  423. # atemphum_lastUpdate = time.time()
  424. # atemp_last = atemp
  425. # ahum_last = ahum
  426. # mqttc.publish(mqtt_topic_atemp, str(atemp), qos=0, retain=True)
  427. # mqttc.publish(mqtt_topic_ahum, str(ahum), qos=0, retain=True)
  428. # mqttc.publish(mqtt_topic_atemphum_lastUpdate, strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False)
  429. #
  430. # domoticz_json = "{\"idx\":" + str(currentsensor_idx) + ",\"nvalue\":0,\"svalue\":\"" + str(temp) + ";" + str(hum) + ";1\"}"
  431. # #if verbosemode:
  432. # # print(domoticz_json)
  433. # mqttc.publish(mqtt_topic_domoticz_in, domoticz_json, qos=0, retain=False)
  434. #
  435. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/temperature", str(temp), qos=0, retain=False)
  436. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/humidity", str(hum), qos=0, retain=False)
  437. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/battery", str(batterystate), qos=0, retain=False)
  438. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/lastUpdate", strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False)
  439. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/availability", "available", qos=0, retain=False)
  440. #
  441. # lacrosse_json = "{\"temperature\":" + str(temp) + ", \"humidity\":" + str(hum) + ", \"battery\":\"" + str(batterystate) + "\"}"
  442. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/json", lacrosse_json, qos=0, retain=False)
  443. #
  444. # tmptext = str(temp) + "° " + str(hum) + "%"
  445. # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/TempHumText", tmptext, qos=0, retain=False)
  446. #
  447. # if verbosemode:
  448. # print("MQTT published")
  449. #
  450. # try:
  451. # touch("/tmp/jeelink2mqtt_running")
  452. # except:
  453. # # guat dann ned...
  454. # pass
  455. #
  456. #else:
  457. # if verbosemode:
  458. # if currentsensor_name is None:
  459. # print("MQTT published")
  460. # else:
  461. # print("MQTT publishing surpressed (interval not expired)")
  462. #
  463. #
  464. #if verbosemode:
  465. # print("\n")
  466. else:
  467. if verbosemode: print("RX: " + serLine + " => ignored invalid data")
  468. pass
  469. else:
  470. if verbosemode:
  471. print("RX: " + serLine)
  472. pass
  473. # publish on MQTT on set interval
  474. publishNow = False
  475. if (int(time.time()) - lastPublishTime) > int(config['sensors'].get('publish_interval')):
  476. if lastPublishTime == 0:
  477. lastPublishTime = int(time.time())
  478. else:
  479. publishNow = True
  480. lastPublishTime = int(time.time())
  481. #for sensor in
  482. #sensors_lastReceivedTime[str(addr)]
  483. # store to InfluxDB on set interval
  484. storeNow = False
  485. if (int(time.time()) - lastStoreTime) > int(config['sensors'].get('store_interval')):
  486. if lastStoreTime == 0:
  487. lastStoreTime = int(time.time())
  488. else:
  489. storeNow = True
  490. lastStoreTime = int(time.time())
  491. if publishNow or storeNow:
  492. #print("available sensor data: ", sensors_lastUpdate)
  493. for id in sensors_lastUpdate:
  494. if (time.time() - sensors_lastUpdate[id]) < sensordata_maxage:
  495. s_name = sensors_id_to_name.get(id, None)
  496. if verbosemode: print("current data available for:", id, s_name)
  497. sensorDataComplete = True
  498. sum_temp=0
  499. for val in sensors_lastValues_temp[id]:
  500. if val is None:
  501. sensorDataComplete = False
  502. else:
  503. sum_temp = sum_temp + val
  504. sum_hum=0
  505. for val in sensors_lastValues_hum[id]:
  506. if val is None:
  507. sensorDataComplete = False
  508. else:
  509. sum_hum = sum_hum + val
  510. if sensorDataComplete:
  511. s_currAvgTemp = round(sum_temp / len(sensors_lastValues_temp[id]), 1)
  512. s_currAvgHum = int(sum_hum / len(sensors_lastValues_hum[id]))
  513. # calc dewpoint
  514. if config['sensors'].getboolean('calculate_dewpoint', False):
  515. s_currDewpoint = round(dewpoint(s_currAvgTemp, s_currAvgHum), 1)
  516. # calc absolute humidity
  517. if config['sensors'].getboolean('calculate_absolute_humidity', False):
  518. s_currAbsHum = round(absoluteHumidity(s_currAvgHum, s_currAvgTemp), 2)
  519. sensors_lastAvgValue_temp[id] = s_currAvgTemp
  520. sensors_lastAvgValue_hum[id] = s_currAvgHum
  521. if verbosemode: print("s_currAvgTemp =", s_currAvgTemp, "s_currAvgHum =", s_currAvgHum)
  522. else:
  523. if verbosemode: print("s_currAvgTemp/s_currAvgHum: not yet enough readings available")
  524. if sensorDataComplete:
  525. # as Home Assistant MQTT binary sensor can only work with 2 states (unless using value templates):
  526. # -> removed "NEW" state string from /battery topic and added /batteryNew topic
  527. if sensors_batteryState.get(id,None) == 2:
  528. s_battNew = True
  529. s_battState = "OK"
  530. elif sensors_batteryState.get(id, None) == 1:
  531. s_battNew = False
  532. s_battState = "LOW"
  533. elif sensors_batteryState.get(id, None) == 0:
  534. s_battNew = False
  535. s_battState = "OK"
  536. if s_battNew:
  537. s_battNew_str = "YES"
  538. else:
  539. s_battNew_str = "NO"
  540. if s_name is not None:
  541. s_domIdx = sensors_yaml[s_name].get('DomoticzIdx', None)
  542. s_topic_temp = sensors_yaml[s_name].get('Topic_Temp', None)
  543. s_topic_hum = sensors_yaml[s_name].get('Topic_Hum', None)
  544. s_topic_dew = sensors_yaml[s_name].get('Topic_Dew', None)
  545. s_topic_absHum = sensors_yaml[s_name].get('Topic_AbsHum', None)
  546. s_influxInstance = sensors_yaml[s_name].get('InfluxDB_Instance', None)
  547. s_isOutsideTempSensor = sensors_yaml[s_name].get('isOutsideTempSensor', None)
  548. if s_domIdx is not None and publishNow:
  549. domoticz_json = "{\"idx\":" + str(s_domIdx) + ",\"nvalue\":0,\"svalue\":\"" + str(s_currAvgTemp) + ";" + str(s_currAvgHum) + ";1\"}"
  550. if verbosemode:
  551. print("Domoticz JSON:", domoticz_json)
  552. mqttc.publish(mqtt_topic_domoticz_in, domoticz_json, qos=0, retain=False)
  553. if s_topic_temp is not None and len(s_topic_temp)>5 and publishNow:
  554. if verbosemode: print("publishing temp on ", s_topic_temp)
  555. mqttc.publish(s_topic_temp, str(s_currAvgTemp), qos=0, retain=False)
  556. if s_topic_dew is not None and len(s_topic_dew)>5 and publishNow and config['sensors'].getboolean('calculate_dewpoint', False):
  557. if verbosemode: print("publishing dewpoint on ", s_topic_dew)
  558. mqttc.publish(s_topic_dew, str(s_currDewpoint), qos=0, retain=False)
  559. if s_topic_absHum is not None and len(s_topic_absHum)>5 and publishNow and config['sensors'].getboolean('calculate_absolute_humidity', False):
  560. if verbosemode: print("publishing absolute humidty on ", s_topic_absHum)
  561. mqttc.publish(s_topic_absHum, str(s_currAbsHum), qos=0, retain=False)
  562. if s_topic_hum is not None and len(s_topic_hum)>5 and publishNow:
  563. if verbosemode: print("publishing hum on ", s_topic_temp)
  564. mqttc.publish(s_topic_hum, str(s_currAvgHum), qos=0, retain=False)
  565. if publishNow:
  566. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/temperature", str(s_currAvgTemp), qos=0, retain=False)
  567. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/humidity", str(s_currAvgHum), qos=0, retain=False)
  568. if config['sensors'].getboolean('calculate_dewpoint', False):
  569. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/dewpoint", str(s_currDewpoint), qos=0, retain=False)
  570. if config['sensors'].getboolean('calculate_absolute_humidity', False):
  571. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/absoluteHumidity", str(s_currAbsHum), qos=0, retain=False)
  572. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/battery", s_battState, qos=0, retain=False)
  573. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/batteryNew", s_battNew_str, qos=0, retain=False)
  574. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/availability", "available", qos=0, retain=False)
  575. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/lastUpdate", strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False)
  576. # publish global lastUpdate for monitoring purposes
  577. mqttc.publish(mqtt_topic_prefix+"/lastUpdate", strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False)
  578. # JSON output
  579. lacrosse_json = "{\"temperature\":" + str(s_currAvgTemp) \
  580. + ", \"humidity\":" + str(s_currAvgHum) \
  581. + ", \"battery\":\"" + str(s_battState) + "\""
  582. if config['sensors'].getboolean('calculate_dewpoint', False):
  583. lacrosse_json = lacrosse_json + ", \"dewpoint\":" + str(s_currDewpoint)
  584. if config['sensors'].getboolean('calculate_absolute_humidity', False):
  585. lacrosse_json = lacrosse_json + ", \"absoluteHumidity\":" + str(s_currAbsHum)
  586. lacrosse_json = lacrosse_json + "}"
  587. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/json", lacrosse_json, qos=0, retain=False)
  588. tmptext = str(s_currAvgTemp) + "° " + str(s_currAvgHum) + "%"
  589. mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/TempHumText", tmptext, qos=0, retain=False)
  590. if s_influxInstance is not None and storeNow:
  591. ### write to InfluxDB here
  592. if s_influxInstance in influxdb_yaml:
  593. influx_measurement = influxdb_yaml[s_influxInstance].get('measurement', None)
  594. t_utc = datetime.utcnow()
  595. t_str = t_utc.isoformat() + 'Z'
  596. if influx_measurement is not None:
  597. influx_fieldnames = influxdb_yaml[s_influxInstance].get('fieldnames', None)
  598. if influx_fieldnames == None:
  599. influx_fieldname_temperature = influx_default_fieldname_temperature
  600. influx_fieldname_humidity = influx_default_fieldname_humidity
  601. influx_fieldname_dewpoint = influx_default_fieldname_dewpoint
  602. influx_fieldname_abshum = influx_default_fieldname_abshum
  603. else:
  604. if influxdb_yaml[s_influxInstance]['fieldnames'].get('temperature', None) != None:
  605. influx_fieldname_temperature = influxdb_yaml[s_influxInstance]['fieldnames'].get('temperature')
  606. else:
  607. influx_fieldname_temperature = influx_default_fieldname_temperature
  608. if influxdb_yaml[s_influxInstance]['fieldnames'].get('humidity', None) != None:
  609. influx_fieldname_humidity = influxdb_yaml[s_influxInstance]['fieldnames'].get('humidity')
  610. else:
  611. influx_fieldname_humidity = influx_default_fieldname_humidity
  612. if influxdb_yaml[s_influxInstance]['fieldnames'].get('dewpoint', None) != None:
  613. influx_fieldname_dewpoint = influxdb_yaml[s_influxInstance]['fieldnames'].get('dewpoint')
  614. else:
  615. influx_fieldname_dewpoint = influx_default_fieldname_dewpoint
  616. if influxdb_yaml[s_influxInstance]['fieldnames'].get('abshum', None) != None:
  617. influx_fieldname_abshum = influxdb_yaml[s_influxInstance]['fieldnames'].get('abshum')
  618. else:
  619. influx_fieldname_abshum = influx_default_fieldname_abshum
  620. influx_datatypes = influxdb_yaml[s_influxInstance].get('datatypes', None)
  621. if influx_datatypes == None:
  622. influx_datatype_temperature = influx_default_datatype_temperature
  623. influx_datatype_humidity = influx_default_datatype_humidity
  624. else:
  625. if influxdb_yaml[s_influxInstance]['datatypes'].get('temperature', None) != None:
  626. tmpdt = influxdb_yaml[s_influxInstance]['datatypes'].get('temperature')
  627. if tmpdt == 'float' or tmpdt == 'int':
  628. influx_datatype_temperature = tmpdt
  629. else:
  630. influx_datatype_temperature = influx_default_datatype_temperature
  631. else:
  632. influx_datatype_temperature = influx_default_datatype_temperature
  633. if influxdb_yaml[s_influxInstance]['datatypes'].get('humidity', None) != None:
  634. tmpdt = influxdb_yaml[s_influxInstance]['datatypes'].get('humidity')
  635. if tmpdt == 'float' or tmpdt == 'int':
  636. influx_datatype_humidity = tmpdt
  637. else:
  638. influx_datatype_humidity = influx_default_datatype_humidity
  639. else:
  640. influx_datatype_humidity = influx_default_datatype_humidity
  641. if influxdb_yaml[s_influxInstance]['datatypes'].get('dewpoint', None) != None:
  642. tmpdt = influxdb_yaml[s_influxInstance]['datatypes'].get('dewpoint')
  643. if tmpdt == 'float' or tmpdt == 'int':
  644. influx_datatype_dewpoint = tmpdt
  645. else:
  646. influx_datatype_dewpoint = influx_default_datatype_dewpoint
  647. else:
  648. influx_datatype_dewpoint = influx_default_datatype_dewpoint
  649. if influxdb_yaml[s_influxInstance]['datatypes'].get('abshum', None) != None:
  650. tmpdt = influxdb_yaml[s_influxInstance]['datatypes'].get('abshum')
  651. if tmpdt == 'float' or tmpdt == 'int':
  652. influx_datatype_abshum = tmpdt
  653. else:
  654. influx_datatype_abshum = influx_default_datatype_abshum
  655. else:
  656. influx_datatype_abshum = influx_default_datatype_abshum
  657. if influx_datatype_temperature == 'int':
  658. influx_value_temp = int(s_currAvgTemp)
  659. else:
  660. influx_value_temp = float(s_currAvgTemp)
  661. if influx_datatype_humidity == 'int':
  662. influx_value_hum = int(s_currAvgHum)
  663. else:
  664. influx_value_hum = float(s_currAvgHum)
  665. if config['sensors'].getboolean('calculate_dewpoint', False) and config['sensors'].getboolean('write_dewpoint_to_influxdb', False):
  666. if influx_datatype_dewpoint == 'int':
  667. influx_value_dewpoint = int(s_currDewpoint)
  668. else:
  669. influx_value_dewpoint = float(s_currDewpoint)
  670. if config['sensors'].getboolean('calculate_absolute_humidity', False) and config['sensors'].getboolean('write_abshum_to_influxdb', False):
  671. if influx_datatype_abshum == 'int':
  672. influx_value_abshum = int(s_currAbsHum)
  673. else:
  674. influx_value_abshum = float(s_currAbsHum)
  675. influx_json = [
  676. {
  677. 'measurement': influx_measurement,
  678. 'tags': {
  679. 'sensor': s_name
  680. },
  681. 'time': t_str,
  682. 'fields': {
  683. influx_fieldname_temperature: influx_value_temp,
  684. influx_fieldname_humidity: influx_value_hum
  685. }
  686. }
  687. ]
  688. if config['sensors'].getboolean('calculate_dewpoint', False) and config['sensors'].getboolean('write_dewpoint_to_influxdb', False):
  689. influx_json[0]["fields"][influx_fieldname_dewpoint] = influx_value_dewpoint
  690. if config['sensors'].getboolean('calculate_absolute_humidity', False) and config['sensors'].getboolean('write_abshum_to_influxdb', False):
  691. influx_json[0]["fields"][influx_fieldname_abshum] = influx_value_abshum
  692. try:
  693. if verbosemode:
  694. print("write to InfluxDB...")
  695. print(influx_json)
  696. influxclient[s_influxInstance].write_points(influx_json)
  697. if verbosemode: print("DONE!")
  698. except Exception as e:
  699. print("Error writing to InfluxDB")
  700. print(influx_json)
  701. print(e)
  702. else:
  703. if verbosemode:
  704. print("Error: invalid InfluxDB instance '" + s_influxInstance + "' configured for sensor '" + s_name + "'")
  705. else: # this is an unknown sensor
  706. if publishNow:
  707. if s_battNew:
  708. if config['mqtt'].getboolean('publish_unknown_new_sensors'):
  709. mqttc.publish(mqtt_topic_prefix+"/NewUnknownSensor/"+str(id)+"/temperature", str(s_currAvgTemp), qos=0, retain=False)
  710. mqttc.publish(mqtt_topic_prefix+"/NewUnknownSensor/"+str(id)+"/humidity", str(s_currAvgHum), qos=0, retain=False)
  711. mqttc.publish(mqtt_topic_prefix+"/NewUnknownSensor/"+str(id)+"/batteryNew", s_battNew_str, qos=0, retain=False)
  712. else:
  713. if config['mqtt'].getboolean('publish_unknown_sensors'):
  714. mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(id)+"/temperature", str(s_currAvgTemp), qos=0, retain=False)
  715. mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(id)+"/humidity", str(s_currAvgHum), qos=0, retain=False)
  716. mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(id)+"/battery", s_battState, qos=0, retain=False)
  717. if verbosemode: print()
  718. # outside sensors
  719. if (int(time.time()) - outSens_lastRun) > int(config['sensors'].get('publish_interval_outside', 60)):
  720. outSens_lastRun = int(time.time())
  721. count_used_out_sensors = 0
  722. sum_out_sensors_temp = 0
  723. sum_out_sensors_hum = 0
  724. # "declare" variables with invalid high values - will be overwritten later or keep unused
  725. sum_out_sensors_temp_min = 100
  726. sum_out_sensors_temp_max = -50
  727. sum_out_sensors_hum_min = 100
  728. sum_out_sensors_hum_max = -50
  729. out_temp_publishvalue = 200
  730. out_hum_publishvalue = 200
  731. out_dewpoint = 200
  732. out_abshum = 200
  733. # arrays for median calculation
  734. out_sensors_temp_median_values = []
  735. out_sensors_hum_median_values = []
  736. for id in sensors_outside_sensors:
  737. lupd = sensors_lastUpdate.get(id, None)
  738. tdiff = 0
  739. if lupd is not None:
  740. tdiff = int(time.time()) - lupd
  741. if lupd is not None and (tdiff < sensordata_maxage):
  742. tmpval_t = sensors_lastAvgValue_temp.get(id, None)
  743. tmpval_h = sensors_lastAvgValue_hum.get(id, None)
  744. if tmpval_t is not None and tmpval_h is not None:
  745. # sum - used for average
  746. sum_out_sensors_temp = sum_out_sensors_temp + tmpval_t
  747. sum_out_sensors_hum = sum_out_sensors_hum + tmpval_h
  748. # memorize values from sensor with lowest reading
  749. if tmpval_t < sum_out_sensors_temp_min:
  750. sum_out_sensors_temp_min = tmpval_t
  751. sum_out_sensors_hum_min = tmpval_h
  752. # memorize values from sensor with highest reading
  753. if tmpval_t > sum_out_sensors_temp_max:
  754. sum_out_sensors_temp_max = tmpval_t
  755. sum_out_sensors_hum_max = tmpval_h
  756. # median
  757. if(config['sensors'].getboolean('outside_sensors_use_median')):
  758. out_sensors_temp_median_values.append(tmpval_t)
  759. out_sensors_hum_median_values.append(tmpval_h)
  760. count_used_out_sensors += 1
  761. lacrosse_json = None
  762. if count_used_out_sensors > 0:
  763. out_temp_avg = round((sum_out_sensors_temp / count_used_out_sensors), 1)
  764. out_hum_avg = int(round((sum_out_sensors_hum / count_used_out_sensors), 0))
  765. # calc MEDIAN if there are more than 2 outside sensors
  766. out_temp_median = 0
  767. out_hum_median = 0
  768. if count_used_out_sensors > 2:
  769. out_temp_median = round(statistics.median(out_sensors_temp_median_values), 1)
  770. out_hum_median = int(round(statistics.median(out_sensors_hum_median_values), 0))
  771. # use MINIMUM if outside_sensors_force_minimum_value is enabled
  772. if(config['sensors'].getboolean('outside_sensors_force_minimum_value')):
  773. if sum_out_sensors_temp_min < 100:
  774. out_temp_publishvalue = sum_out_sensors_temp_min
  775. if sum_out_sensors_hum_min < 100:
  776. out_hum_publishvalue = sum_out_sensors_hum_min
  777. # use MEDIAN if outside_sensors_use_median is enabled and there are more than 2 sensors
  778. elif(config['sensors'].getboolean('outside_sensors_use_median')) and count_used_out_sensors > 2:
  779. out_temp_publishvalue = out_temp_median
  780. out_hum_publishvalue = out_hum_median
  781. # otherwise user AVERAGE
  782. else:
  783. out_temp_publishvalue = out_temp_avg
  784. out_hum_publishvalue = out_hum_avg
  785. # calc dewpoint
  786. if config['sensors'].getboolean('calculate_dewpoint', False):
  787. out_dewpoint = round(dewpoint(out_temp_avg, out_hum_avg), 1)
  788. # calc absolute humidity
  789. if config['sensors'].getboolean('calculate_absolute_humidity', False):
  790. out_abshum = round(absoluteHumidity(out_hum_avg, out_temp_avg), 2)
  791. mqttc.publish(mqtt_topic_atemp, str(out_temp_publishvalue), qos=0, retain=True)
  792. mqttc.publish(mqtt_topic_ahum, str(out_hum_publishvalue), qos=0, retain=True)
  793. mqttc.publish(topic_prefix_outside_temphum + '/temperature', str(out_temp_publishvalue), qos=0, retain=True)
  794. mqttc.publish(topic_prefix_outside_temphum + '/humidity', str(out_hum_publishvalue), qos=0, retain=True)
  795. if config['sensors'].getboolean('calculate_dewpoint', False):
  796. mqttc.publish(topic_prefix_outside_temphum + '/dewpoint', str(out_dewpoint), qos=0, retain=True)
  797. if config['sensors'].getboolean('calculate_absolute_humidity', False):
  798. mqttc.publish(topic_prefix_outside_temphum + '/absoluteHumidity', str(out_abshum), qos=0, retain=True)
  799. # publish AVERAGE
  800. mqttc.publish(topic_prefix_outside_temphum + '/temp_average', str(out_temp_avg), qos=0, retain=True)
  801. mqttc.publish(topic_prefix_outside_temphum + '/hum_average', str(out_hum_avg), qos=0, retain=True)
  802. # publish MEDIAN
  803. if count_used_out_sensors > 2:
  804. mqttc.publish(topic_prefix_outside_temphum + '/temp_median', str(out_temp_median), qos=0, retain=True)
  805. mqttc.publish(topic_prefix_outside_temphum + '/hum_median', str(out_hum_median), qos=0, retain=True)
  806. # publish last update
  807. mqttc.publish(topic_prefix_outside_temphum + '/lastUpdate', strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=True)
  808. # publish TempHumText
  809. tmptext = str(out_temp_publishvalue) + "° " + str(out_hum_publishvalue) + "%"
  810. mqttc.publish(topic_prefix_outside_temphum + "/TempHumText", tmptext, qos=0, retain=False)
  811. lacrosse_json = "{\"temperature\":" + str(out_temp_publishvalue) + \
  812. ", \"humidity\":" + str(out_hum_publishvalue) + "\"" \
  813. ", \"usedSensors\":" + str(count_used_out_sensors)
  814. min = 100
  815. max = 100
  816. if count_used_out_sensors > 1:
  817. mqttc.publish(topic_prefix_outside_temphum + '/usedSensors', str(count_used_out_sensors), qos=0, retain=True)
  818. lacrosse_json = lacrosse_json + \
  819. ", \"temp_average\":" + str(out_temp_avg) + \
  820. ", \"hum_average\":" + str(out_hum_avg)
  821. if count_used_out_sensors > 2:
  822. lacrosse_json = lacrosse_json + \
  823. ", \"temp_median\":" + str(out_temp_median) + \
  824. ", \"hum_median\":" + str(out_temp_median)
  825. # publish MIN
  826. if sum_out_sensors_temp_min < 100:
  827. mqttc.publish(topic_prefix_outside_temphum + '/temp_min', str(sum_out_sensors_temp_min), qos=0, retain=False)
  828. lacrosse_json = lacrosse_json + ", \"temp_min\":" + str(sum_out_sensors_temp_min)
  829. if sum_out_sensors_hum_min < 100:
  830. mqttc.publish(topic_prefix_outside_temphum + '/hum_min', str(sum_out_sensors_hum_min), qos=0, retain=False)
  831. lacrosse_json = lacrosse_json + ", \"hum_min\":" + str(sum_out_sensors_hum_min)
  832. # publish MAX
  833. if sum_out_sensors_temp_max > -50:
  834. mqttc.publish(topic_prefix_outside_temphum + '/temp_max', str(sum_out_sensors_temp_max), qos=0, retain=False)
  835. lacrosse_json = lacrosse_json + ", \"temp_max\":" + str(sum_out_sensors_temp_max)
  836. if sum_out_sensors_hum_max > -50:
  837. mqttc.publish(topic_prefix_outside_temphum + '/hum_max', str(sum_out_sensors_hum_max), qos=0, retain=False)
  838. lacrosse_json = lacrosse_json + ", \"hum_max\":" + str(sum_out_sensors_hum_max)
  839. if lacrosse_json is not None:
  840. if config['sensors'].getboolean('calculate_dewpoint', False):
  841. lacrosse_json = lacrosse_json + ", \"dewpoint\":" + str(out_dewpoint)
  842. if config['sensors'].getboolean('calculate_absolute_humidity', False):
  843. lacrosse_json = lacrosse_json + ", \"absoluteHumidity\":" + str(out_abshum)
  844. lacrosse_json = lacrosse_json + "}"
  845. mqttc.publish(topic_prefix_outside_temphum + "/json", lacrosse_json, qos=0, retain=False)
  846. # combined outside sensors to InfluxDB
  847. o_hasValidData = False
  848. if out_temp_publishvalue < 200 and out_hum_publishvalue < 200:
  849. o_hasValidData = True
  850. o_storeNow = False
  851. if (int(time.time()) - outSens_store_lastRun) > int(config['sensors'].get('store_interval_outside', 300)):
  852. if outSens_store_lastRun == 0:
  853. outSens_store_lastRun = int(time.time())
  854. else:
  855. o_storeNow = True
  856. outSens_store_lastRun = int(time.time())
  857. if o_storeNow and o_hasValidData:
  858. outSens_store_lastRun = int(time.time())
  859. o_influxInstance = config['sensors'].get('influxdb_instance_combined_outside_sensors')
  860. o_influxSensorName = config['sensors'].get('influxdb_sensorname_combined_outside_sensors', None)
  861. if o_influxInstance in influxdb_yaml and o_influxSensorName is not None:
  862. influx_measurement = influxdb_yaml[o_influxInstance].get('measurement', None)
  863. t_utc = datetime.utcnow()
  864. t_str = t_utc.isoformat() + 'Z'
  865. if influx_measurement is not None:
  866. influx_fieldnames = influxdb_yaml[o_influxInstance].get('fieldnames', None)
  867. if influx_fieldnames == None:
  868. influx_fieldname_temperature = influx_default_fieldname_temperature
  869. influx_fieldname_humidity = influx_default_fieldname_humidity
  870. influx_fieldname_dewpoint = influx_default_fieldname_dewpoint
  871. influx_fieldname_abshum = influx_default_fieldname_abshum
  872. else:
  873. if influxdb_yaml[o_influxInstance]['fieldnames'].get('temperature', None) != None:
  874. influx_fieldname_temperature = influxdb_yaml[o_influxInstance]['fieldnames'].get('temperature')
  875. else:
  876. influx_fieldname_temperature = influx_default_fieldname_temperature
  877. if influxdb_yaml[o_influxInstance]['fieldnames'].get('humidity', None) != None:
  878. influx_fieldname_humidity = influxdb_yaml[o_influxInstance]['fieldnames'].get('humidity')
  879. else:
  880. influx_fieldname_humidity = influx_default_fieldname_humidity
  881. if influxdb_yaml[o_influxInstance]['fieldnames'].get('dewpoint', None) != None:
  882. influx_fieldname_dewpoint = influxdb_yaml[o_influxInstance]['fieldnames'].get('dewpoint')
  883. else:
  884. influx_fieldname_dewpoint = influx_default_fieldname_dewpoint
  885. if influxdb_yaml[o_influxInstance]['fieldnames'].get('abshum', None) != None:
  886. influx_fieldname_abshum = influxdb_yaml[o_influxInstance]['fieldnames'].get('abshum')
  887. else:
  888. influx_fieldname_abshum = influx_default_fieldname_abshum
  889. influx_datatypes = influxdb_yaml[o_influxInstance].get('datatypes', None)
  890. if influx_datatypes == None:
  891. influx_datatype_temperature = influx_default_datatype_temperature
  892. influx_datatype_humidity = influx_default_datatype_humidity
  893. else:
  894. if influxdb_yaml[o_influxInstance]['datatypes'].get('temperature', None) != None:
  895. tmpdt = influxdb_yaml[o_influxInstance]['datatypes'].get('temperature')
  896. if tmpdt == 'float' or tmpdt == 'int':
  897. influx_datatype_temperature = tmpdt
  898. else:
  899. influx_datatype_temperature = influx_default_datatype_temperature
  900. else:
  901. influx_datatype_temperature = influx_default_datatype_temperature
  902. if influxdb_yaml[o_influxInstance]['datatypes'].get('humidity', None) != None:
  903. tmpdt = influxdb_yaml[o_influxInstance]['datatypes'].get('humidity')
  904. if tmpdt == 'float' or tmpdt == 'int':
  905. influx_datatype_humidity = tmpdt
  906. else:
  907. influx_datatype_humidity = influx_default_datatype_humidity
  908. else:
  909. influx_datatype_humidity = influx_default_datatype_humidity
  910. if influxdb_yaml[o_influxInstance]['datatypes'].get('dewpoint', None) != None:
  911. tmpdt = influxdb_yaml[o_influxInstance]['datatypes'].get('dewpoint')
  912. if tmpdt == 'float' or tmpdt == 'int':
  913. influx_datatype_dewpoint = tmpdt
  914. else:
  915. influx_datatype_dewpoint = influx_default_datatype_dewpoint
  916. else:
  917. influx_datatype_dewpoint = influx_default_datatype_dewpoint
  918. if influxdb_yaml[o_influxInstance]['datatypes'].get('abshum', None) != None:
  919. tmpdt = influxdb_yaml[o_influxInstance]['datatypes'].get('abshum')
  920. if tmpdt == 'float' or tmpdt == 'int':
  921. influx_datatype_abshum = tmpdt
  922. else:
  923. influx_datatype_abshum = influx_default_datatype_abshum
  924. else:
  925. influx_datatype_abshum = influx_default_datatype_abshum
  926. if influx_datatype_temperature == 'int':
  927. influx_value_temp = int(out_temp_publishvalue)
  928. else:
  929. influx_value_temp = float(out_temp_publishvalue)
  930. if influx_datatype_humidity == 'int':
  931. influx_value_hum = int(out_hum_publishvalue)
  932. else:
  933. influx_value_hum = float(out_hum_publishvalue)
  934. if config['sensors'].getboolean('calculate_dewpoint', False) and config['sensors'].getboolean('write_dewpoint_to_influxdb', False):
  935. if influx_datatype_dewpoint == 'int':
  936. influx_value_dewpoint = int(out_dewpoint)
  937. else:
  938. influx_value_dewpoint = float(out_dewpoint)
  939. if config['sensors'].getboolean('calculate_absolute_humidity', False) and config['sensors'].getboolean('write_abshum_to_influxdb', False):
  940. if influx_datatype_abshum == 'int':
  941. influx_value_abshum = int(out_abshum)
  942. else:
  943. influx_value_abshum = float(out_abshum)
  944. influx_json = [
  945. {
  946. 'measurement': influx_measurement,
  947. 'tags': {
  948. 'sensor': o_influxSensorName
  949. },
  950. 'time': t_str,
  951. 'fields': {
  952. influx_fieldname_temperature: influx_value_temp,
  953. influx_fieldname_humidity: influx_value_hum
  954. }
  955. }
  956. ]
  957. if config['sensors'].getboolean('calculate_dewpoint', False) and config['sensors'].getboolean('write_dewpoint_to_influxdb', False) and out_dewpoint < 200:
  958. influx_json[0]["fields"][influx_fieldname_dewpoint] = influx_value_dewpoint
  959. if config['sensors'].getboolean('calculate_absolute_humidity', False) and config['sensors'].getboolean('write_abshum_to_influxdb', False) and out_abshum < 200:
  960. influx_json[0]["fields"][influx_fieldname_abshum] = influx_value_abshum
  961. try:
  962. if verbosemode:
  963. print("write to InfluxDB...")
  964. print(influx_json)
  965. influxclient[o_influxInstance].write_points(influx_json)
  966. if verbosemode: print("DONE!")
  967. except Exception as e:
  968. print("Error writing to InfluxDB")
  969. print(influx_json)
  970. print(e)
  971. # handle outdated sensor values once a minute
  972. if (int(time.time()) - checkLastUpdateInterval_lastRun) > checkLastUpdateInterval:
  973. # exit program if nothing has been received from JeeLink for 5 min - will be restarted by systemd
  974. now = int(time.time())
  975. if (now - serialReceivedLastTime) > serialReceived_maxAge:
  976. print("Nothing received from JeeLink for " + str(now - serialReceivedLastTime) + "s - there is something wrong. Exiting program so that it will be restarted by systemd.")
  977. quit()
  978. checkLastUpdateInterval_lastRun = int(time.time())
  979. #print("check lastUpdate")
  980. for key in sensors_yaml:
  981. #print(key, '->', sensors_yaml[key])
  982. #print("Sensor name:", key)
  983. sensorId = sensors_yaml[key].get('LaCrosseID', None)
  984. if sensorId >= 0:
  985. lupd = sensors_lastUpdate.get(sensorId, None)
  986. tdiff = 0
  987. if lupd is not None:
  988. tdiff = int(time.time()) - lupd
  989. if lupd is None or (tdiff > sensordata_maxage):
  990. mqttc.publish(mqtt_topic_prefix+"/"+ key +"/availability", "unavailable", qos=0, retain=False)
  991. notifystr = "received no data from sensor '" + key + "' with ID " + str(sensorId) + " for " + str(tdiff) + "s"
  992. mqttc.publish(mqtt_topic_prefix+"/" + mqtt_subtopic_notify, notifystr, qos=0, retain=False)
  993. except KeyboardInterrupt:
  994. print('\n')
  995. exit()