2023-12-12 12:44:34 +01:00
import json
import logging
import mqtt
import nagios
import time
2024-07-14 18:17:50 +02:00
from z_protocol import DID_ACTOR , DID_BATTERY_LEVEL , DID_FOLLOWS_SETPOINT , DID_HEARTBEAT , DID_LINKQUALITY
2023-12-12 12:44:34 +01:00
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 ) :
2024-07-14 18:17:50 +02:00
KEY_BATTERY = ' battery '
KEY_CURRENT_VALUE = None
KEY_LINKQUALITY = ' linkquality '
KEY_SETPOINT = None
2023-12-20 09:36:42 +01:00
#
2023-12-12 12:44:34 +01:00
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
2023-12-16 07:12:29 +01:00
#
2023-12-19 07:12:56 +01:00
BATTERY_LVL_WARNING = 15
BATTERY_LVL_ERROR = 5
2023-12-16 08:16:45 +01:00
#
2023-12-31 17:57:24 +01:00
LINKQUALITY_WARNING = 50
LINKQUALITY_ERROR = 25
#
2023-12-20 09:36:42 +01:00
LAST_MSG_WARNING = 6 * 60 * 60
LAST_MSG_ERROR = 24 * 60 * 60
2023-12-12 12:44:34 +01:00
def __init__ ( self , mqtt_client : mqtt . mqtt_client , topic ) :
self . topic = topic
#
2023-12-20 09:36:42 +01:00
self . __unknown_tm__ = { }
#
2023-12-12 12:44:34 +01:00
mqtt_client . add_callback ( topic , self . __rx__ )
mqtt_client . add_callback ( topic + ' /# ' , self . __rx__ )
#
2023-12-16 08:16:45 +01:00
self . last_device_msg = None
#
2023-12-12 12:44:34 +01:00
self . __target_storage__ = { }
self . __state_storage__ = { }
def __rx__ ( self , client , userdata , message ) :
2023-12-20 07:26:39 +01:00
try :
payload = json . loads ( message . payload )
except json . decoder . JSONDecodeError :
pass
else :
if type ( payload ) is dict :
#
2024-07-14 18:17:50 +02:00
# Device values
2023-12-20 07:26:39 +01:00
#
if message . topic == self . topic :
2024-07-14 18:17:50 +02:00
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 ] )
2023-12-20 07:26:39 +01:00
#
2024-07-14 18:17:50 +02:00
# Device setpoint
2023-12-20 07:26:39 +01:00
#
2024-07-14 18:17:50 +02:00
if message . topic == self . topic + ' /set ' and self . KEY_SETPOINT in payload :
self . target ( self . KEY_SETPOINT , payload [ self . KEY_SETPOINT ] )
2023-12-31 17:57:24 +01:00
#
2024-07-14 18:17:50 +02:00
# heartbeat
2023-12-31 17:57:24 +01:00
#
2024-07-14 18:17:50 +02:00
if message . topic == self . topic :
self . last_device_msg = time . time ( )
2023-12-12 12:44:34 +01:00
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 ) :
2023-12-14 18:16:15 +01:00
self . __state_storage__ [ key ] = time . time ( ) , value
2023-12-12 12:44:34 +01:00
logger . debug ( " Device state identified: %s : %s " , key , repr ( value ) )
def status ( self , key ) :
2023-12-16 07:12:29 +01:00
#
2024-07-14 18:17:50 +02:00
# ACTOR
#
if key == DID_ACTOR :
return self . __nagios_return__ ( DID_ACTOR , nagios . Nagios . WARNING , " Not available for this device " )
#
2023-12-16 08:16:45 +01:00
# HEARTBEAT
#
2024-07-14 18:17:50 +02:00
elif key == DID_HEARTBEAT :
2023-12-16 08:16:45 +01:00
if self . last_device_msg is None :
2024-07-14 18:17:50 +02:00
return self . __nagios_return__ ( DID_HEARTBEAT , nagios . Nagios . UNKNOWN , " Device exists, but no data received " )
2023-12-16 08:16:45 +01:00
else :
dt = time . time ( ) - self . last_device_msg
dt_disp = dt / 60 / 60
if dt > self . LAST_MSG_ERROR :
2024-07-14 18:17:50 +02:00
return self . __nagios_return__ ( DID_HEARTBEAT , nagios . Nagios . ERROR , " Last message %.1f h ago " % dt_disp )
2023-12-16 08:16:45 +01:00
elif dt > self . LAST_MSG_WARNING :
2024-07-14 18:17:50 +02:00
return self . __nagios_return__ ( DID_HEARTBEAT , nagios . Nagios . WARNING , " Last message %.1f h ago " % dt_disp )
2023-12-16 08:16:45 +01:00
else :
2024-07-14 18:17:50 +02:00
return self . __nagios_return__ ( DID_HEARTBEAT , nagios . Nagios . OK , " Last message %.1f h ago " % dt_disp )
2023-12-16 08:16:45 +01:00
#
2023-12-16 07:12:29 +01:00
# FOLLOW SETPOINT
#
2024-07-14 18:17:50 +02:00
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 ) )
2023-12-16 07:12:29 +01:00
try :
2024-07-14 18:17:50 +02:00
tm_t , value_t = self . __target_storage__ [ self . KEY_SETPOINT ]
2023-12-16 07:12:29 +01:00
except KeyError :
if value_s is not None :
2024-07-14 18:17:50 +02:00
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 " )
2023-12-16 07:12:29 +01:00
else :
tm = time . time ( )
dt = tm - tm_t
if value_t != value_s and dt > self . FOLLOW_REQUEST_ERROR :
2024-07-14 18:17:50 +02:00
return self . __nagios_return__ ( DID_FOLLOWS_SETPOINT , nagios . Nagios . ERROR , " Requested setpoint %.1f °C unequal valve setpoint %.1f °C since %.1f min " % ( value_t , value_s , ( time . time ( ) - tm_s ) / 60 ) )
2023-12-16 07:12:29 +01:00
elif value_t != value_s and dt > self . FOLLOW_REQUEST_WARNING :
2024-07-14 18:17:50 +02:00
return self . __nagios_return__ ( DID_FOLLOWS_SETPOINT , nagios . Nagios . WARNING , " Requested setpoint %.1f °C unequal valve setpoint %.1f °C since %.1f min " % ( 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 )
2023-12-16 07:12:29 +01:00
#
# BATTERY
#
2024-07-14 18:17:50 +02:00
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 )
2023-12-16 07:12:29 +01:00
else :
2024-07-14 18:17:50 +02:00
return self . __nagios_return__ ( DID_BATTERY_LEVEL , nagios . Nagios . OK , " Battery okay ( %.1f %% ) " % battery_lvl )
2023-12-31 17:57:24 +01:00
#
# LINKQUALITY
#
2024-07-14 18:17:50 +02:00
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 )
2023-12-31 17:57:24 +01:00
else :
2024-07-14 18:17:50 +02:00
return self . __nagios_return__ ( DID_LINKQUALITY , nagios . Nagios . OK , " Linkquality okay ( %d ) " % linkquality )
2023-12-20 09:36:42 +01:00
2024-07-14 18:17:50 +02:00
def __nagios_return__ ( self , did , status , msg , force = False ) :
2023-12-20 09:36:42 +01:00
tm = time . time ( )
2024-07-14 18:17:50 +02:00
if did not in self . __unknown_tm__ :
self . __unknown_tm__ [ did ] = None
2023-12-20 09:36:42 +01:00
if status == nagios . Nagios . UNKNOWN and not force :
2024-07-14 18:17:50 +02:00
if self . __unknown_tm__ [ did ] is None :
self . __unknown_tm__ [ did ] = tm
dt = tm - self . __unknown_tm__ [ did ]
2023-12-20 09:36:42 +01:00
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 %.1f h " % ( dt / 3600 )
else :
2024-07-14 18:17:50 +02:00
self . __unknown_tm__ [ did ] = None
2023-12-20 09:36:42 +01:00
return { " status " : status , " msg " : msg }
2023-12-12 12:44:34 +01:00
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 ) :
2023-12-23 18:22:37 +01:00
LAST_MSG_WARNING = 24 * 60 * 60
LAST_MSG_ERROR = 48 * 60 * 60
2023-12-12 12:44:34 +01:00
class livarno_sw_br_ct ( base ) :
pass
class brennenstuhl_heatingvalve ( base ) :
2023-12-19 07:12:56 +01:00
BATTERY_LVL_WARNING = 4
BATTERY_LVL_ERROR = 3
2024-07-14 18:17:50 +02:00
#
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 )
2023-12-12 12:44:34 +01:00
class silvercrest_powerplug ( base ) :
pass
class silvercrest_motion_sensor ( base ) :
2023-12-20 07:26:39 +01:00
pass
2023-12-12 12:44:34 +01:00
class my_powerplug ( base ) :
pass
class audio_status ( base ) :
pass
class remote ( base ) :
pass