204 lines
7.5 KiB
Python
204 lines
7.5 KiB
Python
import json
|
|
import logging
|
|
import mqtt
|
|
import nagios
|
|
import time
|
|
|
|
try:
|
|
from config import APP_NAME as ROOT_LOGGER_NAME
|
|
except ImportError:
|
|
ROOT_LOGGER_NAME = 'root'
|
|
logger = logging.getLogger(ROOT_LOGGER_NAME).getChild(__name__)
|
|
|
|
|
|
class base(object):
|
|
MONITORING_HEARTBEAT = "heartbeat"
|
|
MONITORING_BATTERY = "battery"
|
|
MONITORING_FOLLOW_SETPOINT = "follow_setpoint"
|
|
#
|
|
FOLLOW_REQUEST_WARNING = 5 # Seconds, till warning comes up, if device does not follow the command
|
|
FOLLOW_REQUEST_ERROR = 60 # Seconds, till error comes up, if device does not follow the command
|
|
FOLLOW_KEY = None
|
|
#
|
|
BATTERY_LVL_WARNING = 15
|
|
BATTERY_LVL_ERROR = 5
|
|
#
|
|
LAST_MSG_WARNING = 6 * 60 * 60
|
|
LAST_MSG_ERROR = 24 * 60 * 60
|
|
|
|
def __init__(self, mqtt_client: mqtt.mqtt_client, topic):
|
|
self.topic = topic
|
|
#
|
|
self.__unknown_tm__ = {}
|
|
#
|
|
mqtt_client.add_callback(topic, self.__rx__)
|
|
mqtt_client.add_callback(topic + '/#', self.__rx__)
|
|
#
|
|
self.last_device_msg = None
|
|
#
|
|
self.__target_storage__ = {}
|
|
self.__state_storage__ = {}
|
|
#
|
|
self.battery = None
|
|
|
|
def __rx__(self, client, userdata, message):
|
|
try:
|
|
payload = json.loads(message.payload)
|
|
except json.decoder.JSONDecodeError:
|
|
pass
|
|
else:
|
|
if type(payload) is dict:
|
|
#
|
|
# heartbeat
|
|
#
|
|
if message.topic == self.topic:
|
|
self.last_device_msg = time.time()
|
|
#
|
|
# follow setpoint
|
|
#
|
|
if self.FOLLOW_KEY is not None and self.FOLLOW_KEY in payload:
|
|
if message.topic == self.topic + '/set':
|
|
self.target(self.FOLLOW_KEY, payload[self.FOLLOW_KEY])
|
|
if message.topic == self.topic:
|
|
self.state(self.FOLLOW_KEY, payload[self.FOLLOW_KEY])
|
|
#
|
|
# battery level
|
|
#
|
|
if self.MONITORING_BATTERY in payload and message.topic == self.topic:
|
|
self.battery = payload[self.MONITORING_BATTERY]
|
|
|
|
def target(self, key, value):
|
|
tm_t, value_t = self.__target_storage__.get(key, (0, None))
|
|
if value != value_t:
|
|
self.__target_storage__[key] = time.time(), value
|
|
logger.debug("Target value for device identified: %s: %s", key, repr(value))
|
|
|
|
def state(self, key, value):
|
|
self.__state_storage__[key] = time.time(), value
|
|
logger.debug("Device state identified: %s: %s", key, repr(value))
|
|
|
|
def status(self, key):
|
|
#
|
|
# HEARTBEAT
|
|
#
|
|
if key == self.MONITORING_HEARTBEAT:
|
|
if self.last_device_msg is None:
|
|
return self.__nagios_return__(self.MONITORING_HEARTBEAT, nagios.Nagios.UNKNOWN, "Device exists, but no data received")
|
|
else:
|
|
dt = time.time() - self.last_device_msg
|
|
dt_disp = dt / 60 / 60
|
|
if dt > self.LAST_MSG_ERROR:
|
|
return self.__nagios_return__(self.MONITORING_HEARTBEAT, nagios.Nagios.ERROR, "Last message %.1fh ago" % dt_disp)
|
|
elif dt > self.LAST_MSG_WARNING:
|
|
return self.__nagios_return__(self.MONITORING_HEARTBEAT, nagios.Nagios.WARNING, "Last message %.1fh ago" % dt_disp)
|
|
else:
|
|
return self.__nagios_return__(self.MONITORING_HEARTBEAT, nagios.Nagios.OK, "Last message %.1fh ago" % dt_disp)
|
|
#
|
|
# FOLLOW SETPOINT
|
|
#
|
|
elif key == self.MONITORING_FOLLOW_SETPOINT:
|
|
if self.FOLLOW_KEY is None:
|
|
return self.__nagios_return__(self.MONITORING_FOLLOW_SETPOINT, nagios.Nagios.UNKNOWN, "Device exist, but does not follow any setpoint.", force=True)
|
|
tm_s, value_s = self.__state_storage__.get(self.FOLLOW_KEY, (0, None))
|
|
try:
|
|
tm_t, value_t = self.__target_storage__[self.FOLLOW_KEY]
|
|
except KeyError:
|
|
if value_s is not None:
|
|
return self.__nagios_return__(self.MONITORING_FOLLOW_SETPOINT, nagios.Nagios.WARNING, "Current temperature setpoint %.1f°C (age=%.1fmin), but never received a setpoint" % (value_s, (time.time()-tm_s)/60))
|
|
return self.__nagios_return__(self.MONITORING_FOLLOW_SETPOINT, nagios.Nagios.UNKNOWN, "Device exists, but no data received")
|
|
else:
|
|
tm = time.time()
|
|
dt = tm - tm_t
|
|
if value_t != value_s and dt > self.FOLLOW_REQUEST_ERROR:
|
|
return self.__nagios_return__(self.MONITORING_FOLLOW_SETPOINT, nagios.Nagios.ERROR, "Requested setpoint unequal valve setpoint %.1f°C since %.1fmin" % (value_s, (time.time()-tm_s)/60))
|
|
elif value_t != value_s and dt > self.FOLLOW_REQUEST_WARNING:
|
|
return self.__nagios_return__(self.MONITORING_FOLLOW_SETPOINT, nagios.Nagios.WARNING, "Requested setpoint unequal valve setpoint %.1f°C since %.1fmin" % (value_s, (time.time()-tm_s)))
|
|
return self.__nagios_return__(self.MONITORING_FOLLOW_SETPOINT, nagios.Nagios.OK, "Requested setpoint equal valve setpoint %.1f°C" % value_s)
|
|
#
|
|
# BATTERY
|
|
#
|
|
elif key == self.MONITORING_BATTERY:
|
|
if self.battery is None:
|
|
return self.__nagios_return__(self.MONITORING_BATTERY, nagios.Nagios.UNKNOWN, "Device exists, but no data received or unknown monitoring")
|
|
elif self.battery <= self.BATTERY_LVL_ERROR:
|
|
return self.__nagios_return__(self.MONITORING_BATTERY, nagios.Nagios.ERROR, "Battery level critical low (%.1f%%)" % self.battery)
|
|
elif self.battery <= self.BATTERY_LVL_WARNING:
|
|
return self.__nagios_return__(self.MONITORING_BATTERY, nagios.Nagios.WARNING, "Battery level low (%.1f%%)" % self.battery)
|
|
else:
|
|
return self.__nagios_return__(self.MONITORING_BATTERY, nagios.Nagios.OK, "Battery okay (%.1f%%)" % self.battery)
|
|
|
|
def __nagios_return__(self, monitoring_name, status, msg, force=False):
|
|
tm = time.time()
|
|
if monitoring_name not in self.__unknown_tm__:
|
|
self.__unknown_tm__[monitoring_name] = None
|
|
if status == nagios.Nagios.UNKNOWN and not force:
|
|
if self.__unknown_tm__[monitoring_name] is None:
|
|
self.__unknown_tm__[monitoring_name] = tm
|
|
dt = tm - self.__unknown_tm__[monitoring_name]
|
|
if dt >= self.LAST_MSG_ERROR:
|
|
status = nagios.Nagios.UNKNOWN
|
|
elif dt >= self.LAST_MSG_WARNING:
|
|
status = nagios.Nagios.WARNING
|
|
else:
|
|
status = nagios.Nagios.OK
|
|
msg += " - since %.1fh" % (dt / 3600)
|
|
else:
|
|
self.__unknown_tm__[monitoring_name] = None
|
|
return {"status": status, "msg": msg}
|
|
|
|
|
|
class group(object):
|
|
def __init__(self, *args, **kwargs):
|
|
pass
|
|
|
|
|
|
class shelly_sw1(base):
|
|
pass
|
|
|
|
|
|
class tradfri_sw(base):
|
|
pass
|
|
|
|
|
|
class tradfri_sw_br(base):
|
|
pass
|
|
|
|
|
|
class tradfri_sw_br_ct(base):
|
|
pass
|
|
|
|
|
|
class tradfri_button(base):
|
|
LAST_MSG_WARNING = 15 * 60 * 60
|
|
LAST_MSG_ERROR = 24 * 60 * 60
|
|
|
|
|
|
class livarno_sw_br_ct(base):
|
|
pass
|
|
|
|
|
|
class brennenstuhl_heatingvalve(base):
|
|
BATTERY_LVL_WARNING = 4
|
|
BATTERY_LVL_ERROR = 3
|
|
FOLLOW_KEY = "current_heating_setpoint"
|
|
|
|
|
|
class silvercrest_powerplug(base):
|
|
pass
|
|
|
|
|
|
class silvercrest_motion_sensor(base):
|
|
pass
|
|
|
|
|
|
class my_powerplug(base):
|
|
pass
|
|
|
|
|
|
class audio_status(base):
|
|
pass
|
|
|
|
|
|
class remote(base):
|
|
pass
|