#!/usr/bin/python3 -u # -*- coding: utf-8 -*- # import serial import time from time import localtime, strftime from datetime import datetime import configparser from influxdb import InfluxDBClient import os import sys import paho.mqtt.client as mqtt import yaml import json import statistics import math from time import sleep #import math #import numpy as np #import httplib # Change working dir to the same dir as this script os.chdir(sys.path[0]) # stop program if nothing is received from JeeLink for 5 min (will be restarted by systemd) serialReceived_maxAge = 300 config = configparser.ConfigParser() config.read('jeelinklog.ini') serialport = config['jeelink'].get('serialport') serialbaud = int(config['jeelink'].get('baudrate')) if config['jeelink'].get('initTimeout') != None: serialInitTimeout = int(config['jeelink'].get('initTimeout')) else: serialInitTimeout = 2 jeelinkInitCmd = config['jeelink'].get('initCmd') if jeelinkInitCmd == None: jeelinkInitCmd = "30t 3m v" # init cmd for TX29 AND TX35 sensors (17.241 and 9.579 kbps toggle every 30s) #jeelinkInitCmd = "0t 1m v" # init cmd for TX29 sensors only (17.241 kbps, no toggle) mqtt_topic_prefix = config['mqtt'].get('topic_prefix') topic_prefix_outside_temphum = config['mqtt'].get('topic_prefix_outside_temphum') mqtt_topic_atemp = config['mqtt'].get('topic_outside_temp') mqtt_topic_ahum = config['mqtt'].get('topic_outside_hum') mqtt_topic_ahum = config['mqtt'].get('topic_outside_dew') mqtt_topic_ahum = config['mqtt'].get('topic_outside_abshum') mqtt_topic_domoticz_in = "domoticz/in" mqtt_subtopic_notify = config['mqtt'].get('subtopic_notify') logfile_new_sensors = config['main'].get('logfile_new_sensors') path_new_sensors_folder = config['main'].get('path_new_sensors_folder') log_path = config['main'].get('log_path') if not os.path.exists(log_path): os.makedirs(log_path) verbosemode = False #sensors_conf_file = "/home/pi/jeelink_sensors.csv" sensordata_maxage = int(config['sensors'].get('data_maxage')) #minUpdateInterval = 60 #aTempHumPublishInterval = 60 #override_updateinterval_on_change = False #atemp_sensor_idx = 94 #atemp_sensor_idx_2 = 113 average_value_steps = int(config['sensors'].get('average_value_steps')) if average_value_steps == 0: average_value_steps = 1 if len(sys.argv) > 1 and str(sys.argv[1]) == "-v": verbosemode = True def touch(fname, times=None): with open(fname, 'a'): os.utime(fname, times) def on_connect(client, userdata, flags, rc): if verbosemode: print("MQTT connected with result code " + str(rc) + "\n") #client.subscribe("wetter/atemp") def on_disconnect(client, userdata, rc): if rc != 0: print("Unexpected MQTT disconnection. Will auto-reconnect\n") #def on_message(client, userdata, msg): # #print(msg.topic + " " + str(msg.payload)) # global atemp # atemp = msg.payload # dont edit below #starting values only.. ##atemp = 61 ##ahum = 101 ##atemp1 = 61 ##ahum1 = 101 ##atemp2 = 61 ##ahum2 = 101 ##atemp_last = 61 ##ahum_last = 101 checkLastUpdateInterval = 60 checkLastUpdateInterval_lastRun = 0 lastPublishTime = 0 lastStoreTime = 0 sensors = dict() sensors_id_to_name = dict() #sensors_idx = dict() sensors_lastTemp = dict() sensors_lastHum = dict() sensors_lastUpdate = dict() sensors_lastReceivedValue_temp = dict() sensors_lastReceivedTime = dict() sensors_lastValues_temp = dict() sensors_lastValues_hum = dict() sensors_lastAvgValue_temp = dict() sensors_lastAvgValue_hum = dict() sensors_lastValues_lastIndex = dict() #sensors_unavailable = dict() sensors_batteryState = dict() sensors_new_alreadyNotified = dict() sensors_outside_sensors = [] outSens_lastRun = 0 outSens_store_lastRun = 0 influx_default_fieldname_temperature = 'Temperature' influx_default_fieldname_humidity = 'Humidity' influx_default_fieldname_dewpoint = 'Dewpoint' influx_default_fieldname_abshum = 'AbsHumidity' influx_default_datatype_temperature = 'float' influx_default_datatype_humidity = 'int' influx_default_datatype_dewpoint = 'float' influx_default_datatype_abshum = 'float' sensors_yaml = yaml.load(open(config['main'].get('sensors_config_yml')), Loader=yaml.FullLoader) def list_duplicates(seq): seen = set() seen_add = seen.add # adds all elements it doesn't know yet to seen and all other to seen_twice seen_twice = set( x for x in seq if x in seen or seen_add(x) ) # turn the set into a list (as requested) return list( seen_twice ) # Dew point calculation based on formula and JS code found on: https://www.wetterochs.de/wetter/feuchte.html#f4 # dew point from temperature, relative humidity # in °C def dewpoint(temp, relHum): if temp >= 0: a = 7.5 b = 237.3 else: a = 7.6 b = 240.7 c = math.log(vaporPressure(temp, relHum)/6.1078) * math.log10(math.e) return (b * c) / (a - c) def celsiusToKelvin(temp): return temp + 273.15 def absoluteHumidity(relHum, temp): # AF = absolute Feuchte in g Wasserdampf pro m3 Luft # R* = 8314.3 J/(kmol*K) (universelle Gaskonstante) # mw = 18.016 kg/kmol (Molekulargewicht des Wasserdampfes) # r = relative Luftfeuchte # T = Temperatur in °C # TK = Temperatur in Kelvin (TK = T + 273.15) # TD = Taupunkttemperatur in °C # # mw/R* = 18.016 / 8314.3 = 0,0021668691290908 # # AF(r,TK) = 10^5 * mw/R* * DD(T,r)/TK # unused 2nd formula: AF(TD,TK) = 10^5 * mw/R* * SDD(TD)/TK mw = 18.016 R = 8314.3 return 10**5 * mw/R * vaporPressure(temp, relHum) / celsiusToKelvin(temp) # Saettingungsdampfdruck / saturation vapour pressure # from temperature in hPa def saturationVaporPressure(temp): if temp >= 0: a = 7.5 b = 237.3 else: a = 7.6 b = 240.7 return 6.1078 * math.exp(((a*temp)/(b + temp))/ math.log10(math.e)) # Dampfdruck / vapour pressure # from temperature, relative humidity in hPa def vaporPressure(temp, relHum): return relHum/100 * saturationVaporPressure(temp) if verbosemode: print("JeeLink2MQTT by Flo Kra") print("=======================================================================") print() print("JeeLink initCmd: " + jeelinkInitCmd) print() print() # load InfluxDB configuration influxdb_yaml = yaml.load(open(config['main'].get('influx_config_yml')), Loader=yaml.SafeLoader) if verbosemode: print("loading InfluxDB configuration...") print("InfluxDB Instances:") print(json.dumps(influxdb_yaml, indent=4)) influxclient = dict() for instance in influxdb_yaml: i_host = influxdb_yaml[instance].get('host', None) i_port = int(influxdb_yaml[instance].get('port', 8086)) i_username = influxdb_yaml[instance].get('username', None) i_password = influxdb_yaml[instance].get('password', None) i_database = influxdb_yaml[instance].get('database', None) if i_host != None and i_database != None: if i_username != None and i_password != None: influxclient[instance] = InfluxDBClient(i_host, i_port, i_username, i_password, i_database) else: influxclient[instance] = InfluxDBClient(i_host, i_port, i_database) if verbosemode: print() print() # load sensors configuration if verbosemode: print("loading sensors configuration: ") print(json.dumps(sensors_yaml, indent=3)) print() for key in sensors_yaml: #print(key, '->', sensors_yaml[key]) if verbosemode: print("Sensor name:", key) sensorName = key if sensors_yaml[key].get('LaCrosseID') >= 0: if verbosemode: print("LaCrosseID:", sensors_yaml[key].get('LaCrosseID')) sensorId = sensors_yaml[key].get('LaCrosseID') sensors_id_to_name[sensorId] = sensorName if sensors_yaml[key].get('DomoticzIdx'): if verbosemode: print("DomoticzIdx:", sensors_yaml[key].get('DomoticzIdx')) #sensors_idx[sensorId] = sensors_yaml[key].get('DomoticzIdx') if sensors_yaml[key].get('Topic_Temp'): if verbosemode: print("Topic_Temp:", sensors_yaml[key].get('Topic_Temp')) if sensors_yaml[key].get('Topic_Hum'): if verbosemode: print("Topic_Hum:", sensors_yaml[key].get('Topic_Hum')) if sensors_yaml[key].get('InfluxDB_Instance'): if verbosemode: print("InfluxDB_Instance:", sensors_yaml[key].get('InfluxDB_Instance')) if sensors_yaml[key].get('InfluxDB_Instance') not in influxdb_yaml: print("Error: invalid InfluxDB instance '" + sensors_yaml[key].get('InfluxDB_Instance') + "' configured for sensor '" + sensorName + "'") if sensors_yaml[key].get('isOutsideTempSensor'): if verbosemode: print("isOutsideTempSensor:", sensors_yaml[key].get('isOutsideTempSensor')) sensors_outside_sensors.append(sensorId) else: print("WARNING: Sensor " + key + " has no LaCrosseID!") if verbosemode: print() if verbosemode: print() print() #print(json.dumps(sensor)) #print("sensors_id_to_name =",sensors_id_to_name) #print("outside sensors: ", sensors_outside_sensors) mqttc = mqtt.Client() mqttc.on_connect = on_connect mqttc.on_disconnect = on_disconnect ##mqttc.on_message = on_message if config['mqtt'].get('user') != "" and config['mqtt'].get('password') != "": mqttc.username_pw_set(config['mqtt'].get('user'), config['mqtt'].get('password')) mqttc.connect(config['mqtt'].get('server'), config['mqtt'].getint('port'), 60) mqttc.loop_start() #mqttc.loop_forever() ser = serial.Serial(port=serialport, baudrate = serialbaud, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, bytesize=serial.EIGHTBITS, timeout=1) checkLastUpdateInterval_lastRun = time.time() # first check after timeout expired # set serialReceivedLastTime to current time so that we can detect if nothing is received for some time serialReceivedLastTime = int(time.time()) # init JeeLink device sleep(serialInitTimeout) ser.flushInput() ser.write(jeelinkInitCmd.encode('ascii')) # send init command to Jeelink #ser.write('v\r\n'.encode('ascii')) # get JeeLink version info sleep(0.2) try: while True: msg_was_sent = 0 #clear serial buffer to remove junk and noise ###ser.flushInput() #read buffer until cr/lf serLine = ser.readline().strip() # catch exception on invalid char coming in: UnicodeDecodeError: 'ascii' codec can't decode byte 0xf4 in position 6: ordinal not in range(128) try: serLine = serLine.decode('ascii') except: serLine = False if(serLine): if serLine.find('LaCrosseITPlusReader') != -1: if verbosemode: print("Jeelink version/config: " + serLine) print() print() elif serLine.find('OK 9') != -1: # set serialReceivedLastTime to current time so that we can detect if nothing is received for some time serialReceivedLastTime = int(time.time()) if verbosemode: print("RX: " + serLine + " => LaCrosse sensor") # uns interessieren nur reinkommende Zeilen die mit "OK 9 " beginnen # 0 1 2 3 4 5 6 # OK 9 ID XXX XXX XXX XXX # | | | | | | | # | | | | | | --- Humidity incl. WeakBatteryFlag # | | | | | |------ Temp * 10 + 1000 LSB # | | | | |---------- Temp * 10 + 1000 MSB # | | | |-------------- Sensor type (1 or 2) +128 if NewBatteryFlag # | | |----------------- Sensor ID # | |------------------- fix "9" # |---------------------- fix "OK" serLineParts = serLine.split(' ') #print("Len: " + str(len(serLineParts))) if(len(serLineParts) == 7): # check correct size of incoming data #addr = serLineParts[2] #addr = "{0:x}".format(int(serLineParts[2])) #addr = hex((int(serLineParts[2]))) addr = int(serLineParts[2]) addrhex = "{0:x}".format(int(serLineParts[2])) lastUpdate = sensors_lastUpdate.get(addr, None) lastTemp = sensors_lastTemp.get(addr, None) lastHum = sensors_lastHum.get(addr, None) currentsensor_name = sensors_id_to_name.get(addr, None) # extract sensor data received from JeeLink if int(serLineParts[3]) >= 128: batt_new = 1 type = int(serLineParts[3]) - 128 else: batt_new = 0 type = int(serLineParts[3]) temp = (int(serLineParts[4])*256 + int(serLineParts[5]) - 1000)/10.0 if int(serLineParts[6]) >= 128: batt_low = 1 hum = int(serLineParts[6]) - 128 else: batt_low = 0 hum = int(serLineParts[6]) if hum > 100: hum = 100 if batt_new == 1: sensors_batteryState[addr] = 2 elif batt_low == 1: sensors_batteryState[addr] = 1 else: sensors_batteryState[addr] = 0 lastValues_lastIndex = sensors_lastValues_lastIndex.get(addr, None) if sensors_lastValues_temp.get(addr, None) == None: sensors_lastValues_temp[addr] = [None] * average_value_steps if sensors_lastValues_hum.get(addr, None) == None: sensors_lastValues_hum[addr] = [None] * average_value_steps data_okay = False if sensors_lastReceivedValue_temp.get(addr, None) == None: # this is the first time we receive from that sensor in that session if verbosemode: print("first received from sensor",str(addr)) sensors_lastReceivedValue_temp[addr] = temp sensors_lastReceivedTime[addr] = int(time.time()) #lastValues_lastIndex = 0 else: lastValue = sensors_lastReceivedValue_temp.get(addr, None) max_off_value = float(config['sensors'].get('max_off_value')) ignore_off_value_timeout = int(config['sensors'].get('ignore_off_value_timeout')) 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 sensors_lastReceivedValue_temp[addr] = temp sensors_lastReceivedTime[addr] = int(time.time()) #print("Last Value=",lastValue,"currValue=",temp) data_okay = True else: if verbosemode: print("skipped sensor reading - Last Value=",lastValue,"currValue=",temp) if data_okay: if lastValues_lastIndex == None: lastValues_lastIndex = 0 elif lastValues_lastIndex == (average_value_steps - 1): lastValues_lastIndex = 0 else: lastValues_lastIndex += 1 if verbosemode: print("lastValues_lastIndex =", lastValues_lastIndex) sensors_lastValues_lastIndex[addr] = lastValues_lastIndex sensors_lastValues_temp[addr][lastValues_lastIndex] = temp sensors_lastValues_hum[addr][lastValues_lastIndex] = hum sensors_lastUpdate[addr] = int(time.time()) if verbosemode: print("sensors_lastValues_temp =", sensors_lastValues_temp[addr]) if currentsensor_name is None: if batt_new == 1 and not sensors_new_alreadyNotified.get('addr', False): notifystr = "NEW sensor with ID " + str(addr) mqttc.publish(mqtt_topic_prefix+"/" + mqtt_subtopic_notify, notifystr, qos=0, retain=False) if not os.path.exists(log_path+'/new'): os.makedirs(log_path+'/new') fname = log_path + '/new/' + str(addr) if not os.path.exists(fname): try: touch(fname) except: pass try: f = open(log_path+'/'+logfile_new_sensors, 'a') f.write(str(datetime.now())+": "+notifystr+"\n") f.close() except: # guat dann hoit ned... pass sensors_new_alreadyNotified[addr] = True else: if not os.path.exists(log_path+'/unknown'): os.makedirs(log_path+'/unknown') fname = log_path + '/unknown/' + str(addr) if not os.path.exists(fname): try: touch(fname) except: pass if verbosemode: print("unknown sensor ID " + str(addr)) if verbosemode: 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)) print() #if senddata: # sensors_lastUpdate[str(addr)] = int(time.time()) # sensors_lastTemp[str(addr)] = temp # sensors_lastHum[str(addr)] = hum # # isAtemp = False # if int(currentsensor_idx) == atemp_sensor_idx: # atemp1 = temp # ahum1 = hum # isAtemp = True # elif int(currentsensor_idx) == atemp_sensor_idx_2: # atemp2 = temp # ahum2 = hum # isAtemp = True # # if isAtemp: # if atemp1 <= atemp2: # atemp = atemp1 # ahum = ahum1 # else: # atemp = atemp2 # ahum = ahum2 # # if atemp < 61 and ahum < 101: # if atemp != atemp_last or ahum != ahum_last or ((time.time() - atemphum_lastUpdate) > aTempHumPublishInterval): # atemphum_lastUpdate = time.time() # atemp_last = atemp # ahum_last = ahum # mqttc.publish(mqtt_topic_atemp, str(atemp), qos=0, retain=True) # mqttc.publish(mqtt_topic_ahum, str(ahum), qos=0, retain=True) # mqttc.publish(mqtt_topic_atemphum_lastUpdate, strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False) # # domoticz_json = "{\"idx\":" + str(currentsensor_idx) + ",\"nvalue\":0,\"svalue\":\"" + str(temp) + ";" + str(hum) + ";1\"}" # #if verbosemode: # # print(domoticz_json) # mqttc.publish(mqtt_topic_domoticz_in, domoticz_json, qos=0, retain=False) # # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/temperature", str(temp), qos=0, retain=False) # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/humidity", str(hum), qos=0, retain=False) # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/battery", str(batterystate), qos=0, retain=False) # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/lastUpdate", strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False) # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/availability", "available", qos=0, retain=False) # # lacrosse_json = "{\"temperature\":" + str(temp) + ", \"humidity\":" + str(hum) + ", \"battery\":\"" + str(batterystate) + "\"}" # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/json", lacrosse_json, qos=0, retain=False) # # tmptext = str(temp) + "° " + str(hum) + "%" # mqttc.publish(mqtt_topic_prefix+"/"+str(currentsensor_name)+"/TempHumText", tmptext, qos=0, retain=False) # # if verbosemode: # print("MQTT published") # # try: # touch("/tmp/jeelink2mqtt_running") # except: # # guat dann ned... # pass # #else: # if verbosemode: # if currentsensor_name is None: # print("MQTT published") # else: # print("MQTT publishing surpressed (interval not expired)") # # #if verbosemode: # print("\n") else: if verbosemode: print("RX: " + serLine + " => ignored invalid data") pass else: if verbosemode: print("RX: " + serLine) pass # publish on MQTT on set interval publishNow = False if (int(time.time()) - lastPublishTime) > int(config['sensors'].get('publish_interval')): if lastPublishTime == 0: lastPublishTime = int(time.time()) else: publishNow = True lastPublishTime = int(time.time()) #for sensor in #sensors_lastReceivedTime[str(addr)] # store to InfluxDB on set interval storeNow = False if (int(time.time()) - lastStoreTime) > int(config['sensors'].get('store_interval')): if lastStoreTime == 0: lastStoreTime = int(time.time()) else: storeNow = True lastStoreTime = int(time.time()) if publishNow or storeNow: #print("available sensor data: ", sensors_lastUpdate) for id in sensors_lastUpdate: if (time.time() - sensors_lastUpdate[id]) < sensordata_maxage: s_name = sensors_id_to_name.get(id, None) if verbosemode: print("current data available for:", id, s_name) sensorDataComplete = True sum_temp=0 for val in sensors_lastValues_temp[id]: if val is None: sensorDataComplete = False else: sum_temp = sum_temp + val sum_hum=0 for val in sensors_lastValues_hum[id]: if val is None: sensorDataComplete = False else: sum_hum = sum_hum + val if sensorDataComplete: s_currAvgTemp = round(sum_temp / len(sensors_lastValues_temp[id]), 1) s_currAvgHum = int(sum_hum / len(sensors_lastValues_hum[id])) # calc dewpoint if config['sensors'].getboolean('calculate_dewpoint', False): s_currDewpoint = round(dewpoint(s_currAvgTemp, s_currAvgHum), 1) # calc absolute humidity if config['sensors'].getboolean('calculate_absolute_humidity', False): s_currAbsHum = round(absoluteHumidity(s_currAvgHum, s_currAvgTemp), 2) sensors_lastAvgValue_temp[id] = s_currAvgTemp sensors_lastAvgValue_hum[id] = s_currAvgHum if verbosemode: print("s_currAvgTemp =", s_currAvgTemp, "s_currAvgHum =", s_currAvgHum) else: if verbosemode: print("s_currAvgTemp/s_currAvgHum: not yet enough readings available") if sensorDataComplete: # as Home Assistant MQTT binary sensor can only work with 2 states (unless using value templates): # -> removed "NEW" state string from /battery topic and added /batteryNew topic if sensors_batteryState.get(id,None) == 2: s_battNew = True s_battState = "OK" elif sensors_batteryState.get(id, None) == 1: s_battNew = False s_battState = "LOW" elif sensors_batteryState.get(id, None) == 0: s_battNew = False s_battState = "OK" if s_battNew: s_battNew_str = "YES" else: s_battNew_str = "NO" if s_name is not None: s_domIdx = sensors_yaml[s_name].get('DomoticzIdx', None) s_topic_temp = sensors_yaml[s_name].get('Topic_Temp', None) s_topic_hum = sensors_yaml[s_name].get('Topic_Hum', None) s_topic_dew = sensors_yaml[s_name].get('Topic_Dew', None) s_topic_absHum = sensors_yaml[s_name].get('Topic_AbsHum', None) s_influxInstance = sensors_yaml[s_name].get('InfluxDB_Instance', None) s_isOutsideTempSensor = sensors_yaml[s_name].get('isOutsideTempSensor', None) if s_domIdx is not None and publishNow: domoticz_json = "{\"idx\":" + str(s_domIdx) + ",\"nvalue\":0,\"svalue\":\"" + str(s_currAvgTemp) + ";" + str(s_currAvgHum) + ";1\"}" if verbosemode: print("Domoticz JSON:", domoticz_json) mqttc.publish(mqtt_topic_domoticz_in, domoticz_json, qos=0, retain=False) if s_topic_temp is not None and len(s_topic_temp)>5 and publishNow: if verbosemode: print("publishing temp on ", s_topic_temp) mqttc.publish(s_topic_temp, str(s_currAvgTemp), qos=0, retain=False) if s_topic_dew is not None and len(s_topic_dew)>5 and publishNow and config['sensors'].getboolean('calculate_dewpoint', False): if verbosemode: print("publishing dewpoint on ", s_topic_dew) mqttc.publish(s_topic_dew, str(s_currDewpoint), qos=0, retain=False) if s_topic_absHum is not None and len(s_topic_absHum)>5 and publishNow and config['sensors'].getboolean('calculate_absolute_humidity', False): if verbosemode: print("publishing absolute humidty on ", s_topic_absHum) mqttc.publish(s_topic_absHum, str(s_currAbsHum), qos=0, retain=False) if s_topic_hum is not None and len(s_topic_hum)>5 and publishNow: if verbosemode: print("publishing hum on ", s_topic_temp) mqttc.publish(s_topic_hum, str(s_currAvgHum), qos=0, retain=False) if publishNow: mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/temperature", str(s_currAvgTemp), qos=0, retain=False) mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/humidity", str(s_currAvgHum), qos=0, retain=False) if config['sensors'].getboolean('calculate_dewpoint', False): mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/dewpoint", str(s_currDewpoint), qos=0, retain=False) if config['sensors'].getboolean('calculate_absolute_humidity', False): mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/absoluteHumidity", str(s_currAbsHum), qos=0, retain=False) mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/battery", s_battState, qos=0, retain=False) mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/batteryNew", s_battNew_str, qos=0, retain=False) mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/availability", "available", qos=0, retain=False) mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/lastUpdate", strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False) # publish global lastUpdate for monitoring purposes mqttc.publish(mqtt_topic_prefix+"/lastUpdate", strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=False) # JSON output lacrosse_json = "{\"temperature\":" + str(s_currAvgTemp) \ + ", \"humidity\":" + str(s_currAvgHum) \ + ", \"battery\":\"" + str(s_battState) + "\"" if config['sensors'].getboolean('calculate_dewpoint', False): lacrosse_json = lacrosse_json + ", \"dewpoint\":" + str(s_currDewpoint) if config['sensors'].getboolean('calculate_absolute_humidity', False): lacrosse_json = lacrosse_json + ", \"absoluteHumidity\":" + str(s_currAbsHum) lacrosse_json = lacrosse_json + "}" mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/json", lacrosse_json, qos=0, retain=False) tmptext = str(s_currAvgTemp) + "° " + str(s_currAvgHum) + "%" mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/TempHumText", tmptext, qos=0, retain=False) if s_influxInstance is not None and storeNow: ### write to InfluxDB here if s_influxInstance in influxdb_yaml: influx_measurement = influxdb_yaml[s_influxInstance].get('measurement', None) t_utc = datetime.utcnow() t_str = t_utc.isoformat() + 'Z' if influx_measurement is not None: influx_fieldnames = influxdb_yaml[s_influxInstance].get('fieldnames', None) if influx_fieldnames == None: influx_fieldname_temperature = influx_default_fieldname_temperature influx_fieldname_humidity = influx_default_fieldname_humidity influx_fieldname_dewpoint = influx_default_fieldname_dewpoint influx_fieldname_abshum = influx_default_fieldname_abshum else: if influxdb_yaml[s_influxInstance]['fieldnames'].get('temperature', None) != None: influx_fieldname_temperature = influxdb_yaml[s_influxInstance]['fieldnames'].get('temperature') else: influx_fieldname_temperature = influx_default_fieldname_temperature if influxdb_yaml[s_influxInstance]['fieldnames'].get('humidity', None) != None: influx_fieldname_humidity = influxdb_yaml[s_influxInstance]['fieldnames'].get('humidity') else: influx_fieldname_humidity = influx_default_fieldname_humidity if influxdb_yaml[s_influxInstance]['fieldnames'].get('dewpoint', None) != None: influx_fieldname_dewpoint = influxdb_yaml[s_influxInstance]['fieldnames'].get('dewpoint') else: influx_fieldname_dewpoint = influx_default_fieldname_dewpoint if influxdb_yaml[s_influxInstance]['fieldnames'].get('abshum', None) != None: influx_fieldname_abshum = influxdb_yaml[s_influxInstance]['fieldnames'].get('abshum') else: influx_fieldname_abshum = influx_default_fieldname_abshum influx_datatypes = influxdb_yaml[s_influxInstance].get('datatypes', None) if influx_datatypes == None: influx_datatype_temperature = influx_default_datatype_temperature influx_datatype_humidity = influx_default_datatype_humidity else: if influxdb_yaml[s_influxInstance]['datatypes'].get('temperature', None) != None: tmpdt = influxdb_yaml[s_influxInstance]['datatypes'].get('temperature') if tmpdt == 'float' or tmpdt == 'int': influx_datatype_temperature = tmpdt else: influx_datatype_temperature = influx_default_datatype_temperature else: influx_datatype_temperature = influx_default_datatype_temperature if influxdb_yaml[s_influxInstance]['datatypes'].get('humidity', None) != None: tmpdt = influxdb_yaml[s_influxInstance]['datatypes'].get('humidity') if tmpdt == 'float' or tmpdt == 'int': influx_datatype_humidity = tmpdt else: influx_datatype_humidity = influx_default_datatype_humidity else: influx_datatype_humidity = influx_default_datatype_humidity if influxdb_yaml[s_influxInstance]['datatypes'].get('dewpoint', None) != None: tmpdt = influxdb_yaml[s_influxInstance]['datatypes'].get('dewpoint') if tmpdt == 'float' or tmpdt == 'int': influx_datatype_dewpoint = tmpdt else: influx_datatype_dewpoint = influx_default_datatype_dewpoint else: influx_datatype_dewpoint = influx_default_datatype_dewpoint if influxdb_yaml[s_influxInstance]['datatypes'].get('abshum', None) != None: tmpdt = influxdb_yaml[s_influxInstance]['datatypes'].get('abshum') if tmpdt == 'float' or tmpdt == 'int': influx_datatype_abshum = tmpdt else: influx_datatype_abshum = influx_default_datatype_abshum else: influx_datatype_abshum = influx_default_datatype_abshum if influx_datatype_temperature == 'int': influx_value_temp = int(s_currAvgTemp) else: influx_value_temp = float(s_currAvgTemp) if influx_datatype_humidity == 'int': influx_value_hum = int(s_currAvgHum) else: influx_value_hum = float(s_currAvgHum) if config['sensors'].getboolean('calculate_dewpoint', False) and config['sensors'].getboolean('write_dewpoint_to_influxdb', False): if influx_datatype_dewpoint == 'int': influx_value_dewpoint = int(s_currDewpoint) else: influx_value_dewpoint = float(s_currDewpoint) if config['sensors'].getboolean('calculate_absolute_humidity', False) and config['sensors'].getboolean('write_abshum_to_influxdb', False): if influx_datatype_abshum == 'int': influx_value_abshum = int(s_currAbsHum) else: influx_value_abshum = float(s_currAbsHum) influx_json = [ { 'measurement': influx_measurement, 'tags': { 'sensor': s_name }, 'time': t_str, 'fields': { influx_fieldname_temperature: influx_value_temp, influx_fieldname_humidity: influx_value_hum } } ] if config['sensors'].getboolean('calculate_dewpoint', False) and config['sensors'].getboolean('write_dewpoint_to_influxdb', False): influx_json[0]["fields"][influx_fieldname_dewpoint] = influx_value_dewpoint if config['sensors'].getboolean('calculate_absolute_humidity', False) and config['sensors'].getboolean('write_abshum_to_influxdb', False): influx_json[0]["fields"][influx_fieldname_abshum] = influx_value_abshum try: if verbosemode: print("write to InfluxDB...") print(influx_json) influxclient[s_influxInstance].write_points(influx_json) if verbosemode: print("DONE!") except Exception as e: print("Error writing to InfluxDB") print(influx_json) print(e) else: if verbosemode: print("Error: invalid InfluxDB instance '" + s_influxInstance + "' configured for sensor '" + s_name + "'") else: # this is an unknown sensor if publishNow: if s_battNew: if config['mqtt'].getboolean('publish_unknown_new_sensors'): mqttc.publish(mqtt_topic_prefix+"/NewUnknownSensor/"+str(id)+"/temperature", str(s_currAvgTemp), qos=0, retain=False) mqttc.publish(mqtt_topic_prefix+"/NewUnknownSensor/"+str(id)+"/humidity", str(s_currAvgHum), qos=0, retain=False) mqttc.publish(mqtt_topic_prefix+"/NewUnknownSensor/"+str(id)+"/batteryNew", s_battNew_str, qos=0, retain=False) else: if config['mqtt'].getboolean('publish_unknown_sensors'): mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(id)+"/temperature", str(s_currAvgTemp), qos=0, retain=False) mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(id)+"/humidity", str(s_currAvgHum), qos=0, retain=False) mqttc.publish(mqtt_topic_prefix+"/UnknownSensor/"+str(id)+"/battery", s_battState, qos=0, retain=False) if verbosemode: print() # outside sensors if (int(time.time()) - outSens_lastRun) > int(config['sensors'].get('publish_interval_outside', 60)): outSens_lastRun = int(time.time()) sum_out_sensors_temp = 0 sum_out_sensors_temp_min = 100 sum_out_sensors_temp_max = -50 sum_out_sensors_hum = 0 sum_out_sensors_hum_min = 100 sum_out_sensors_hum_max = -50 count_used_out_sensors = 0 # "declare" variables with invalid high values - will be overwritten later out_temp_publishvalue = 200 out_hum_publishvalue = 200 out_dewpoint = 200 out_abshum = 200 # median if(config['sensors'].getboolean('outside_sensors_use_median')): out_sensors_temp_median_values = [] out_sensors_hum_median_values = [] for id in sensors_outside_sensors: lupd = sensors_lastUpdate.get(id, None) tdiff = 0 if lupd is not None: tdiff = int(time.time()) - lupd if lupd is not None and (tdiff < sensordata_maxage): tmpval_t = sensors_lastAvgValue_temp.get(id, None) tmpval_h = sensors_lastAvgValue_hum.get(id, None) if tmpval_t is not None and tmpval_h is not None: sum_out_sensors_temp = sum_out_sensors_temp + tmpval_t sum_out_sensors_hum = sum_out_sensors_hum + tmpval_h if tmpval_t < sum_out_sensors_temp_min: sum_out_sensors_temp_min = tmpval_t if tmpval_t < sum_out_sensors_hum_min: sum_out_sensors_hum_min = tmpval_h if tmpval_t > sum_out_sensors_temp_max: sum_out_sensors_temp_max = tmpval_t if tmpval_t > sum_out_sensors_hum_max: sum_out_sensors_hum_max = tmpval_h # median if(config['sensors'].getboolean('outside_sensors_use_median')): out_sensors_temp_median_values.append(tmpval_t) out_sensors_hum_median_values.append(tmpval_h) count_used_out_sensors += 1 lacrosse_json = None if count_used_out_sensors > 0: out_temp_avg = round((sum_out_sensors_temp / count_used_out_sensors), 1) out_hum_avg = int(round((sum_out_sensors_hum / count_used_out_sensors), 0)) if(config['sensors'].getboolean('outside_sensors_use_median')): # median out_temp_median = round(statistics.median(out_sensors_temp_median_values), 1) out_hum_median = int(round(statistics.median(out_sensors_hum_median_values), 0)) out_temp_publishvalue = out_temp_median out_hum_publishvalue = out_hum_median else: out_temp_publishvalue = out_temp_avg out_hum_publishvalue = out_hum_avg # calc dewpoint if config['sensors'].getboolean('calculate_dewpoint', False): out_dewpoint = round(dewpoint(out_temp_avg, out_hum_avg), 1) # calc absolute humidity if config['sensors'].getboolean('calculate_absolute_humidity', False): out_abshum = round(absoluteHumidity(out_hum_avg, out_temp_avg), 2) mqttc.publish(mqtt_topic_atemp, str(out_temp_publishvalue), qos=0, retain=True) mqttc.publish(mqtt_topic_ahum, str(out_hum_publishvalue), qos=0, retain=True) mqttc.publish(topic_prefix_outside_temphum + '/temperature', str(out_temp_publishvalue), qos=0, retain=True) mqttc.publish(topic_prefix_outside_temphum + '/humidity', str(out_hum_publishvalue), qos=0, retain=True) if config['sensors'].getboolean('calculate_dewpoint', False): mqttc.publish(topic_prefix_outside_temphum + '/dewpoint', str(out_dewpoint), qos=0, retain=True) if config['sensors'].getboolean('calculate_absolute_humidity', False): mqttc.publish(topic_prefix_outside_temphum + '/absoluteHumidity', str(out_abshum), qos=0, retain=True) mqttc.publish(topic_prefix_outside_temphum + '/temp_average', str(out_temp_avg), qos=0, retain=True) mqttc.publish(topic_prefix_outside_temphum + '/hum_average', str(out_hum_avg), qos=0, retain=True) if(config['sensors'].getboolean('outside_sensors_use_median')): mqttc.publish(topic_prefix_outside_temphum + '/temp_median', str(out_temp_median), qos=0, retain=True) mqttc.publish(topic_prefix_outside_temphum + '/hum_median', str(out_hum_median), qos=0, retain=True) mqttc.publish(topic_prefix_outside_temphum + '/lastUpdate', strftime("%Y-%m-%d %H:%M:%S", localtime()), qos=0, retain=True) tmptext = str(out_temp_publishvalue) + "° " + str(out_hum_publishvalue) + "%" mqttc.publish(topic_prefix_outside_temphum + "/TempHumText", tmptext, qos=0, retain=False) lacrosse_json = "{\"temperature\":" + str(out_temp_publishvalue) + \ ", \"humidity\":" + str(out_hum_publishvalue) + "\"" \ ", \"usedSensors\":" + str(count_used_out_sensors) min = 100 max = 100 if count_used_out_sensors > 1: mqttc.publish(topic_prefix_outside_temphum + '/usedSensors', str(count_used_out_sensors), qos=0, retain=True) lacrosse_json = lacrosse_json + \ ", \"temp_average\":" + str(out_temp_avg) + \ ", \"hum_average\":" + str(out_hum_avg) + \ ", \"temp_median\":" + str(out_temp_median) + \ ", \"hum_median\":" + str(out_temp_median) if sum_out_sensors_temp_min < 100: mqttc.publish(topic_prefix_outside_temphum + '/temp_min', str(sum_out_sensors_temp_min), qos=0, retain=False) lacrosse_json = lacrosse_json + ", \"temp_min\":" + str(sum_out_sensors_temp_min) if sum_out_sensors_temp_max > -50: mqttc.publish(topic_prefix_outside_temphum + '/temp_max', str(sum_out_sensors_temp_max), qos=0, retain=False) lacrosse_json = lacrosse_json + ", \"temp_max\":" + str(sum_out_sensors_temp_max) if sum_out_sensors_hum_min < 100: mqttc.publish(topic_prefix_outside_temphum + '/hum_min', str(sum_out_sensors_hum_min), qos=0, retain=False) lacrosse_json = lacrosse_json + ", \"hum_min\":" + str(sum_out_sensors_hum_min) if sum_out_sensors_hum_max > -50: mqttc.publish(topic_prefix_outside_temphum + '/hum_max', str(sum_out_sensors_hum_max), qos=0, retain=False) lacrosse_json = lacrosse_json + ", \"hum_max\":" + str(sum_out_sensors_hum_max) if lacrosse_json is not None: if config['sensors'].getboolean('calculate_dewpoint', False): lacrosse_json = lacrosse_json + ", \"dewpoint\":" + str(out_dewpoint) if config['sensors'].getboolean('calculate_absolute_humidity', False): lacrosse_json = lacrosse_json + ", \"absoluteHumidity\":" + str(out_abshum) lacrosse_json = lacrosse_json + "}" mqttc.publish(topic_prefix_outside_temphum + "/json", lacrosse_json, qos=0, retain=False) # combined outside sensors to InfluxDB o_hasValidData = False if out_temp_publishvalue < 200 and out_hum_publishvalue < 200: o_hasValidData = True o_storeNow = False if (int(time.time()) - outSens_store_lastRun) > int(config['sensors'].get('store_interval_outside', 300)): if outSens_store_lastRun == 0: outSens_store_lastRun = int(time.time()) else: o_storeNow = True outSens_store_lastRun = int(time.time()) if o_storeNow and o_hasValidData: outSens_store_lastRun = int(time.time()) o_influxInstance = config['sensors'].get('influxdb_instance_combined_outside_sensors') o_influxSensorName = config['sensors'].get('influxdb_sensorname_combined_outside_sensors', None) if o_influxInstance in influxdb_yaml and o_influxSensorName is not None: influx_measurement = influxdb_yaml[o_influxInstance].get('measurement', None) t_utc = datetime.utcnow() t_str = t_utc.isoformat() + 'Z' if influx_measurement is not None: influx_fieldnames = influxdb_yaml[o_influxInstance].get('fieldnames', None) if influx_fieldnames == None: influx_fieldname_temperature = influx_default_fieldname_temperature influx_fieldname_humidity = influx_default_fieldname_humidity influx_fieldname_dewpoint = influx_default_fieldname_dewpoint influx_fieldname_abshum = influx_default_fieldname_abshum else: if influxdb_yaml[o_influxInstance]['fieldnames'].get('temperature', None) != None: influx_fieldname_temperature = influxdb_yaml[o_influxInstance]['fieldnames'].get('temperature') else: influx_fieldname_temperature = influx_default_fieldname_temperature if influxdb_yaml[o_influxInstance]['fieldnames'].get('humidity', None) != None: influx_fieldname_humidity = influxdb_yaml[o_influxInstance]['fieldnames'].get('humidity') else: influx_fieldname_humidity = influx_default_fieldname_humidity if influxdb_yaml[o_influxInstance]['fieldnames'].get('dewpoint', None) != None: influx_fieldname_dewpoint = influxdb_yaml[o_influxInstance]['fieldnames'].get('dewpoint') else: influx_fieldname_dewpoint = influx_default_fieldname_dewpoint if influxdb_yaml[o_influxInstance]['fieldnames'].get('abshum', None) != None: influx_fieldname_abshum = influxdb_yaml[o_influxInstance]['fieldnames'].get('abshum') else: influx_fieldname_abshum = influx_default_fieldname_abshum influx_datatypes = influxdb_yaml[o_influxInstance].get('datatypes', None) if influx_datatypes == None: influx_datatype_temperature = influx_default_datatype_temperature influx_datatype_humidity = influx_default_datatype_humidity else: if influxdb_yaml[o_influxInstance]['datatypes'].get('temperature', None) != None: tmpdt = influxdb_yaml[o_influxInstance]['datatypes'].get('temperature') if tmpdt == 'float' or tmpdt == 'int': influx_datatype_temperature = tmpdt else: influx_datatype_temperature = influx_default_datatype_temperature else: influx_datatype_temperature = influx_default_datatype_temperature if influxdb_yaml[o_influxInstance]['datatypes'].get('humidity', None) != None: tmpdt = influxdb_yaml[o_influxInstance]['datatypes'].get('humidity') if tmpdt == 'float' or tmpdt == 'int': influx_datatype_humidity = tmpdt else: influx_datatype_humidity = influx_default_datatype_humidity else: influx_datatype_humidity = influx_default_datatype_humidity if influxdb_yaml[o_influxInstance]['datatypes'].get('dewpoint', None) != None: tmpdt = influxdb_yaml[o_influxInstance]['datatypes'].get('dewpoint') if tmpdt == 'float' or tmpdt == 'int': influx_datatype_dewpoint = tmpdt else: influx_datatype_dewpoint = influx_default_datatype_dewpoint else: influx_datatype_dewpoint = influx_default_datatype_dewpoint if influxdb_yaml[o_influxInstance]['datatypes'].get('abshum', None) != None: tmpdt = influxdb_yaml[o_influxInstance]['datatypes'].get('abshum') if tmpdt == 'float' or tmpdt == 'int': influx_datatype_abshum = tmpdt else: influx_datatype_abshum = influx_default_datatype_abshum else: influx_datatype_abshum = influx_default_datatype_abshum if influx_datatype_temperature == 'int': influx_value_temp = int(out_temp_publishvalue) else: influx_value_temp = float(out_temp_publishvalue) if influx_datatype_humidity == 'int': influx_value_hum = int(out_hum_publishvalue) else: influx_value_hum = float(out_hum_publishvalue) if config['sensors'].getboolean('calculate_dewpoint', False) and config['sensors'].getboolean('write_dewpoint_to_influxdb', False): if influx_datatype_dewpoint == 'int': influx_value_dewpoint = int(out_dewpoint) else: influx_value_dewpoint = float(out_dewpoint) if config['sensors'].getboolean('calculate_absolute_humidity', False) and config['sensors'].getboolean('write_abshum_to_influxdb', False): if influx_datatype_abshum == 'int': influx_value_abshum = int(out_abshum) else: influx_value_abshum = float(out_abshum) influx_json = [ { 'measurement': influx_measurement, 'tags': { 'sensor': o_influxSensorName }, 'time': t_str, 'fields': { influx_fieldname_temperature: influx_value_temp, influx_fieldname_humidity: influx_value_hum } } ] if config['sensors'].getboolean('calculate_dewpoint', False) and config['sensors'].getboolean('write_dewpoint_to_influxdb', False) and out_dewpoint < 200: influx_json[0]["fields"][influx_fieldname_dewpoint] = influx_value_dewpoint if config['sensors'].getboolean('calculate_absolute_humidity', False) and config['sensors'].getboolean('write_abshum_to_influxdb', False) and out_abshum < 200: influx_json[0]["fields"][influx_fieldname_abshum] = influx_value_abshum try: if verbosemode: print("write to InfluxDB...") print(influx_json) influxclient[o_influxInstance].write_points(influx_json) if verbosemode: print("DONE!") except Exception as e: print("Error writing to InfluxDB") print(influx_json) print(e) # handle outdated sensor values once a minute if (int(time.time()) - checkLastUpdateInterval_lastRun) > checkLastUpdateInterval: # exit program if nothing has been received from JeeLink for 5 min - will be restarted by systemd now = int(time.time()) if (now - serialReceivedLastTime) > serialReceived_maxAge: print("Nothing received from JeeLink for " + str(now - serialReceivedLastTime) + "s - there is something wrong. Exiting program so that it will be restarted by systemd.") quit() checkLastUpdateInterval_lastRun = int(time.time()) #print("check lastUpdate") for key in sensors_yaml: #print(key, '->', sensors_yaml[key]) #print("Sensor name:", key) sensorId = sensors_yaml[key].get('LaCrosseID', None) if sensorId >= 0: lupd = sensors_lastUpdate.get(sensorId, None) tdiff = 0 if lupd is not None: tdiff = int(time.time()) - lupd if lupd is None or (tdiff > sensordata_maxage): mqttc.publish(mqtt_topic_prefix+"/"+ key +"/availability", "unavailable", qos=0, retain=False) notifystr = "received no data from sensor '" + key + "' with ID " + str(sensorId) + " for " + str(tdiff) + "s" mqttc.publish(mqtt_topic_prefix+"/" + mqtt_subtopic_notify, notifystr, qos=0, retain=False) except KeyboardInterrupt: print('\n') exit()