Deploy add-on 0.17.0 (release/1.0 merge e36565f, PR #185) to `master`.
Contents: SAFETY-103, MAINT-13, MAINT-14, SAFETY-107, MAINT-15. See `goodwe_controller/CHANGELOG.md` `## 0.17.0`.
- Owner go for deploy: 2026-10-11.
- predeploy_check (`--deployed-version 0.16.0`) on fresh live options: to be run before merge.
- Merge only after `checks / unit (pull_request)` is green on this head, pinning `head_commit_id`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
While a battery settles from its ended drain (SAFETY-95), the house point
was grid + every battery's reading. A register can be 10 s older than the
meter, so B swung 860 -> 1650 -> 870 W on A's old reading (rig check 5).
Now the house point is held and moved only as far as the meter proves:
each battery is between its last changed reading and its commands since
(a reading that changed this cycle, or a refused side, as it reads).
N=1 and 1 s registers are byte-identical to release/1.0.
DECISIONS/SAFETY-103-old-readings-after-a-drain.md, test_safety103.py.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mutation M13 (a battery out of the split counted in the house point)
survived; a direct check now kills it. win[0] is read through a guard
(test_named_absence).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test table gives 1.5 kW at N=2 and 3.9 kW summed at N=3 under a
surplus, and about 1 kW after a lost link without one; not 1.9 kW.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A charge is complete at SoC >= target, or at >= target - 3 once the SoC has
made no new high for 60 min of allowed charging, or when the charging time
runs out at >= target - 3. The 36 h timeout counts only the minutes the price
gate let the pack charge (plus a 7 day wall-clock backstop). A cycle that
fails (stuck drain, charge below target - 3) backs off 7 days, tells the
operator once (EN + NL), and no cycle starts on that pack in that time unless
the operator forces one. State survives a restart; old state files load.
No option added. DECISIONS/MAINT-13-full-pack-is-complete.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- gate_open is true with no price gate set up, in a cheap window, or while the
pack is taking charge; the stall rule and timeout now work on a no-tariff site.
- reset the save timer on a phase change; exact 5 min gap-cap test.
- test_safety93: original timeout scenario kept (B finished) plus a case where a
due B takes the slot after A's failure.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
test_static_tariff forbids main.py reading `static_profile`; a small
static_gated() in static_tariff.py answers it, and the Controller keeps the
answer in `_price_gated` beside `cheap`.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
- A price-provider site with no fixed tariff is not a gate (its plan never
sets `cheap`); DECISIONS §3 says so and a test pins it.
- The fixed-tariff check is worked out once an hour, not every tick.
- test_maint13: run() steps really are `step` minutes; the long loops use
5-minute steps (the per-tick cap); wall backstop checked "not before".
- test_safety93: the drain bound is the rig's configured drain rate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At one inverter SAFETY-52's "another writer" end tested target != loop_w.
A held refusal makes the loop ask 0 W, so any binding claim (maintenance
charge, peak guard, trade) ended it every cycle, and compute() learned it
again in the same cycle: "BATTERY REFUSES" / "ENDED: another writer" each
second on the rig. Now the held refusal is judged afresh against the
writer's ask (compute() gets the same inputs as before); it ends only if
compute() no longer finds it. Commands unchanged: 300-seed differential
trace against release/1.0, N=1 and N=2, every command and refusal state
identical. DECISIONS/SAFETY-107.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
HOLD's time check moves into Maintenance._hold(), used by step() and
_no_soc(). A hold that loses its SoC now ends at hold_min, writes
last_completed and logs the same COMPLETE line. The 15 min HOLD line says
the hold still ends at its normal time. Behaviour with a reading is
unchanged (differential trace against db9e231).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bump version to 0.17.0 and add the closed 0.17.0 CHANGELOG section.
No code change, no option change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SAFETY-107 row names the tested base; MAINT-17 says code reading only;
SAFETY-105 figure is from a simulation; SAFETY-97 edge is 175 W; the
maintenance note cites MAINT-14 and MAINT-15; SAFETY-108 "about 15 s".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Deploy add-on 0.17.0 (release/1.0 merge
e36565f, PR #185) tomaster.Contents: SAFETY-103, MAINT-13, MAINT-14, SAFETY-107, MAINT-15. See
goodwe_controller/CHANGELOG.md## 0.17.0.--deployed-version 0.16.0) on fresh live options: to be run before merge.checks / unit (pull_request)is green on this head, pinninghead_commit_id.🤖 Generated with Claude Code
https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC