UX-08 phase 2b: short, plain status rows and page banners
checks / unit (pull_request) Successful in 8m47s
infra-pin / fresh (pull_request) Failing after 1s

Every Commissioning row and every page banner is now worded in
app/messages.py (row_*, banner_*): one sentence of at most 15 words, a red
row up to three sentences so a latch still says how it clears, banners at
most 12 words. No row names a Home Assistant entity: sensors by their
setting, solar sources by their place in the list. Reason codes and limits
stay on the log line and event list; rows keep the numbers a reader acts on.

UX-07 item 1: ClampWatchdog counts over-rating and integrator-bound
engagements separately, and after an episode each count shows under its own
alarm's title instead of both under "output clamp".

No row colour, visibility or banner order changed. Rows carry an "id" so the
tests pin meaning rather than wording. The row and banner kinds leave the
allowlist (200 -> 168, cap equal). DOCS.md gains "Status page"; choices are in
DECISIONS/UX-08-short-notifications.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LyR88rXETvwhTgx6yHE39p
This commit is contained in:
2026-09-27 02:12:10 +02:00
co-authored by Claude Opus 5.5
parent 48ef06ac1f
commit d017fc67bf
19 changed files with 989 additions and 578 deletions
+45 -20
View File
@@ -477,7 +477,7 @@ History recorded before you set it keeps the old sign. See
The add-on reads your entity's own `unit_of_measurement` and converts: `mW`,
`W`, `kW`, `MW`, `GW`, `TW` — Home Assistant's own power units. A sensor with
**no** unit, or with a unit that is not one of those, is **refused**, and the
status page says which entity and which unit. That is deliberate: there is no
status page says so (*the sensor has no unit*). That is deliberate: there is no
magnitude test that can separate 30 W of dusk from 30 kW of noon, so a guess
here would be a silent factor of 1000 in the one channel that exists to be
believed a week later.
@@ -534,7 +534,7 @@ about:
|---|---|---|
| all three reporting 1200 / 2100 / 450 W | `3750.0` | `PV 3 750 W` |
| all three reporting, one of them **0 W** | `1650.0` | `PV 1 650 W` |
| one of the three `unavailable` | `unknown` | `PV —`, plus a caution naming which entity, why, and what the others reported |
| one of the three `unavailable` | `unknown` | `PV —`, plus a caution naming which source is missing |
| all three gone | `unknown` | as above |
**It does not publish the sum of the ones that answered.** Two inverters out of
@@ -546,8 +546,8 @@ it say something false. So the total goes to `unknown` and the recorder stores a
gap, exactly as it does for a single dead sensor.
The diagnostic is not thrown away — it is put where a person will see it. The
status page names the entity that is missing, the reason, **and** what the
others reported. That page is read live by a human who wants to fix something;
status page names the missing source by its place in the `pv_entities` list
(*source 2 of 3*). That page is read live by a human who wants to fix something;
the recorded number is read cold by an analysis that would believe it.
Note the **third row against the second**: a member honestly reporting `0 W` is
@@ -569,7 +569,7 @@ It is **instrumentation**. It is not an input to the control law, the
allocator, the arbiter, the peak guard, the maintenance cycle or any watchdog;
it is not part of `inputs_ok`, so a PV integration that breaks cannot stop the
battery being regulated; and its status row is a **caution**, never a
`NOT READY`. Wiring production into the control law would be a behaviour change
*Not ready*. Wiring production into the control law would be a behaviour change
on a system driving a real battery — and it would destroy the measurement,
because the loop would then be reacting to the thing being measured.
@@ -791,8 +791,8 @@ repeats. Thresholding that at 30 s would rebuild the false-trip limit cycle at
the exact operating point the controller aims for. Freeze detection is a
separate problem and needs the low-power case solved first.
You read it yourself instead: the add-on's status page shows it on the P1 line,
as `… 4 rejected, measurement unchanged for 312 s`. On a house drawing real
You read it yourself instead: the add-on's status page shows it on the Meter
row, as `… 4 readings refused, unchanged for 5 minutes`. On a house drawing real
power that figure stays in the seconds; minutes of it while the load is clearly
not near zero is the meter to go and look at.
@@ -1050,9 +1050,9 @@ If the setpoint this add-on is emitting and the grid figure it is regulating on
point **opposite ways** — charging the battery while the house imports, or
discharging it while the house exports — with both over `sign_disagree_w`, for
`sign_disagree_s` **continuously**, the add-on commands **0 W** and holds it
there. The banner reads `FAILSAFE`, the commissioning list says which two
numbers disagreed and for how long, and the `Controller status` entity reports
`failsafe`.
there. The banner reads *Command and meter point opposite ways*, the add-on
log says which two numbers disagreed and for how long, and the `Controller
status` entity reports `failsafe`.
#### ⚠️ At two or more inverters this is TWO checks, not one (SAFETY-30)
@@ -1194,7 +1194,8 @@ start, because a `persistent_notification` is in memory and that is precisely ho
the 2026-09-02 outage went unseen. It is cleared by pressing **Start**, like the
other latches.
The banner reads **`NOT DELIVERING — <unit> …`** as a *warning*, not a failure,
The banner reads **`<unit> is not delivering. Control keeps running.`** as a
*warning*, not a failure,
and it **displaces** `Running — holding grid at zero`. That last sentence is a
claim the site cannot make while a unit is absent, and a page that goes on
reading healthy while the fault runs is the whole shape this check exists to
@@ -1428,7 +1429,7 @@ What exists today, and what does not:
| SoC guard on the loop | ❌ — the BMS is the only energy backstop |
On the legacy `meter_source: off` path there are no P1 telegrams, so there is no
quarter-hour average to guard, and the status page says **NOT COVERED**. There
quarter-hour average to guard, and the status page says *Peak guard: off*. There
the mitigation is still operational: set `max_w` no higher than the loop actually
needs (the tuning was measured at 2000 W and a 1.78 kW load step is tracked fine
there), fit the e-stop, and watch for the alarm.
@@ -1712,13 +1713,13 @@ the add-on UI.
⚠️ **Only `meter_source: homewizard_local` is covered.** It is the only transport
that reads the meter's own document; on `ha_dsmr`, `mqtt_p1`, `ha_signed` and
`off` there is no signal to threshold and the status page says **NOT COVERED**
rather than pretending the guard is armed.
`off` there is no signal to threshold and the status page says *Stuck-meter
check: off* rather than pretending the guard is armed.
The same row appears for a second reason: a setpoint device that reports a range
from which no command magnitude is available in both directions leaves no rail
to measure at all, and a 0 W rail would be satisfied by *every* command. That is
reported as **NOT COVERED** too, and the detector stays silent rather than
reported as *off* too, and the detector stays silent rather than
degenerating into the bare "unchanged for N seconds" rule this design exists to
avoid.
@@ -2851,8 +2852,8 @@ discharge, and the running 15-minute average is still projected over the peak
limit. That 15-minute period will set or raise the month's billed peak
(capaciteitstarief). The controller is not at fault: the house load is larger
than the inverter. Switching off load in the next few minutes is the only thing
that still changes the bill. The projection and the watts are in the add-on log
and on the status page. See *The capacity-peak guard*.
that still changes the bill. The projection and the watts are in the add-on
log. See *The capacity-peak guard*.
### Battery stopped limiting the peak
@@ -2888,8 +2889,8 @@ listening to the add-on; this has happened on this hardware, once for
6 h 25 min. Switch the inverters off at the isolator. The alarm is latched: a
Home Assistant or add-on restart does not clear it, and the card is raised
again every 5 min until you start control on the add-on page. The watts and the time
window are in the add-on log and on the status page. See *The circulating-power
alarm*.
window are in the add-on log; the *Circulating* line on the status page shows the
live watts. See *The circulating-power alarm*.
### An inverter is not delivering
@@ -2919,7 +2920,7 @@ regulating against a number that has stopped moving. The age is still fresh, so
no other guard stops it. This is an alarm only: control keeps running and keeps
following the reading. Held for a whole 15-minute period, it can set the month's
billed peak. Check the meter; if it is stuck, stop control on the add-on page.
The limit and command figures are in the add-on log and on the status page. See
The limit and command figures are in the add-on log. See
*The frozen-meter alarm*.
## The Web UI
@@ -2935,6 +2936,30 @@ inverter.** The configuration page reads and writes the add-on's own settings
and can restart the add-on; the set of commands that reach the hardware is
exactly the three buttons it always had.
## Status page
The banner at the top says in one line what matters most now. Below it, the
**Commissioning** list has one short row per check: ✓ green is fine, ! amber
needs a look, ✗ red needs action. The rows name settings in words, never a Home
Assistant entity id. The full figures of any alarm are in the add-on log, on the
ERROR or WARNING line of that alarm. The texts are in `app/messages.py`.
| Row starts with | What it tells you |
|---|---|
| Controller | When the controller last ran. Red when it has stalled: the control board then sets the inverter to 0 W. |
| Meter | The grid reading, its age, readings refused, and how long its value has not changed. |
| Stuck-meter check | Whether the frozen-meter alarm can run. It needs the HomeWizard local source (`homewizard_local`), an inverter rating, and a power range the inverter can reach. *The inverter's own limit* means the setpoint device's range sets the threshold, below `max_w`. |
| Meter reading may be stuck | The frozen-meter alarm is on. See *Meter reading may be stuck* under Notifications. |
| *month*: peak … | This month's peak, and how close it is to the soft limit. |
| Peak guard, Load too high, Battery at its peak reserve | What the capacity-peak guard is doing. *Peak guard: off* means `meter_source` is `off`, so there is no 15-minute average to guard. |
| Battery level, Battery power, Grid power | The reading of that sensor. *No usable reading* means the sensor is missing **or** its reading is held back as too old (a separate stale-reading row then says so). Check it in Developer Tools before replacing it. |
| Inverter control | The setpoint entity, and the power range the inverter accepts. |
| Power request too high, Battery is not keeping up | The two loop alarms, each with its own count since start. After an episode clears, the count stays, ending in *not now*. See *The winding-loop alarm*. |
| Direction check, Site direction check | The sign checks (SAFETY-02/30). *Opposite for 12 of 60 seconds* counts toward a stop. When it trips, the inverter, or every inverter for the site check, is set to 0 W until you start control. |
| Circulating power check, Inverters worked against each other | SAFETY-31, only with several inverters. |
| Delivery check, *name* is not delivering | SAFETY-42, only with several inverters. |
| Solar production | The PV reading. Sources are numbered in the order of `pv_entities`. The *reversed-sign list* is `pv_invert_entities`. A strongly negative reading by day means that source's sign is reversed. |
## Status entities
If an MQTT broker is available the add-on publishes setpoint, grid power,