The NL default for a hand-back driver without words (handback._BACK_NL) was changed to "de eigen sturing" in round 2 at the PR #239 review's request (comment 2495: the article as in the generic clause). The brief does not name it, so DECISIONS now says where it came from and a test checks the exact Dutch sentence. No app code change. Co-Authored-By: Claude <noreply@anthropic.com>
63 lines
3.8 KiB
Markdown
63 lines
3.8 KiB
Markdown
# 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).
|