51 lines
2.3 KiB
Python
51 lines
2.3 KiB
Python
"""Independent AppDaemon worker monitoring the boiler climate heartbeat."""
|
|
import time
|
|
|
|
import appdaemon.plugins.hass.hassapi as hass
|
|
from logger_interface import LoggerInterface
|
|
from boilerregulator import finite
|
|
|
|
|
|
class BoilerWatchdog(hass.Hass, LoggerInterface):
|
|
def initialize(self):
|
|
self.initialize_logger_interface(self.get_ad_api(), ascii_encode=False)
|
|
self.base = self.args.get('mqtt_topic', 'appdaemon/radiator_heating').rstrip('/')
|
|
self.mqtt_namespace = self.args.get('mqtt_namespace', 'mqtt')
|
|
self.timeout = finite(self.args.get('heartbeat_timeout', 180))
|
|
if self.timeout < 60:
|
|
raise ValueError('heartbeat_timeout must be at least 60 seconds')
|
|
if self.AD.plugins.get_plugin_object(self.mqtt_namespace) is None:
|
|
raise RuntimeError('MQTT plugin missing; configure it and restart AppDaemon')
|
|
self.last_heartbeat = None
|
|
self.waiting_since = time.monotonic()
|
|
self.fault = False
|
|
self.warned = False
|
|
self.listen_event(self._mqtt_event, 'MQTT_MESSAGE', namespace=self.mqtt_namespace)
|
|
self.run_every(self._check, 'now', 10)
|
|
self._check()
|
|
|
|
def _mqtt_event(self, event, data, kwargs):
|
|
if data.get('topic') == self.base + '/heartbeat':
|
|
self.last_heartbeat = time.monotonic()
|
|
if self.warned:
|
|
self.log_info('Boiler climate heartbeat recovered')
|
|
self.fault = False
|
|
self.warned = False
|
|
elif data.get('topic') is None and data.get('state') == 'Connected':
|
|
self.last_heartbeat = None
|
|
self.waiting_since = time.monotonic()
|
|
self.fault = False
|
|
self.warned = False
|
|
self._check()
|
|
|
|
def _check(self, kwargs=None):
|
|
age = time.monotonic() - (self.waiting_since if self.last_heartbeat is None else self.last_heartbeat)
|
|
if self.last_heartbeat is None or age >= self.timeout:
|
|
if not self.fault:
|
|
self.call_service('mqtt/publish', namespace=self.mqtt_namespace,
|
|
topic=self.base + '/availability', payload='offline', qos=0, retain=True)
|
|
self.fault = True
|
|
if age >= self.timeout and not self.warned:
|
|
self.log_warning('Boiler climate unavailable: no heartbeat within the timeout')
|
|
self.warned = True
|