TEL-04: a third meter_source for a single signed entity
TEL-01 shipped ha_dsmr and mqtt_p1, and neither can read the meter that is
actually fitted here. The house has a HomeWizard P1 exposing ONE signed
entity, sensor.p1_meter_active_power (+ import, - export); ha_dsmr wants two
unsigned registers and refuses a negative one outright, which is every
exporting telegram. So sensor.p1_sample_age_s could not be produced at this
site, and FW-01's watchdog needs it - measured, not theoretical: the house P1
went 51.1 s and 36.2 s without a state change overnight, both past
meter_max_age_s 30, so without the age sensor the watchdog would false-trip
the battery to 0 W.
Adds meter_source: ha_signed, reading p1_net_entity (and optionally
p1_phase_net_entities in L1..L3 order for the capacity-tariff peak). The
derivation is split_signed(), sitting next to make_sample's subtraction for
the same reason it does - the moment a user is asked to write two template
sensors that split a signed value, the sign convention is back in unreviewed
YAML underneath a safety input, which is exactly what TEL-01 removed.
The transport is a subclass of HaDsmrSource overriding only _wanted() and
build(), so every rule TEL-01 established is inherited rather than
re-implemented: ingest timestamping, meter_max_age_s, the clock-recomputed
sensor.p1_sample_age_s republished ~1 Hz, the plausibility ceiling, the
"prime the cache from get_states but never build a sample out of it" rule,
"a reconnect emits nothing", and unavailable/unknown treated as a MISSING
reading and never as 0 W.
Defaults to off. An existing install is unaffected until it opts in.
test_p1.py: 122 -> 174 checks. Includes an end-to-end run of the new
transport against a fake Home Assistant websocket, and the sign convention
asserted against real captured readings from
sim/scenarios/ha-p1_meter_active_power-2026-08-{20,23}.json (-5710 W at
13:46 local under full sun is export; +775 W at midnight is import).
Non-vacuity: ten mutations of the new rules, each applied alone and reverted
byte-identical. Nine turn the suite red. The tenth - splitting the per-phase
signed values rather than passing them through - is an equivalent mutant,
because make_sample subtracts the two lists again and does not sign-check
per-phase figures. That is recorded in a ponytail: comment at the site rather
than left for the next reviewer to rediscover.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Du77usMj8XNKNFZGmUiWDa
This commit is contained in:
co-authored by
Claude Opus 5
parent
c24bc0a011
commit
8b51a51e20
+27
-11
@@ -51,14 +51,28 @@ phase having charged nothing.
|
||||
### P1 meter ingestion
|
||||
|
||||
`meter_entity` above expects one signed sensor, which usually means a template
|
||||
someone wrote by hand. A Belgian P1 meter does not publish one: it publishes two
|
||||
**unsigned** registers, consumption and injection. Setting `meter_source` moves
|
||||
that subtraction into the add-on, where it is done once and tested, and replaces
|
||||
`meter_entity` entirely.
|
||||
someone wrote by hand. Setting `meter_source` moves the whole derivation into
|
||||
the add-on, where it is done once and tested, and replaces `meter_entity`
|
||||
entirely.
|
||||
|
||||
Which mode you want depends on what your P1 reader publishes, and there are two
|
||||
shapes in the wild:
|
||||
|
||||
- **Two unsigned registers**, consumption and injection, which is what a Belgian
|
||||
P1 read over DSMR gives you → `ha_dsmr`, or `mqtt_p1` for a bridge. The add-on
|
||||
subtracts them.
|
||||
- **One signed figure**, positive = import and negative = export, which is what
|
||||
a HomeWizard P1 gives you (`sensor.p1_meter_active_power`) → `ha_signed`. The
|
||||
add-on splits it. `ha_dsmr` **cannot** read this: it wants two registers and
|
||||
rejects a negative one outright, which is every exporting telegram.
|
||||
|
||||
Either way, do not build the missing shape out of template sensors. The point of
|
||||
`meter_source` is that the sign convention is derived in one tested place rather
|
||||
than in YAML nobody reviews underneath a safety input.
|
||||
|
||||
| option | default | meaning |
|
||||
|---|---|---|
|
||||
| `meter_source` | `off` | `off` keeps `meter_entity`. `ha_dsmr` subscribes to the DSMR integration over the HA WebSocket; `mqtt_p1` reads a topic |
|
||||
| `meter_source` | `off` | `off` keeps `meter_entity`. `ha_dsmr` subscribes to the DSMR integration over the HA WebSocket; `mqtt_p1` reads a topic; `ha_signed` subscribes to one signed entity over the HA WebSocket |
|
||||
| `meter_phases` | 1 | 1 or 3. Must match the telegram, or every telegram is rejected and logged |
|
||||
| `meter_max_age_s` | 30 | Beyond this the reading is stale and grid power reads as *missing*. On its own it does **not** command 0 W — see the timing note below. It is also the longest a reading is held forward into the 15-minute average |
|
||||
| `meter_mqtt_topic` | | `mqtt_p1` only |
|
||||
@@ -66,6 +80,8 @@ that subtraction into the add-on, where it is done once and tested, and replaces
|
||||
| `p1_export_entity` | | The **unsigned** injection sensor |
|
||||
| `p1_phase_import_entities` | `[]` | L1..L3, in order. Needed for the capacity-tariff peak on a three-phase connection |
|
||||
| `p1_phase_export_entities` | `[]` | L1..L3, in order |
|
||||
| `p1_net_entity` | | `ha_signed` only. The **signed** net-power sensor: `+` import, `-` export |
|
||||
| `p1_phase_net_entities` | `[]` | `ha_signed` only. L1..L3, in order, each signed the same way. Needed for the capacity-tariff peak on a three-phase connection |
|
||||
|
||||
#### How long a dead meter takes to reach 0 W
|
||||
|
||||
@@ -124,12 +140,12 @@ disabled there is nothing feeding it, and an age sensor climbing with no ingeste
|
||||
behind it would trip the firmware watchdog on a system that is working fine.
|
||||
|
||||
> **Known limit, `mqtt_p1` only.** The age measures *arrival*, not change. On the
|
||||
> `ha_dsmr` path that is exactly right: a frozen meter emits no `state_changed`,
|
||||
> so nothing arrives and the age climbs. On the MQTT path a bridge that is stuck
|
||||
> republishing its last telegram keeps arriving, so the age stays near zero and a
|
||||
> frozen meter still looks fresh. Detecting *that* needs a change-detector rather
|
||||
> than an arrival-detector, and it is not in this version. Prefer `ha_dsmr` where
|
||||
> both are available.
|
||||
> two HA WebSocket paths (`ha_dsmr`, `ha_signed`) that is exactly right: a frozen
|
||||
> meter emits no `state_changed`, so nothing arrives and the age climbs. On the
|
||||
> MQTT path a bridge that is stuck republishing its last telegram keeps arriving,
|
||||
> so the age stays near zero and a frozen meter still looks fresh. Detecting
|
||||
> *that* needs a change-detector rather than an arrival-detector, and it is not
|
||||
> in this version. Prefer an HA WebSocket source where both are available.
|
||||
|
||||
### Control
|
||||
|
||||
|
||||
Reference in New Issue
Block a user