|
@@ -0,0 +1,776 @@
|
|
|
+#!/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 math
|
|
|
+#import numpy as np
|
|
|
+#import httplib
|
|
|
+
|
|
|
+# Change working dir to the same dir as this script
|
|
|
+os.chdir(sys.path[0])
|
|
|
+
|
|
|
+config = configparser.ConfigParser()
|
|
|
+config.read('jeelinklog.ini')
|
|
|
+
|
|
|
+serialport = config['jeelink'].get('serialport')
|
|
|
+serialbaud = int(config['jeelink'].get('baudrate'))
|
|
|
+
|
|
|
+mqtt_server = config['mqtt'].get('server')
|
|
|
+mqtt_port = int(config['mqtt'].get('port'))
|
|
|
+mqtt_user = config['mqtt'].get('user')
|
|
|
+mqtt_password = config['mqtt'].get('password')
|
|
|
+
|
|
|
+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_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_interval = 120
|
|
|
+
|
|
|
+influx_default_fieldname_temperature = 'Temperature'
|
|
|
+influx_default_fieldname_humidity = 'Humidity'
|
|
|
+influx_default_datatype_temperature = 'float'
|
|
|
+influx_default_datatype_temperature = 'int'
|
|
|
+
|
|
|
+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 )
|
|
|
+
|
|
|
+if verbosemode:
|
|
|
+ print("JeeLink2MQTT by Flo Kra")
|
|
|
+ print("=======================================================================")
|
|
|
+ print("loading InfluxDB configuration...")
|
|
|
+
|
|
|
+influxdb_yaml = yaml.load(open(config['main'].get('influx_config_yml')), Loader=yaml.SafeLoader)
|
|
|
+
|
|
|
+if verbosemode:
|
|
|
+ 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("loading sensors configuration: ")
|
|
|
+ print(json.dumps(sensors_yaml, indent=3))
|
|
|
+
|
|
|
+for key in sensors_yaml:
|
|
|
+ #print(key, '->', sensors_yaml[key])
|
|
|
+ if verbosemode: print("Sensor name:", key)
|
|
|
+ sensorName = key
|
|
|
+ if sensors_yaml[key].get('LaCrosseID'):
|
|
|
+ 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()
|
|
|
+
|
|
|
+#print(json.dumps(sensor))
|
|
|
+#print("sensors_id_to_name =",sensors_id_to_name)
|
|
|
+#print("outside sensors: ", sensors_outside_sensors)
|
|
|
+
|
|
|
+if verbosemode:
|
|
|
+ print("\n")
|
|
|
+
|
|
|
+
|
|
|
+mqttc = mqtt.Client()
|
|
|
+mqttc.on_connect = on_connect
|
|
|
+mqttc.on_disconnect = on_disconnect
|
|
|
+##mqttc.on_message = on_message
|
|
|
+
|
|
|
+if mqtt_user != "" and mqtt_password != "":
|
|
|
+ mqttc.username_pw_set(mqtt_user, mqtt_password)
|
|
|
+mqttc.connect(mqtt_server, mqtt_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
|
|
|
+
|
|
|
+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 = ""
|
|
|
+
|
|
|
+ if(serLine):
|
|
|
+ if serLine.find('OK 9') != -1:
|
|
|
+ if verbosemode:
|
|
|
+ print(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(' ')
|
|
|
+
|
|
|
+ #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("addr: " + str(addr) + " = 0x" + str(addrhex) + " batt_new: " + str(batt_new) + " type: " + str(type) + " batt_low: " + str(batt_low) + " temp: " + str(temp) + " hum: " + str(hum) + " Name: " + str(currentsensor_name))
|
|
|
+ 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")
|
|
|
+
|
|
|
+
|
|
|
+ # 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]))
|
|
|
+ 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:
|
|
|
+
|
|
|
+ if sensors_batteryState.get(id,None) == 2:
|
|
|
+ s_battNew = True
|
|
|
+ s_battState = "NEW"
|
|
|
+ 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_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_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_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)
|
|
|
+ mqttc.publish(mqtt_topic_prefix+"/"+ s_name +"/battery", s_battState, 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)
|
|
|
+
|
|
|
+ lacrosse_json = "{\"temperature\":" + str(s_currAvgTemp) + ", \"humidity\":" + str(s_currAvgHum) + ", \"battery\":\"" + str(s_battState) + "\"}"
|
|
|
+ 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
|
|
|
+ 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
|
|
|
+
|
|
|
+ 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 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)
|
|
|
+
|
|
|
+ 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
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+
|
|
|
+ try:
|
|
|
+ if verbosemode: print("write to InfluxDB...")
|
|
|
+ influxclient[s_influxInstance].write_points(influx_json)
|
|
|
+ if verbosemode: print("DONE!")
|
|
|
+ except Exception as e:
|
|
|
+ print("Error writing to InfluxDB")
|
|
|
+ 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:
|
|
|
+ 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)
|
|
|
+ else:
|
|
|
+ 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) > outSens_interval:
|
|
|
+ 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
|
|
|
+
|
|
|
+ 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
|
|
|
+
|
|
|
+ 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))
|
|
|
+
|
|
|
+ mqttc.publish(mqtt_topic_atemp, str(out_temp_avg), qos=0, retain=True)
|
|
|
+ mqttc.publish(mqtt_topic_ahum, str(out_hum_avg), qos=0, retain=True)
|
|
|
+
|
|
|
+ mqttc.publish(topic_prefix_outside_temphum + '/temperature', str(out_temp_avg), qos=0, retain=True)
|
|
|
+ mqttc.publish(topic_prefix_outside_temphum + '/humidity', str(out_hum_avg), 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_avg) + "° " + str(out_hum_avg) + "%"
|
|
|
+ mqttc.publish(topic_prefix_outside_temphum + "/TempHumText", tmptext, qos=0, retain=False)
|
|
|
+
|
|
|
+ lacrosse_json = "{\"temperature\":" + str(out_temp_avg) + ", \"humidity\":" + str(out_hum_avg) + "\", \"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 = "{\"temperature\":" + str(out_temp_avg) + ", \"humidity\":" + str(out_hum_avg) + ", \"usedSensors\":" + str(count_used_out_sensors)
|
|
|
+
|
|
|
+ if sum_out_sensors_temp_min < 100:
|
|
|
+ mqttc.publish(topic_prefix_outside_temphum + '/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 + '/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 + '/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 + '/max', str(sum_out_sensors_hum_max), qos=0, retain=False)
|
|
|
+ lacrosse_json = lacrosse_json + ", \"hum_max\":" + str(sum_out_sensors_hum_max)
|
|
|
+
|
|
|
+ lacrosse_json = lacrosse_json + "}"
|
|
|
+
|
|
|
+ if lacrosse_json is not None:
|
|
|
+ mqttc.publish(topic_prefix_outside_temphum + "/json", lacrosse_json, qos=0, retain=False)
|
|
|
+
|
|
|
+
|
|
|
+ # handle outdated sensor values once a minute
|
|
|
+ if (int(time.time()) - checkLastUpdateInterval_lastRun) > checkLastUpdateInterval:
|
|
|
+ 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 is not None:
|
|
|
+ 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()
|