# SMA-10 - two idle states: hold 0 W, or hand the inverter back **Status: for review.** Tests: `test_sma10_two_idle_states.py`. Source: SMA-9 report sections 1.4, 3.2; owner decision 2026-10-20 (Q1, "as proposed"). A unit is idle in one of two ways, and the driver says which: `Driver.hands_back`. **False (GoodWe):** the add-on holds 0 W, `release()` is `write(0)` and the Controller never calls it, so Stop keeps today's 0 W write and today's 0 W heartbeat. **True:** `release()` gives the unit back to whatever steers it by itself (SMA energy management), once, and then nothing is written to it until control starts again. **Why two.** When the add-on goes away (Stop, shutdown, SIGTERM, restart, task death, a config refusal) nobody is left to hold a number, so the unit goes back to its own controller. When the add-on itself decides to stop (sign trip, site sign, circulating power, a refused or non-finite write, missing readings, another writer, an inverter fault) it has just said it cannot trust that battery, and handing it to something else would let that something do what it likes with it. Those hold 0 W through `write(0)` for every unit, whatever its driver. Shadow mode on a unit that hands back uses `release()`: it must leave the unit alone, and 0 W would be a command. **Where.** `Controller.command_site_release()` sits beside `command_site_zero()` (unchanged). Stop and `shutdown()` call it; the two maintenance-abort paths keep zero (the add-on runs on, the sentence says 0 W for a moment). With no hand-back unit it is `command_site_zero()`. `_write_unit()` also refuses to write a hand-back unit while the add-on is idle (stopping, stopped, or shadow mode): it releases once and returns, so the heartbeat and the cycle's own "stopped" 0 W cannot reach it. A safety stop runs while control runs, so it falls through to the write. Start re-arms: `released` drops and `last_write` clears. **Bounds.** `RELEASE_BUDGET_S` equals `WRITE_BUDGET_S` (4 s): units are released together, so one slow unit costs the kill window no more than a hung GoodWe write does. A release at the bound is abandoned, not awaited. A release that raises, hangs or returns false leaves that unit un-handed (the next idle write tries again) and does not touch the others. Exit 78 before the Controller exists has no driver to release and keeps today's path. **Words.** Every sentence that said "set to 0 W" for the add-on going away or for shadow mode is built from the roster in `app/handback.py` (EN and NL): a roster with no hand-back unit gets None and keeps its old literal, so a GoodWe site sees no changed byte. A hand-back unit's words (`Driver.words`: `back`, `back_nl`) come from its driver; one without them gets "its own control" / "de eigen sturing" (the article as in the generic clause; PR #239 review comment 2495, not the brief). MQTT `status` is unchanged; the Home battery row of a handed-back unit says "Back under SMA control" and carries `handed_back`. **Not done here (SMA-4, SMA-5, SMA-11):** any SMA/FoxESS driver, the read-back after `release()`, per-driver timing, DOCS.md's safety model and the "control stalled" sentence, the site-level "Held at 0 W" tiles in shadow mode on a mixed roster, and the MQTT per-unit setpoint of a handed-back unit (it still publishes 0). **After a failed release** (returns False, raises or hangs past the bound) the unit gets one `write(0)` through `Driver.release_fallback()` (default `write(0)`; a driver where 0 W is not always safe overrides it), then nothing: a repeated 0 W would keep resetting the inverter's own fallback timer, so after that the unit is left to its own fallback. The release is retried once per heartbeat; one error line and one action-log event per failure streak, one line on recovery. Worst case at shutdown is `RELEASE_BUDGET_S` + `FALLBACK_BUDGET_S` (6 s).