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 class my_ambient(base): KEY_TEMPERATURE = 'temperature' KEY_PRESSURE = 'pressure' KEY_HUMIDITY = 'humidity' # LAST_MSG_WARNING = 20 * 60 LAST_MSG_ERROR = 30 * 60 def __init__(self, mqtt_client: mqtt.mqtt_client, topic): super().__init__(mqtt_client, topic) self.__last_value_rx__ = { self.KEY_HUMIDITY: None, self.KEY_PRESSURE: None, self.KEY_TEMPERATURE: None } def __rx__(self, client, userdata, message): try: payload = json.loads(message.payload) except json.decoder.JSONDecodeError: pass else: key = message.topic.split('/')[-1] if key in self.__last_value_rx__: self.__last_value_rx__[key] = time.time() return super().__rx__(client, userdata, message) def status(self, key): if key == DID_HEARTBEAT: status = nagios.Nagios.OK msg = "" for key in self.__last_value_rx__: last_value_rx = self.__last_value_rx__[key] if len(msg) != 0: msg += "; " if last_value_rx is None: target_status = nagios.Nagios.UNKNOWN msg += "%s (never)" % key else: dt = time.time() - last_value_rx msg += "%s (%.1f min ago)" % (key, dt / 60) if dt > self.LAST_MSG_ERROR: target_status = nagios.Nagios.ERROR elif dt > self.LAST_MSG_WARNING: target_status = nagios.Nagios.WARNING else: target_status = nagios.Nagios.OK if target_status > status: status = target_status return self.__nagios_return__(DID_HEARTBEAT, status, msg) else: return super().status(key)