SMA-10: two idle states. When the add-on goes away, an inverter whose driver hands back is given back (release(), once, then no more writes to it). A safety stop or refusal still holds 0 W for every unit. GoodWe behaves exactly as before.
Driver.hands_back (GoodWe: False; release() stays write(0) and is never called).
Controller.command_site_release() beside command_site_zero(): used by the Stop button and shutdown() (so SIGTERM, restart, task death and a late config refusal). The maintenance-abort paths and every safety/refusal path keep zero.
Shadow mode releases a hand-back unit. The heartbeat and the cycle write nothing to a released unit until control starts again.
release() is bounded by RELEASE_BUDGET_S (4 s, the write budget), units run together, and a raising or hanging release on one unit does not stop the others.
The Stop, restart, shadow and stopped sentences are built from the roster in app/handback.py (EN and NL, the make's words come from the driver). A GoodWe-only roster keeps every old literal.
Out of scope, not done: any SMA or FoxESS driver, the read-back after release(), per-driver timing, DOCS.md.
Tests: test_sma10_two_idle_states.py (new); test_messages.py has two classification entries for the control-task log line that moved into a roster builder.
SMA-10: two idle states. When the add-on goes away, an inverter whose driver hands back is given back (`release()`, once, then no more writes to it). A safety stop or refusal still holds 0 W for every unit. GoodWe behaves exactly as before.
- `Driver.hands_back` (GoodWe: False; `release()` stays `write(0)` and is never called).
- `Controller.command_site_release()` beside `command_site_zero()`: used by the Stop button and `shutdown()` (so SIGTERM, restart, task death and a late config refusal). The maintenance-abort paths and every safety/refusal path keep zero.
- Shadow mode releases a hand-back unit. The heartbeat and the cycle write nothing to a released unit until control starts again.
- `release()` is bounded by `RELEASE_BUDGET_S` (4 s, the write budget), units run together, and a raising or hanging release on one unit does not stop the others.
- The Stop, restart, shadow and stopped sentences are built from the roster in `app/handback.py` (EN and NL, the make's words come from the driver). A GoodWe-only roster keeps every old literal.
- Note: `DECISIONS/SMA-10-two-idle-states.md`; CHANGELOG line.
Out of scope, not done: any SMA or FoxESS driver, the read-back after `release()`, per-driver timing, DOCS.md.
Tests: `test_sma10_two_idle_states.py` (new); `test_messages.py` has two classification entries for the control-task log line that moved into a roster builder.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
A driver now says whether its release() hands the inverter back
(Driver.hands_back; GoodWe is False, so release() stays write(0) and the
Controller never calls it). command_site_release() sits next to
command_site_zero(): the Stop button and shutdown() (so SIGTERM, restart,
control task death and a late config refusal) hand a hand-back unit back,
once, and zero every other unit as before. Safety stops and refusals still
hold 0 W for every unit. Shadow mode releases a hand-back unit. After a
release nothing is written to the unit (heartbeat and cycle included) until
control starts again. release() is bounded by RELEASE_BUDGET_S (= the write
budget), runs concurrently per unit, and a raising or hanging release on one
unit does not stop the others.
The "set to 0 W" sentences for Stop, restart, shadow mode and the stopped
banner are built from the roster (app/handback.py, EN and NL, words from the
driver); a GoodWe-only roster keeps every old literal. A handed-back unit
shows "Back under <its control>" on Home.
Existing test edited: test_messages.py (two classification entries for the
CRITICAL line that moved into a roster builder, and the new release bound).
Co-Authored-By: Claude <noreply@anthropic.com>
Review (SMA-10): not merged, one change needed. Head a1be9c4 checked.
What is right: GoodWe is byte-identical (wire-level replay of 7 identity tools N=1/N=2, TEL-07 announce 0/0/0 at N=1 and N=2, 34 sentences EN/NL same sha, GoodWe Stop + heartbeat + shutdown wire log identical to base). The release-vs-zero call sites match the owner decision. The idle gate cannot fire during a safety stop, because auto only moves with the button and shadow only with the option. Full suite: 100 files, 0 failed (local). CI 1560/1561 green. Mutants (a), (b) and 2 of mine are all caught. The shadow banner change is fine.
Change needed: a failed release leaves the unit at its last command. When release() returns False, raises or hangs, nothing writes 0 W. Measured: the unit carried 1120 W, I pressed Stop, release failed, and the unit stayed at 1120 W with no further write. On shutdown, the add-on then exits with the unit still at that value. This is the "silence is the last command forever" case that DEV-02 forbids.
When a release fails, hold the unit at 0 W (bounded write(0)) until a retry succeeds. Do this on Stop, shutdown and shadow. Keep shutdown inside the Supervisor window and state the worst-case time.
Retry at heartbeat pace, not on every cycle. Log and add an event once per failure streak, plus once on recovery. Today every idle cycle adds an ERROR and an event (the 40-line action log fills up), and a hung release stalls every idle cycle by 4 s.
Add tests for 1 and 2, with a mutant for each.
Nit: handback._BACK_NL = "eigen sturing" gives "gaat terug naar eigen sturing". It should be "de eigen sturing", as the generic clause already says.
Follow-ups (not blocking): shadow tiles on a mixed roster, MQTT per-unit setpoint 0 for a handed-back unit, DOCS, "control stalled". All of them are in the dev's "left as is" list.
**Review (SMA-10): not merged, one change needed.** Head a1be9c4 checked.
What is right: GoodWe is byte-identical (wire-level replay of 7 identity tools N=1/N=2, TEL-07 announce 0/0/0 at N=1 and N=2, 34 sentences EN/NL same sha, GoodWe Stop + heartbeat + shutdown wire log identical to base). The release-vs-zero call sites match the owner decision. The idle gate cannot fire during a safety stop, because `auto` only moves with the button and `shadow` only with the option. Full suite: 100 files, 0 failed (local). CI 1560/1561 green. Mutants (a), (b) and 2 of mine are all caught. The shadow banner change is fine.
**Change needed: a failed release leaves the unit at its last command.** When `release()` returns False, raises or hangs, nothing writes 0 W. Measured: the unit carried 1120 W, I pressed Stop, release failed, and the unit stayed at 1120 W with no further write. On shutdown, the add-on then exits with the unit still at that value. This is the "silence is the last command forever" case that DEV-02 forbids.
1. When a release fails, hold the unit at 0 W (bounded `write(0)`) until a retry succeeds. Do this on Stop, shutdown and shadow. Keep shutdown inside the Supervisor window and state the worst-case time.
2. Retry at heartbeat pace, not on every cycle. Log and add an event once per failure streak, plus once on recovery. Today every idle cycle adds an ERROR and an event (the 40-line action log fills up), and a hung release stalls every idle cycle by 4 s.
3. Add tests for 1 and 2, with a mutant for each.
Nit: `handback._BACK_NL = "eigen sturing"` gives "gaat terug naar eigen sturing". It should be "de eigen sturing", as the generic clause already says.
Follow-ups (not blocking): shadow tiles on a mixed roster, MQTT per-unit setpoint 0 for a handed-back unit, DOCS, "control stalled". All of them are in the dev's "left as is" list.
Correction to my point 1. SMA-9 §1.4/§5 (SMA-5) relies on the inverter's own fallback timer (41193/41195) to take over when writes stop. A 0 W write re-sent on every retry would keep resetting that timer, so "hold 0 W until a retry succeeds" is the wrong fix.
What I recommend: after a failed release, send one bounded write(0), then go quiet. The unit stops carrying its last share, and the device's own fallback can still take over.
The alternative is to deliberately rely on the device fallback (SMA-5). That is the manager's choice. If it is chosen, the DECISIONS note must say so, and the measured case (1120 W left standing after a failed release) goes on a card.
Point 2 (retry and log pace) and point 3 (tests) are unchanged.
**Correction to my point 1.** SMA-9 §1.4/§5 (SMA-5) relies on the inverter's own fallback timer (41193/41195) to take over when writes stop. A 0 W write re-sent on every retry would keep resetting that timer, so "hold 0 W until a retry succeeds" is the wrong fix.
What I recommend: after a failed release, send **one** bounded `write(0)`, then go quiet. The unit stops carrying its last share, and the device's own fallback can still take over.
The alternative is to deliberately rely on the device fallback (SMA-5). That is the manager's choice. If it is chosen, the DECISIONS note must say so, and the measured case (1120 W left standing after a failed release) goes on a card.
Point 2 (retry and log pace) and point 3 (tests) are unchanged.
When release() fails (False, raises or hangs past RELEASE_BUDGET_S) the unit
gets one bounded write(0) through the new Driver.release_fallback() hook
(default write(0)), then nothing more, so the inverter's own fallback timer is
not reset. The release is retried once per heartbeat, not on every idle
write. One error line and one action-log event per failure streak, one line on
recovery. Dutch default "terug naar de eigen sturing". DECISIONS says what a
unit is left at.
test_messages.py: the RELEASE_BUDGET_S data entry is gone again (the line
that needed it is gone).
Co-Authored-By: Claude <noreply@anthropic.com>
When control takes the unit again, release_fails, release_retry_at and
release_zero_sent are reset, so the next Stop is a new streak with its own one
0 W, error line and event. Tests: a streak that Start interrupts without a
recovery; a driver that overrides release_fallback(). _BACK_NL "de eigen
sturing" (round 2) is kept.
Co-Authored-By: Claude <noreply@anthropic.com>
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>
Failed release (False, raise, hang), on Stop and on shutdown: the unit at 1120 W gets exactly one 0 W, then none over 6 heartbeats. Release is retried once per heartbeat (7 tries). One ERROR line and one event per streak; one INFO line and one event on recovery. A hung release now slows one cycle per heartbeat (6 of 30), not every cycle.
Reset: only a write while control runs clears the failed state. Heartbeats, a site 0 W and shadow mode while stopped do not. Stop/Start/Stop gives two single 0 W writes, two error lines and two events. The hook override gets release, fallback, its own write, and no 0 W from the add-on.
Shutdown with release and fallback both hanging: 6.01 s, also with two such units in parallel. The GoodWe unit still gets its 0 W.
GoodWe is byte-identical to base: 7 replay tools (N=1, N=2), announce 0/0/0, sentence hash ec9b9945.
Mutants red: (n) 1 check, (r) 4, my own (reset before the idle gate) 16. Test file 91 ok. Full suite on head: 100 files, 0 failed. CI 1566 and 1567 green.
DECISIONS and CHANGELOG are short and true. "de eigen sturing" is accepted.
Follow-up (not blocking): if Start is pressed while a hung release from Stop is still running, the late fallback sends one 0 W to the running unit and logs "hand-back failed". The next cycle writes again.
Re-review (round 2) of 6cae5a2: **merging.**
1. Failed release (False, raise, hang), on Stop and on shutdown: the unit at 1120 W gets exactly one 0 W, then none over 6 heartbeats. Release is retried once per heartbeat (7 tries). One ERROR line and one event per streak; one INFO line and one event on recovery. A hung release now slows one cycle per heartbeat (6 of 30), not every cycle.
2. Reset: only a write while control runs clears the failed state. Heartbeats, a site 0 W and shadow mode while stopped do not. Stop/Start/Stop gives two single 0 W writes, two error lines and two events. The hook override gets release, fallback, its own write, and no 0 W from the add-on.
3. Shutdown with release and fallback both hanging: 6.01 s, also with two such units in parallel. The GoodWe unit still gets its 0 W.
4. GoodWe is byte-identical to base: 7 replay tools (N=1, N=2), announce 0/0/0, sentence hash ec9b9945.
5. Mutants red: (n) 1 check, (r) 4, my own (reset before the idle gate) 16. Test file 91 ok. Full suite on head: 100 files, 0 failed. CI 1566 and 1567 green.
6. DECISIONS and CHANGELOG are short and true. "de eigen sturing" is accepted.
Follow-up (not blocking): if Start is pressed while a hung release from Stop is still running, the late fallback sends one 0 W to the running unit and logs "hand-back failed". The next cycle writes again.
admin
merged commit dbdc37b959 into release/1.02026-11-07 23:43:15 +01:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
SMA-10: two idle states. When the add-on goes away, an inverter whose driver hands back is given back (
release(), once, then no more writes to it). A safety stop or refusal still holds 0 W for every unit. GoodWe behaves exactly as before.Driver.hands_back(GoodWe: False;release()stayswrite(0)and is never called).Controller.command_site_release()besidecommand_site_zero(): used by the Stop button andshutdown()(so SIGTERM, restart, task death and a late config refusal). The maintenance-abort paths and every safety/refusal path keep zero.release()is bounded byRELEASE_BUDGET_S(4 s, the write budget), units run together, and a raising or hanging release on one unit does not stop the others.app/handback.py(EN and NL, the make's words come from the driver). A GoodWe-only roster keeps every old literal.DECISIONS/SMA-10-two-idle-states.md; CHANGELOG line.Out of scope, not done: any SMA or FoxESS driver, the read-back after
release(), per-driver timing, DOCS.md.Tests:
test_sma10_two_idle_states.py(new);test_messages.pyhas two classification entries for the control-task log line that moved into a roster builder.🤖 Generated with Claude Code
https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
Review (SMA-10): not merged, one change needed. Head
a1be9c4checked.What is right: GoodWe is byte-identical (wire-level replay of 7 identity tools N=1/N=2, TEL-07 announce 0/0/0 at N=1 and N=2, 34 sentences EN/NL same sha, GoodWe Stop + heartbeat + shutdown wire log identical to base). The release-vs-zero call sites match the owner decision. The idle gate cannot fire during a safety stop, because
autoonly moves with the button andshadowonly with the option. Full suite: 100 files, 0 failed (local). CI 1560/1561 green. Mutants (a), (b) and 2 of mine are all caught. The shadow banner change is fine.Change needed: a failed release leaves the unit at its last command. When
release()returns False, raises or hangs, nothing writes 0 W. Measured: the unit carried 1120 W, I pressed Stop, release failed, and the unit stayed at 1120 W with no further write. On shutdown, the add-on then exits with the unit still at that value. This is the "silence is the last command forever" case that DEV-02 forbids.write(0)) until a retry succeeds. Do this on Stop, shutdown and shadow. Keep shutdown inside the Supervisor window and state the worst-case time.Nit:
handback._BACK_NL = "eigen sturing"gives "gaat terug naar eigen sturing". It should be "de eigen sturing", as the generic clause already says.Follow-ups (not blocking): shadow tiles on a mixed roster, MQTT per-unit setpoint 0 for a handed-back unit, DOCS, "control stalled". All of them are in the dev's "left as is" list.
Correction to my point 1. SMA-9 §1.4/§5 (SMA-5) relies on the inverter's own fallback timer (41193/41195) to take over when writes stop. A 0 W write re-sent on every retry would keep resetting that timer, so "hold 0 W until a retry succeeds" is the wrong fix.
What I recommend: after a failed release, send one bounded
write(0), then go quiet. The unit stops carrying its last share, and the device's own fallback can still take over.The alternative is to deliberately rely on the device fallback (SMA-5). That is the manager's choice. If it is chosen, the DECISIONS note must say so, and the measured case (1120 W left standing after a failed release) goes on a card.
Point 2 (retry and log pace) and point 3 (tests) are unchanged.
Re-review (round 2) of
6cae5a2: merging.Follow-up (not blocking): if Start is pressed while a hung release from Stop is still running, the late fallback sends one 0 W to the running unit and logs "hand-back failed". The next cycle writes again.