123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- 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)
|