Files
goodwe-addon/goodwe_controller/DECISIONS/SMA-2-driver-seam.md
T
glenn schrooyenandClaude e56eeef8f0
checks / unit (pull_request) Successful in 23m14s
infra-pin / fresh (pull_request) Successful in 2s
SMA-2: each inverter is reached through a driver; GoodWe behaviour unchanged
app/drivers.py holds one Driver per roster entry (configured, actuator_id,
limits, read, write, release, probe, link_stale_s, batt_bound, settle_s, brand,
words). GoodWeEsphomeDriver makes the calls the code it replaced made, in the
same order. Inverter gets driver; has_writer, is_live and link_note read it. The
SoC/power read, the write, the device range (run_control, amain, the settings
panel), rule 2 per unit and the SAFETY-28 census go through it.

setpoint_entity is optional per entry beside driver, host, port, unit_id and
maintenance; only goodwe_esphome is selectable, the rest are checked and unused.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-11-07 11:28:47 +01:00

2.3 KiB

SMA-2 - each inverter is reached through a driver

Status: for review. Tests: test_sma2_driver.py. Source: SMA-9 report section 1.1. A pure refactor: no behaviour change for GoodWe, no change to a command, a log line or a sentence the add-on shows.

Every place that read or steered an inverter used the GoodWe ESPHome entities directly, so a second make meant touching all of them. app/drivers.py now holds one Driver per roster entry (Inverter.driver): configured(), actuator_id(), limits(), read(), write(), release(), probe(), and link_stale_s, batt_bound, settle_s, brand, words. GoodWeEsphomeDriver makes the same calls in the same order as the code it replaced; its release() is write(0).

Moved to the driver: the SoC and power read (_read_unit), the write (_write_unit, still under the SAFETY-28 gate, the clamp, shadow mode and the write budget), the device range (run_control, amain, the settings panel), liveness and its window (has_writer, is_live, link_note), the freshness bound, the status rows' "is a source set" test, rule 2's per-unit range check and SAFETY-28's census. Not moved: the clamp, the gate, the heartbeat and command_site_zero(); stop is still 0 W for every unit, and nothing calls release() yet (SMA-10). settle_s and the control constants are not read from the driver yet either: with only GoodWe they would change nothing.

The seam is not TCP-shaped. The census asks each entry's driver for its actuator id: the setpoint entity for GoodWe, host:port:unit_id for a driver reached over a socket, so two units behind one gateway are two actuators and one unit named twice is the SAFETY-28 refusal. The driver binds its Home Assistant on first use by the Controller, so a unit added later is wired too.

Schema. setpoint_entity is optional per entry (rule 9(d) still requires it for GoodWe, with the same sentence), beside driver (only goodwe_esphome), host, port, unit_id, maintenance. The last four are checked for shape and not read. The Supervisor shape probe was re-run (section K).

Checked against 23ff9d0: replay digests and a log of every Home Assistant call are byte-identical at N=1 and N=2; the TEL-07 announce diff is 0/0/0; a fake driver beside a GoodWe unit allocates, grades, drops and publishes per unit; a call site that goes round the seam is caught.