250 lines
9.7 KiB
Python

import json
import logging
import mqtt
import nagios
import time
from z_protocol import DID_ACTOR, DID_BATTERY_LEVEL, DID_FOLLOWS_SETPOINT, DID_HEARTBEAT, DID_LINKQUALITY
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):
KEY_BATTERY = 'battery'
KEY_CURRENT_VALUE = None
KEY_LINKQUALITY = 'linkquality'
KEY_SETPOINT = None
#
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
#
BATTERY_LVL_WARNING = 15
BATTERY_LVL_ERROR = 5
#
LINKQUALITY_WARNING = 50
LINKQUALITY_ERROR = 25
#
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__ = {}
def __rx__(self, client, userdata, message):
try:
payload = json.loads(message.payload)
except json.decoder.JSONDecodeError:
pass
else:
if type(payload) is dict:
#
# Device values
#
if message.topic == self.topic:
for key in [self.KEY_BATTERY, self.KEY_CURRENT_VALUE, self.KEY_LINKQUALITY, self.KEY_SETPOINT]:
if key in payload:
self.state(key, payload[key])
#
# Device setpoint
#
if message.topic == self.topic + '/set' and self.KEY_SETPOINT in payload:
self.target(self.KEY_SETPOINT, payload[self.KEY_SETPOINT])
#
# heartbeat
#
if message.topic == self.topic:
self.last_device_msg = time.time()
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):
#
# ACTOR
#
if key == DID_ACTOR:
return self.__nagios_return__(DID_ACTOR, nagios.Nagios.WARNING, "Not available for this device")
#
# HEARTBEAT
#
elif key == DID_HEARTBEAT:
if self.last_device_msg is None:
return self.__nagios_return__(DID_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__(DID_HEARTBEAT, nagios.Nagios.ERROR, "Last message %.1fh ago" % dt_disp)
elif dt > self.LAST_MSG_WARNING:
return self.__nagios_return__(DID_HEARTBEAT, nagios.Nagios.WARNING, "Last message %.1fh ago" % dt_disp)
else:
return self.__nagios_return__(DID_HEARTBEAT, nagios.Nagios.OK, "Last message %.1fh ago" % dt_disp)
#
# FOLLOW SETPOINT
#
elif key == DID_FOLLOWS_SETPOINT:
if self.KEY_SETPOINT is None:
return self.__nagios_return__(DID_FOLLOWS_SETPOINT, nagios.Nagios.UNKNOWN, "Device exist, but does not follow any setpoint.", force=True)
tm_s, value_s = self.__state_storage__.get(self.KEY_SETPOINT, (0, None))
try:
tm_t, value_t = self.__target_storage__[self.KEY_SETPOINT]
except KeyError:
if value_s is not None:
return self.__nagios_return__(DID_FOLLOWS_SETPOINT, nagios.Nagios.OK, "Current temperature setpoint %.1f C, but never received a setpoint. That might be okay." % value_s)
return self.__nagios_return__(DID_FOLLOWS_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__(DID_FOLLOWS_SETPOINT, nagios.Nagios.ERROR, "Requested setpoint %.1f C unequal valve setpoint %.1f C since %.1fmin" % (value_t, value_s, (time.time()-tm_s)/60))
elif value_t != value_s and dt > self.FOLLOW_REQUEST_WARNING:
return self.__nagios_return__(DID_FOLLOWS_SETPOINT, nagios.Nagios.WARNING, "Requested setpoint %.1f C unequal valve setpoint %.1f C since %.1fmin" % (value_t, value_s, (time.time()-tm_s)))
return self.__nagios_return__(DID_FOLLOWS_SETPOINT, nagios.Nagios.OK, "Requested setpoint equal valve setpoint %.1f C" % value_s)
#
# BATTERY
#
elif key == DID_BATTERY_LEVEL:
battery_lvl = self.__state_storage__.get(self.KEY_BATTERY, (0, None))[1]
if battery_lvl is None:
return self.__nagios_return__(DID_BATTERY_LEVEL, nagios.Nagios.UNKNOWN, "Device exists, but no data received or unknown monitoring")
elif battery_lvl <= self.BATTERY_LVL_ERROR:
return self.__nagios_return__(DID_BATTERY_LEVEL, nagios.Nagios.ERROR, "Battery level critical low (%.1f%%)" % battery_lvl)
elif battery_lvl <= self.BATTERY_LVL_WARNING:
return self.__nagios_return__(DID_BATTERY_LEVEL, nagios.Nagios.WARNING, "Battery level low (%.1f%%)" % battery_lvl)
else:
return self.__nagios_return__(DID_BATTERY_LEVEL, nagios.Nagios.OK, "Battery okay (%.1f%%)" % battery_lvl)
#
# LINKQUALITY
#
elif key == DID_LINKQUALITY:
linkquality = self.__state_storage__.get(self.KEY_LINKQUALITY, (0, None))[1]
if linkquality is None:
return self.__nagios_return__(DID_LINKQUALITY, nagios.Nagios.UNKNOWN, "Device exists, but no data received or unknown monitoring")
elif linkquality <= self.LINKQUALITY_ERROR:
return self.__nagios_return__(DID_LINKQUALITY, nagios.Nagios.ERROR, "Linkquality critical low (%d)" % linkquality)
elif linkquality <= self.LINKQUALITY_WARNING:
return self.__nagios_return__(DID_LINKQUALITY, nagios.Nagios.WARNING, "Linkquality level low (%d)" % linkquality)
else:
return self.__nagios_return__(DID_LINKQUALITY, nagios.Nagios.OK, "Linkquality okay (%d)" % linkquality)
def __nagios_return__(self, did, status, msg, force=False):
tm = time.time()
if did not in self.__unknown_tm__:
self.__unknown_tm__[did] = None
if status == nagios.Nagios.UNKNOWN and not force:
if self.__unknown_tm__[did] is None:
self.__unknown_tm__[did] = tm
dt = tm - self.__unknown_tm__[did]
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__[did] = 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 = 24 * 60 * 60
LAST_MSG_ERROR = 48 * 60 * 60
class livarno_sw_br_ct(base):
pass
class brennenstuhl_heatingvalve(base):
BATTERY_LVL_WARNING = 4
BATTERY_LVL_ERROR = 3
#
ACTOR_WARN_OFFSET = 1.5
ACTOR_ERR_OFFSET = 2.5
#
KEY_SETPOINT = "current_heating_setpoint"
KEY_CURRENT_VALUE = "local_temperature"
def status(self, key):
#
# ACTOR
#
if key == DID_ACTOR:
tm_s, value_s = self.__state_storage__.get(self.KEY_SETPOINT, (0, None))
tm_c, value_c = self.__state_storage__.get(self.KEY_CURRENT_VALUE, (0, None))
#
if value_s is None or value_c is None:
return self.__nagios_return__(DID_ACTOR, nagios.Nagios.UNKNOWN, "Device exists, but no data received")
elif value_s <= 5:
return self.__nagios_return__(DID_ACTOR, nagios.Nagios.OK, "No monitoring in Summer Mode")
elif value_c > value_s + self.ACTOR_ERR_OFFSET:
return self.__nagios_return__(DID_ACTOR, nagios.Nagios.ERROR, "Current Temperature much to high %.1f C > %.1f C" % (value_c, value_s))
elif value_c > value_s + self.ACTOR_WARN_OFFSET:
return self.__nagios_return__(DID_ACTOR, nagios.Nagios.WARNING, "Current Temperature to high %.1f C > %.1f C" % (value_c, value_s))
else:
return self.__nagios_return__(DID_ACTOR, nagios.Nagios.OK, "Current Temperature okay %.1f C > %.1f C" % (value_c, value_s))
else:
return super().status(key)
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