# 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 census still counts every `setpoint_entity` key as before and adds the driver's own actuator to it (parity with 23ff9d0 on 349 option shapes is a test). The driver binds its Home Assistant on first use by the Controller, so a unit added later is wired too. **Schema, and why the new keys are not in it.** The brief asks for `driver`, `host`, `port`, `unit_id`, `maintenance` per entry and for `setpoint_entity` to become optional. Owner decision 2026-11-07: clients see nothing new until SMA-4/SMA-7 use the keys. The Supervisor form shows every schema key (and marks required ones), and a nested entry with a key the schema lacks has that key silently dropped (measured, shape probe section K), so the only way to hide them is to keep them out of config.yaml. So for now config.yaml, the translations and the labels are exactly 23ff9d0's, and `setpoint_entity` stays required there: until a driver without one is selectable, no entry can lack it. The code underneath is the brief's: the driver defaults to `goodwe_esphome`, rule 9(d) is per driver (a GoodWe entry without a setpoint entity is refused, same sentence), and rule 9(h) checks `driver`, `host`, `port`, `unit_id`, `maintenance` when a caller that skips the schema (the panel's save path, sim/inject, the checks) supplies them; those sentences name the key, having no label. When SMA-4 needs them: add them to the schema as optional with translations, make `setpoint_entity` `str?`, and add them to `uilayout` as card fields or a hidden list. **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.