UX-11 review: restored stops log their figures to events, the quiet-meter WARNING is rate-limited
- After a restart, each restored stop (direction per inverter and site, delivery, circulation) also writes its saved reason to the action log. log_level defaults to info and events starts empty, so the DEBUG evidence alone left the figures only in state.json. - A flapping meter (ha_signed under a flat load, 42-97 s between changes) logs at most one WARNING/INFO pair per 10 minutes, with the episodes in between at DEBUG. - LONG_LINE_OK refuses any key whose text is a safety reason. - test_watchdog keeps the byte pin beside the meaning pin, since the text did not change. - DOCS says the DEBUG line needs log_level debug. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -119,6 +119,9 @@ WRITE_BUDGET_S = 4.0
|
||||
# restart this add-on (measured on TEST-23), so a latched alarm is re-asserted
|
||||
# this often. Why: goodwe docs/DECISIONS.md, SAFETY-34.
|
||||
REANNOUNCE_S = 300.0
|
||||
# UX-11 review: a quiet meter logs at most one WARNING this often, because on
|
||||
# `ha_signed` a healthy meter under a flat load can go 42-97 s between changes.
|
||||
METER_QUIET_REPEAT_S = 600.0
|
||||
_mono = time.monotonic # patched by the checks; asyncio keeps the real one
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
@@ -619,6 +622,13 @@ class Controller:
|
||||
messages.reason_code(inv.sign_wd.reason))
|
||||
_LOG.debug("Evidence for %s: %s.", inv.name,
|
||||
inv.sign_wd.reason or "(reason not recorded)")
|
||||
# UX-11 review: `events` starts empty and DEBUG is off by
|
||||
# default, so the saved figures go on the action log too.
|
||||
saved = inv.sign_wd.reason or messages.NO_REASON_SAVED
|
||||
self.log_event(f"FAILSAFE {inv.name}, saved before the "
|
||||
f"restart: {saved}",
|
||||
hint=messages.hint("stop_restored",
|
||||
unit=inv.name))
|
||||
|
||||
# --- SAFETY-42 + SAFETY-34 -------------------------------------------
|
||||
# ⚠️ A LATCH THAT DOES NOT ACT, WHICH IS THE ONE ASYMMETRY WITH EVERY
|
||||
@@ -645,6 +655,11 @@ class Controller:
|
||||
inv.name, messages.reason_code(inv.delivery_wd.reason))
|
||||
_LOG.debug("Evidence for %s: %s.", inv.name,
|
||||
inv.delivery_wd.reason or "(reason not recorded)")
|
||||
saved = inv.delivery_wd.reason or messages.NO_REASON_SAVED
|
||||
self.log_event(f"NOT DELIVERING {inv.name}, saved before the "
|
||||
f"restart: {saved}",
|
||||
hint=messages.hint("not_delivering",
|
||||
unit=inv.name))
|
||||
|
||||
# --- SAFETY-30: THE SITE HALF OF THE SIGN CHECK ----------------------
|
||||
# ⚠️ ONE PER SITE, BESIDE THE ONE PER INVERTER, AND ARMED ONLY AT N>=2.
|
||||
@@ -678,6 +693,10 @@ class Controller:
|
||||
"control: %s.", messages.reason_code(self.site_sign_wd.reason))
|
||||
_LOG.debug("Evidence for the site: %s.",
|
||||
self.site_sign_wd.reason or "(reason not recorded)")
|
||||
self.log_event("FAILSAFE site, saved before the restart: "
|
||||
+ (self.site_sign_wd.reason
|
||||
or messages.NO_REASON_SAVED),
|
||||
hint=messages.hint("site_direction"))
|
||||
# And the per-unit detectors are told which question they are asking,
|
||||
# here as well as in `cycle()`. `checks()` and the status page read
|
||||
# `inv.sign_wd.cfg` and can be called before the first cycle has run, so
|
||||
@@ -733,6 +752,10 @@ class Controller:
|
||||
messages.reason_code(self.circ_wd.reason))
|
||||
_LOG.debug("Evidence for the site: %s.",
|
||||
self.circ_wd.reason or "(reason not recorded)")
|
||||
self.log_event("CIRCULATING POWER, saved before the restart: "
|
||||
+ (self.circ_wd.reason
|
||||
or messages.NO_REASON_SAVED),
|
||||
hint=messages.hint("circulating"))
|
||||
|
||||
# SAFETY-03. The output clamp's alarm - see clamp.py for why the clamp
|
||||
# itself is the cheap half.
|
||||
@@ -940,6 +963,9 @@ class Controller:
|
||||
self.why_words = "Starting"
|
||||
# UX-11: has this meter episode's WARNING been logged? See read_inputs.
|
||||
self._meter_quiet_logged = False
|
||||
# ...when the last WARNING was, and whether this episode logged one.
|
||||
self._meter_quiet_warned = None
|
||||
self._meter_quiet_loud = False
|
||||
# ALLOC-01. The allocation record from the last resolution, or None
|
||||
# before the first cycle. ⚠️ IT IS KEPT SO THAT SOMETHING CAN GATE ON
|
||||
# IT: SAFETY-28 puts the write path behind this record, and DEV-02 reads
|
||||
@@ -1912,16 +1938,30 @@ class Controller:
|
||||
# UX-11: the red meter row says "the add-on log shows why", so a meter
|
||||
# that goes quiet gets one WARNING per episode, not one per cycle. It
|
||||
# is a log line only: no notification, and nothing it decides.
|
||||
# A flapping meter logs at most one WARNING per METER_QUIET_REPEAT_S,
|
||||
# and the episodes in between go to DEBUG.
|
||||
if self.p1_enabled:
|
||||
if (self.p1.stale and not self._meter_quiet_logged
|
||||
and self.p1.published_age_s > self.p1.max_age_s):
|
||||
self._meter_quiet_logged = True
|
||||
_LOG.warning("No meter reading for over %s, so control "
|
||||
"waits and then sets the battery to 0 W.",
|
||||
messages.seconds(self.p1.max_age_s))
|
||||
now = _mono()
|
||||
self._meter_quiet_loud = (
|
||||
self._meter_quiet_warned is None
|
||||
or now - self._meter_quiet_warned >= METER_QUIET_REPEAT_S)
|
||||
if self._meter_quiet_loud:
|
||||
self._meter_quiet_warned = now
|
||||
_LOG.warning("No meter reading for over %s, so control "
|
||||
"waits and then sets the battery to 0 W.",
|
||||
messages.seconds(self.p1.max_age_s))
|
||||
else:
|
||||
_LOG.debug("No meter reading for over %s again.",
|
||||
messages.seconds(self.p1.max_age_s))
|
||||
elif not self.p1.stale and self._meter_quiet_logged:
|
||||
self._meter_quiet_logged = False
|
||||
_LOG.info("Meter readings are back.")
|
||||
if self._meter_quiet_loud:
|
||||
_LOG.info("Meter readings are back.")
|
||||
else:
|
||||
_LOG.debug("Meter readings are back.")
|
||||
|
||||
if self.inputs_ok:
|
||||
if self.inputs_bad_since is not None:
|
||||
|
||||
Reference in New Issue
Block a user