DOC-1: operator docs name the real P1 entity and a roster that starts
checks / unit (pull_request) Successful in 8m8s

- DOCS.md / config.yaml: the watchdog's P1 age entity is
  sensor.goodwe_p1_sample_age (device "GoodWe" + "P1 sample age", as
  app/mqtt.py announces and the live install recorded), not
  sensor.p1_sample_age_s.
- DOCS.md: the inverters: example and per-entry table carry the required
  bms_soc_floor (and capacity_kwh); config.yaml's schema comment says four
  required keys, not three.
- Field guide: marked v0.1.0/outdated; slew_w row no longer recommends the
  1000 W that SAFETY-39 now refuses. HTML regenerated; the PDF is NOT
  rebuilt (no Chrome here). README/DOCS pointers fixed.
- test_docs.py: the documented roster must pass app/validate.py and the
  schema's per-entry keys; the documented entity id must equal the id
  composed from app/mqtt.py; the wrong id may appear in no shipped doc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
This commit is contained in:
2026-09-24 23:24:50 +02:00
co-authored by Claude Opus 5.5
parent a45ed06c05
commit 29ca9b5157
7 changed files with 243 additions and 22 deletions
+14 -9
View File
@@ -4,8 +4,8 @@ Drives a GoodWe ES/BP battery inverter over its RS485 meter bus: holds net grid
exchange at zero, and runs a monthly battery maintenance cycle so the BMS can
balance cells and recalibrate its coulomb counter.
Installers: read `FIELD-GUIDE.md` in the repository. It is not optional reading —
it contains the commissioning gates and the failure modes.
Installers: the printed field guide (`docs/GoodWe-RS485-Field-Guide.pdf`) is
v0.1.0 and outdated; where it disagrees with this page, this page wins.
## Before you start
@@ -165,12 +165,14 @@ inverters:
soc_entity: sensor.goodwe_a_soc
batt_entity: sensor.goodwe_a_battery_power
max_w: 5000
bms_soc_floor: 10
capacity_kwh: 15.36
- name: inverter-b
setpoint_entity: number.goodwe_b_setpoint_w
soc_entity: sensor.goodwe_b_soc
batt_entity: sensor.goodwe_b_battery_power
max_w: 4600
bms_soc_floor: 10
capacity_kwh: 20.48
```
@@ -190,16 +192,18 @@ inverter's own **AC** power. If it is DC the AC-side ceiling is nearer 4324 W.
| `name` | yes | Short identifier. It namespaces this unit's SAFETY-02 latch, heartbeat and write bookkeeping in the add-on's state file, so **two units may not share one** |
| `setpoint_entity` | yes | This inverter's own ESPHome `number.*_goodwe_setpoint_w` |
| `max_w` | yes | The supervised cap on what may be written to **this** inverter. The site's ceiling is the **sum** |
| `bms_soc_floor` | yes | This pack's own BMS cut-out percent (SAFETY-32). See [`bms_soc_floor`](#tariff-all-optional) |
| `soc_entity` | | This pack's state of charge |
| `batt_entity` | | This unit's battery power, `+` = discharging |
| `batt_invert` | | Flip if needed |
| `capacity_kwh` | | This pack's usable capacity. State it on every entry or on none — see [OPT-09](#how-the-site-target-is-divided-between-the-packs-opt-09) |
⚠️ **Nothing is inherited.** An entry that leaves out `soc_entity` has **no SoC
sensor** — it does *not* fall back to the flat one. That is deliberate: every
per-device value became per-instance in 0.5.x precisely so that no inverter is
ever graded against a sibling's numbers, and a silent fallback would rebuild
that in YAML. `name`, `setpoint_entity` and `max_w` are required of every entry
for the same reason: there is no rating to borrow.
that in YAML. `name`, `setpoint_entity`, `max_w` and `bms_soc_floor` are required
of every entry for the same reason: there is no rating or cut-out to borrow.
⚠️ **Clear the flat `setpoint_entity` when you declare a roster,** or the add-on
refuses to start and says so. It is the single-inverter form and it is not read
@@ -600,7 +604,7 @@ than in YAML nobody reviews underneath a safety input.
> `ha_signed`.** It reads the same meter and produces the same numbers, but it
> polls the meter directly instead of watching a Home Assistant entity — and
> that is the difference between an age sensor a watchdog can threshold and one
> it cannot. See "`sensor.p1_sample_age_s`" below; `ha_signed` remains for
> it cannot. See "`sensor.goodwe_p1_sample_age`" below; `ha_signed` remains for
> installs where the meter is only reachable through Home Assistant.
> ⚠️ **`ha_dsmr` and `mqtt_p1` have never processed a telegram from real
@@ -755,7 +759,7 @@ connection-level reading is still accepted on its own.
**Why this mode exists:** every HTTP response is an *arrival*. The meter
answered, now, with its current reading — whether or not the number moved. That
is the signal `sensor.p1_sample_age_s` needs and the one Home Assistant cannot
is the signal `sensor.goodwe_p1_sample_age` needs and the one Home Assistant cannot
give it at all (see the note below). It is also simply fewer moving parts: the
five-second cadence is the meter's own, rather than an integration's polling of
it re-published as a state change.
@@ -792,12 +796,13 @@ as `… 4 rejected, measurement unchanged for 312 s`. 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.
#### `sensor.p1_sample_age_s`
#### `sensor.goodwe_p1_sample_age`
Published over MQTT discovery whenever a broker is available: **seconds since the
newest accepted telegram**, refreshed every second rather than only when a
telegram lands. The ESP32's stale-input watchdog subscribes to this exact entity
id, so do not rename it.
id; do not rename the `GoodWe` device or this entity, since HA composes the id
from both (see [What appears in Home Assistant at two inverters](#what-appears-in-home-assistant-at-two-inverters)).
The reason it is recomputed against the clock is that Home Assistant only pushes
a state when the state *changes*. A meter sitting at a genuinely constant reading
@@ -833,7 +838,7 @@ behind it would trip the firmware watchdog on a system that is working fine.
> went **42.2 s and 97.0 s** between changes, and 23 Aug peaks at 29.1 s — all
> past the default `meter_max_age_s` of 30.
>
> So `sensor.p1_sample_age_s` on `ha_signed` is safe to *read*, and it is
> So `sensor.goodwe_p1_sample_age` on `ha_signed` is safe to *read*, and it is
> correct whenever the value is moving, but it must not yet be thresholded by
> the ESP32 stale-input watchdog: a quiet house would trip the battery to 0 W.
> Raising `meter_max_age_s` is **not** the fix — the two conditions produce an