Add AC input watchdog that notifies when commanded ON but grid power is not flowing
This commit is contained in:
1 parent
ed49dbf429
commit
f44d74e93b
1 file changed
+97
@@ -264,6 +264,8 @@ class Engine:
|
|||||||
self.last_seen_at = None
|
self.last_seen_at = None
|
||||||
self.stale_notified = False
|
self.stale_notified = False
|
||||||
self.telemetry_writes = 0
|
self.telemetry_writes = 0
|
||||||
|
self.ac_watchdog_since = None
|
||||||
|
self.ac_watchdog_tripped = False
|
||||||
|
|
||||||
def evaluate(self, variables, now):
|
def evaluate(self, variables, now):
|
||||||
for state in self.states:
|
for state in self.states:
|
||||||
@@ -474,6 +476,9 @@ class Engine:
|
|||||||
floor = self.profile.battery_floor
|
floor = self.profile.battery_floor
|
||||||
if floor is not None and floor.enabled:
|
if floor is not None and floor.enabled:
|
||||||
names.add(floor.field)
|
names.add(floor.field)
|
||||||
|
watchdog = self.profile.ac_input_watchdog
|
||||||
|
if watchdog is not None and watchdog.enabled:
|
||||||
|
names.add(watchdog.field)
|
||||||
return names
|
return names
|
||||||
|
|
||||||
def summarize(self, variables, now):
|
def summarize(self, variables, now):
|
||||||
@@ -674,6 +679,90 @@ class Engine:
|
|||||||
force=True,
|
force=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def check_ac_watchdog(self, variables, now):
|
||||||
|
watchdog = self.profile.ac_input_watchdog
|
||||||
|
if watchdog is None or not watchdog.enabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.last_commanded:
|
||||||
|
if self.ac_watchdog_tripped:
|
||||||
|
self.ac_watchdog_tripped = False
|
||||||
|
self.report("AC input watchdog cleared, target is OFF", force=True)
|
||||||
|
await self.notify_ac_watchdog(variables, recovered=True)
|
||||||
|
self.ac_watchdog_since = None
|
||||||
|
return
|
||||||
|
|
||||||
|
value = variables.get(watchdog.field)
|
||||||
|
if not isinstance(value, (int, float)) or isinstance(value, bool):
|
||||||
|
return
|
||||||
|
|
||||||
|
if value >= watchdog.min_watts:
|
||||||
|
if self.ac_watchdog_tripped:
|
||||||
|
self.ac_watchdog_tripped = False
|
||||||
|
self.report(
|
||||||
|
f"AC input watchdog cleared, {watchdog.field}={value:g}W",
|
||||||
|
force=True,
|
||||||
|
)
|
||||||
|
await self.notify_ac_watchdog(variables, recovered=True)
|
||||||
|
self.ac_watchdog_since = None
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.ac_watchdog_since is None:
|
||||||
|
self.ac_watchdog_since = now
|
||||||
|
|
||||||
|
held = now - self.ac_watchdog_since
|
||||||
|
if held < (watchdog.dwell or 0):
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.ac_watchdog_tripped:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.ac_watchdog_tripped = True
|
||||||
|
self.report(
|
||||||
|
f"AC INPUT WATCHDOG: {watchdog.field}={value:g}W has stayed under "
|
||||||
|
f"{watchdog.min_watts:g}W while commanded ON for "
|
||||||
|
f"{format_duration(held)}",
|
||||||
|
force=True,
|
||||||
|
)
|
||||||
|
await self.notify_ac_watchdog(variables, duration_seconds=held)
|
||||||
|
|
||||||
|
async def notify_ac_watchdog(self, variables, duration_seconds=None, recovered=False):
|
||||||
|
watchdog = self.profile.ac_input_watchdog
|
||||||
|
if watchdog is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
if recovered and not watchdog.notify_recovered:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.notifier.available():
|
||||||
|
if not recovered:
|
||||||
|
self.report(
|
||||||
|
"AC input watchdog tripped but no notification channel is "
|
||||||
|
"enabled",
|
||||||
|
force=True,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
settings = self.profile.notifications
|
||||||
|
context = self.context(self.last_commanded, None, variables, "ac_watchdog")
|
||||||
|
context["field"] = watchdog.field
|
||||||
|
context["min_watts"] = f"{watchdog.min_watts:g}"
|
||||||
|
context["duration"] = (
|
||||||
|
format_duration(duration_seconds) if duration_seconds else ""
|
||||||
|
)
|
||||||
|
|
||||||
|
template = watchdog.recovered_template if recovered else watchdog.notify_template
|
||||||
|
body = render(template, context)
|
||||||
|
title = render(settings.title or "{profile}", context)
|
||||||
|
|
||||||
|
await self.notifier.send(
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
priority=None if recovered else "urgent",
|
||||||
|
key="ac_watchdog_recovered" if recovered else "ac_watchdog",
|
||||||
|
force=True,
|
||||||
|
)
|
||||||
|
|
||||||
def record_event(self, desired, reason, rule, variables, cause="rule"):
|
def record_event(self, desired, reason, rule, variables, cause="rule"):
|
||||||
source_identity = self.anker_profile.get("identity", {})
|
source_identity = self.anker_profile.get("identity", {})
|
||||||
target_identity = self.target.profile.get("identity", {})
|
target_identity = self.target.profile.get("identity", {})
|
||||||
@@ -1028,6 +1117,8 @@ class Engine:
|
|||||||
self.report("telemetry complete, resuming evaluation", force=True)
|
self.report("telemetry complete, resuming evaluation", force=True)
|
||||||
self.incomplete_reported = False
|
self.incomplete_reported = False
|
||||||
|
|
||||||
|
await self.check_ac_watchdog(variables, now)
|
||||||
|
|
||||||
if await self.check_floor(session, variables, now):
|
if await self.check_floor(session, variables, now):
|
||||||
self.heartbeat(variables, now)
|
self.heartbeat(variables, now)
|
||||||
return
|
return
|
||||||
@@ -1063,6 +1154,12 @@ class Engine:
|
|||||||
else:
|
else:
|
||||||
self.report("battery floor: NONE SET", force=True)
|
self.report("battery floor: NONE SET", force=True)
|
||||||
|
|
||||||
|
if self.profile.ac_input_watchdog and self.profile.ac_input_watchdog.enabled:
|
||||||
|
self.report(
|
||||||
|
f"AC input watchdog: {self.profile.ac_input_watchdog.describe()}",
|
||||||
|
force=True,
|
||||||
|
)
|
||||||
|
|
||||||
if self.profile.monitor_fields:
|
if self.profile.monitor_fields:
|
||||||
self.report(
|
self.report(
|
||||||
"also logging (not used by any rule): "
|
"also logging (not used by any rule): "
|
||||||
|
|||||||
Reference in new issue
Block a user