Release 0.22.0: release/1.0 -> master #251

Merged
admin merged 67 commits from release/1.0 into master 2026-11-14 15:58:34 +01:00
Owner

Brings release/1.0 to master as add-on version 0.22.0 (master is 0.21.0, 5e00cbb, live since 2026-11-08). Owner go 2026-11-14: "Go release". The cut PR #250 (release-0.22.0 -> release/1.0) carries the version bump and the CHANGELOG; this PR is opened by the reviewer after #250 merges, and the owner merges it.

Why now: inverter A's next monthly-cycle try is 2026-11-19 at 19:00 local and needs MAINT-24 and MAINT-25 live. The deploy must land before 2026-11-19 19:00 local.

Minor bump, no option change. config.yaml options and schema (86 options), app/validate.py, translations/, build.yaml, Dockerfile, requirements.txt and run.sh are identical to master 0.21.0 (git diff 5e00cbb..4c0122a is empty for them; the cut changes only the version line). It is minor because control acts differently: a monthly charge and the other battery's grid charge (MAINT-24), the plan during the 7-day wait (MAINT-25), a slow meter (SAFETY-136) and a young 15-minute period (SAFETY-135). MAINT-20 part 2 and MAINT-21 change no behaviour. MAINT-28 (#247) was reverted (#249) and is not in this release; the tree of 4c0122a equals the tree of 1b5f7c9.

What the deploy needs

  • Green checks / unit (pull_request) on this PR (a real run, not skipped), then the owner merges. Merging deploys nothing.
  • No tag has been made. The deploy is a separate step with its own owner go (docs/RELEASE.md): ha store reload, then the update of the add-on on the box. Not run by the dev agent.
  • Existing options need no change. Before the deploy, run tools/predeploy_check.py --deployed-version 0.21.0 against a FRESH export of the live options (.data.options) on the merged tree: the copy used for the check below is from 2026-11-08 17:41.
  • master must still be 5e00cbb when this opens; if it moved, stop and re-check.
  • Watch after the deploy: from the first cheap night, A's monthly charge at about 1.5 kW and B with no grid charge ("no trade (cycle)"); the Home plan line for the 7-day wait; the peak notifications for a few nights; a clean stop and restart of the add-on.
  • Rollback is a revert with a new bump (docs/RELEASE.md).

Predeploy check

predeploy_check.py --deployed-version 0.21.0 --options <live-options-now.json .data.options> in goodwe-addon-checks:1, --network none, read-only mounts:

  • before the bump (4c0122a): REFUSED, "the deploy would be a no-op" (version 0.21.0 on both sides, 44 runtime commits since the last version change). That is the expected result.
  • after the bump (94214d6; the merged head e79d403 differs from it only in CHANGELOG text): "live options pass this version's validator" and "CLEARED for deploy", rc 0. No warnings.
  • Limit: the options file is dated 2026-11-08 17:41 and is 6 days old. It shows that those options start 0.22.0; it says nothing about options changed since. Hence the fresh export above.

Review result (REL-0.22.0 review)

  • #250 merged at b700c0be922c17797a60305e027494003d76deaf (parents 4c0122a + e79d403, tree equal to e79d403), through the API, pinned to e79d403 after a green checks / unit (pull_request) run 1654 (job 2805, pull_request, attempt 1, 12:54 to 13:20 CET).
  • Independent review of 634001c: only config.yaml (version) and CHANGELOG.md changed; options, schema, translations, validator and option defaults are identical to master 5e00cbb; every first-parent merge since 0.21.0 has a row; MAINT-28 is history only; the 0.21.0 and older sections are identical to master. Five wording fixes pushed as e79d403 (SAFETY-136 row claimed a gain at 15 s late that the rig did not show; SAFETY-111 line said "4 nights, longer than 36 h"; MAINT-28 count; MAINT-25/26 next-try wording). Findings: #250 issuecomment-2608.

The deploy needs a fresh export of the live options first: the predeploy file is from 2026-11-08 17:41. The deploy must land before 2026-11-19 19:00 local.

CHANGELOG section

0.22.0

A minor bump: a battery's monthly charge now gets the cheap night, the plan knows about the 7-day wait, a slow meter is followed more gently, and one peak-guard stop no longer costs a night charge its whole period. No option changes.
No option was added, removed or renamed, and no default changed (86 options). Existing installs need no option change: options that start 0.21.0 also start 0.22.0, and the Supervisor form is the same. It is a minor bump because control acts differently in the cases below; every saved setting still starts.

A battery's monthly charge gets the cheap night. While one battery is in the charge part of its monthly cycle, the other battery no longer grid-charges ahead of it, and the charge aims at this month's highest peak, as the peak guard does, not at the lower limit. In a rig test with the live setup (two batteries, one in its monthly charge), the cycling battery charged at about 1.5 kW in every cheap quarter (13.3 kWh in a night) and the other battery bought nothing; before, it charged at about 225 W or not at all. The peak limit was kept. At the start of a month, before a peak above the limit has been seen, this charge is much slower (SAFETY-111).

The plan knows about the 7-day wait. After a failed monthly cycle, Home no longer shows a cycle every night. It says the cycle waits, and shows the day the wait ends. The cycle tries again at the first start hour after the wait (MAINT-26). In a rig test no cycle was drawn during the wait, and none started.

One peak-guard stop no longer costs the night charge its period. With battery modes on, a night grid charge that the peak guard stops early in a 15-minute period used to stay stopped until the period ended. It may now start again after a minute, once per period, and not in the last minute. A real overload is cut as fast as before. In a rig test of the 01:00 case the night's charge was 0.45 to 0.46 kWh, against 0.42 to 0.44 kWh before. The status says "grid charge paused, it tries again in a minute" while it waits.

A slow meter is followed more gently. With a meter whose readings come about 4 s or more apart, such as a 5 s meter, control now takes smaller steps after a big change until the meter shows it. With Home Assistant readings 10 s late, less power goes to the grid when loads switch on and off: 44 % less in a simulation and 29 % less in a rig test. Meters of 1 or 2 s are left alone by design (the same commands in 63 simulation runs). With readings 15 s late the gain is smaller (19 % in a simulation), and a rig test sent more to the grid than before; see the known issues.

No change in behaviour: the comments in the main program file are shorter, and the file is split into smaller files by job. A rig test saw the same start-up, stops and commands.

Contents of this release, in merge order since 0.21.0 (5e00cbb):

56fc41f The comments in the main program file are shorter and clearer. There is no change in behaviour (MAINT-20, #224)
8508ac3 The main program file is split into smaller files by job. There is no change in behaviour; a rig test saw the same start-up, stops and commands (MAINT-21, #243)
a8ecf17 With a 5 s meter, control follows the meter more gently after a change. With Home Assistant readings 10 s late, less power goes to the grid when loads switch on and off. Meters of 1 or 2 s are left alone (SAFETY-136, #244)
02eac3b A night grid charge that the peak guard stops early in a 15-minute period may start again after a minute, once per period, instead of staying stopped until the period ends. A real overload is cut as fast as before. Battery modes only (SAFETY-135, #245)
1b5f7c9 While a battery's monthly cycle charges, the other battery no longer grid-charges ahead of it, and the charge aims at this month's highest peak, so it gets the cheap night. After a failed cycle, the plan no longer shows a cycle every night and says the cycle waits (MAINT-24, MAINT-25, #246)
f3f9483, 4c0122a History only: MAINT-28 was merged (#247) and then taken out again (#249). The two cancel out and the code is the same as at 1b5f7c9. MAINT-28 is not in this release (MAINT-28)

Known issues

  • A false "Battery is not keeping up" can still show when the peak guard pauses a maintenance charge (SAFETY-92).
  • A battery capped at a low limit with a perfectly flat reading can still trip the stale check (SAFETY-74).
  • A battery whose reading at rest is beyond about ±175 W is still not caught when it refuses (SAFETY-97).
  • The peak guard can still back off near the end of a 15-minute period when a meter reading is late, by less than 30 s. This is about the guard's discharge only; a grid charge is cut back since 0.19.2 (SAFETY-99).
  • Two inverters: the maintenance charge can switch on and off while the house is above the peak aim (SAFETY-88).
  • Two inverters: the "circulating" figure counts a battery's reading at rest (SAFETY-98).
  • Two inverters: a drain that starts during a big step in house use can send the other battery to full power, and about 1 kW goes to the grid for about 10 s (SAFETY-104).
  • Two inverters: when a battery comes back during a maintenance charge, the grid can step by several kW at once (SAFETY-101).
  • Two inverters: when a battery drops out during a solar surplus, the others can swing by up to 1.5 kW in a simulation (SAFETY-105).
  • Two inverters: after a drain ends, a change in house use can go unanswered for about 9 s, because some readings are old (SAFETY-106).
  • Two inverters: when one battery's level is missing, both batteries are held at 0 W, and for about 15 s the last command stays on (SAFETY-108).
  • Two inverters: if one battery's level stays missing for 12 h, the other battery's cycle can be marked failed, with a 7-day wait and a notice, though it is healthy. This comes from reading the code; it has not been seen on a site (MAINT-17).
  • When the 15-minute demand forecast sensor counts the battery's own charge, the maintenance charge runs at about half the power the peak limit leaves, so it takes about twice as long (seen on a site: about 1.2 kW instead of 2.5 kW; the peak limit was kept). At the start of a month, before a peak above the limit, that is about 2 kWh a night in a rig test: the cycle can reach its 36 h limit after about 4 nights and fail, and since 0.22.0 the other battery gets no grid charge on those nights (SAFETY-111).
  • One inverter: after a link drop, the battery's own safety stop to 0 W can be taken as a refusal, and the site stays at 0 W for up to 15 min (SAFETY-102).
  • After a Home Assistant restart, control still waits until Home Assistant has finished starting, which can take several minutes, though the readings are already there (SAFETY-115).
  • While Home Assistant is not answering, the page can list inverters as refusing commands and sensors as missing, and send you to Developer Tools, though they are fine (UX-37).
  • If Home Assistant hangs or stops, the page keeps saying "Waiting for Home Assistant" and never turns into an alarm (SAFETY-113).
  • Right after Home Assistant has started, the page can say for about 4 s that an inverter cannot be controlled, and the first command can come late (SAFETY-114).
  • After a Home Assistant restart, the controller status sensor can still say "running" while the add-on cannot control. It was seen once, and the cause is not known (SAFETY-116).
  • Battery modes on: after a planned top-up from the grid, the Batteries screen can say "Enough sun is forecast" (UX-32).
  • Battery modes on: the forecast line "we count on" can show more sun than the plan counts, because the plan takes house use off first (UX-33).
  • Battery modes on: before a planned top-up, the Today list shows a smaller "gives" amount than the plan will use; it grows as the charge happens (UX-34).
  • Two inverters, battery modes on: while the other battery is in its maintenance charge, a battery's card can say it charges from the grid while it stays at 0 W (OPT-18).
  • Two inverters, battery modes on: while a battery refuses to charge in a cheap run, the action log on the page gets about 8 lines an hour for its retries, and the page hint says "waiting for a better price" (UX-38).
  • A battery in a safety stop, or one the add-on cannot reach, can still be started for a cycle, and its drain then waits about 12 h and ends in a failed-cycle notice. This comes from reading the code; it has not been seen on a site (MAINT-19).
  • On the Settings page, the rows of the solar forecast lists are labelled "Phase 1", "Phase 2" instead of "Sensor 1", "Sensor 2" (UX-27).
  • In the solar forecast list, a blank or repeated row makes "source 2 of 2" count differently from the rows on the Settings page (UX-28).
  • The solar forecast message says "has no reading" also when the sensor shows a number that is too old or in the wrong unit (UX-29).
  • If you use only the forecast lists, two Settings notes sit on the single-sensor field, and one of them tells you to set that field (UX-30).
  • After a restart during a price outage, the "No prices" message can say "yet" when prices did come in earlier (UX-39).
  • On the new settings page, you cannot see whether a price service token is set. Check the Configuration tab (UX-41).
  • On the classic settings page, the first click on Save after editing a field can do nothing. Click again (UX-42).
  • A steady overload on a 1 s meter can warn, and send the Home Assistant notification, 2 or 3 times at the start of each 15-minute period instead of once. The peak limit is still kept (SAFETY-122).
  • A fixed tariff is priced once per hour, so a price window that starts or ends at :15, :30 or :45 gets the wrong price for part of that hour. Windows that start and end on the hour are right (TARIFF-23).
  • Step 6 of the setup wizard stays empty because of an error in the page, in English and in Dutch (UX-51).
  • On Dutch pages, refusal texts, the last-event line under the Home buttons, the installer details and the trading status sentences are still English, and a few Dutch texts read badly, such as "1 metingen geweigerd" (UX-52).
  • Some price texts say "hour" where the add-on acts on a 15-minute period, such as "cheapest hour" (UX-47).
  • After the add-on restarts, the Home price chart shows no price for the earlier part of today, and the setup check can say "12 of 24 hours", though the prices are known (UX-48).
  • With battery modes on and only batteries on "Leftover solar", the Home price chart can mark a cheap period where no battery charges (UX-49).
  • On a phone, the bars of the Home price chart are too thin to tap one 15-minute period. The arrow keys work (UX-50).
  • In the last second of a 15-minute period, being a few watts over the aim can make the peak guard ask a battery for full power for one second. When a charge ends on a period edge, the last-second give can fade out over about 10 s, and a rig test exported up to 7.4 kW for about 8 s (SAFETY-124).
  • During the night grid charge, a 15-minute period can end 6 to 14 W over the peak aim, because the battery charges harder than asked. This was seen in simulation; a rig test saw 2 to 8 W (OPT-35).
  • The test rig could not bring back the false peak alarm that SAFETY-126 fixes, so that fix is shown in simulation only. Watch the notifications for a few nights (TEST-35).
  • The night cheap run is sized for the batteries' full charging power, not for what the peak limit leaves. Under a tight limit the run can end before the batteries reach their level, and Home can show a level they do not reach (79.9 % against a shown 95 % in a rig test) (OPT-25).
  • By day, a Leftover solar plan that has only a daily forecast figure counts the sun from now, not from when the figure was read. It counts about 22 % too much for a figure one hour old; the night plan is not affected (OPT-30).
  • The night charge can buy too much in a house that uses more at night than by day, such as one with a car charger or heat pump, and on a sunny day, because spare sun in one expensive hour does not count for the later ones (OPT-31).
  • A cheap run that has already started can overlap a new give run after a re-plan, so a battery can charge from the grid at the dearest price for part of an hour. This was seen in a test case, not on a site. Also in 0.19.2 (OPT-32).
  • If the solar forecast sensors stop updating before 17:00, as in winter or further north, there is no forecast from three hours after their last update until 20:00 (OPT-33).
  • With a forecast that gives only a daily total, the sun is spread an hour off on the two clock-change days after the change. The day's total is right (OPT-24).
  • The add-on learns the house's use only once a solar forecast is set, so a site that adds a forecast later waits one more day for its first plan (OPT-26).
  • Home says "No trading on price yet: no prices have arrived" when every battery is on Solar first, though the prices are shown (UX-55).
  • The Home plan header can say "Charges in the cheapest hour, gives power in the dearest" when Home draws no charge, and it says "hour" for 15-minute prices (UX-56).
  • While the peak guard holds back charging, Home hides the rest of the cheap run and gives no reason. The charge starts again when the guard lets go (UX-57).
  • With battery modes on and selling on, Home can draw a "Sells up to" block when the add-on judges the trade not worth it, and then it will not sell (UX-58).
  • Home, the plan and the Forecast tab stop drawing a battery's give at the trading floor, though control goes on giving down to the battery's cut-out (UX-59).
  • During a price outage, the Batteries screen says "Prices differ too little" and Home says "The price gap is too small" (UX-60).
  • With battery modes on and no meter, or a meter that is a Home Assistant entity, a grid charge is not possible and nothing says so (UX-61).
  • On the Batteries screen, a charge the add-on refused in a cheap run reads as "Saves its power for ...", for up to 15 minutes (UX-62).
  • A Leftover solar battery with no capacity set never charges from the grid when a solar forecast is set, and nothing warns about it (UX-63).
  • The "How the modes work" box on the Batteries screen does not describe Cheap hours only (UX-64).
  • With battery modes on, Settings still shows "Trade on the price spread", which the modes ignore. A site with modes on and no price source starts, and the Batteries screen says "No prices, so it follows the house" without pointing to Settings (UX-65).
  • With battery modes off and several equal prices in a row, Home and the Forecast tab draw one price quarter, while the add-on trades every quarter of the run (UX-67).
  • Late in the day, the Forecast tab's "now" line and last bars sit over the axis labels at the right edge (UX-68).
  • The Forecast tab's level curve charges at each battery's full rating. Under a peak limit the levels it draws are too high and a charge block can end early (54.6 % shown against 39.7 % in a rig test) (UX-70).
  • The peak forecast check says maintenance charging is not kept under the peak limit when no peak forecast is set, but since 0.19.2 it is (UX-73).
  • The peak guard row can show the same figure twice, such as "at this rate 3 500 W, over 3 500 W". Also in 0.19.2 (UX-74).
  • If the inverter's setpoint entity appears in Home Assistant after the add-on has started, the page keeps saying "Inverter control: not found in Home Assistant" and the batteries say "An alarm stopped control" until a restart, though control runs (UX-75).
  • In the last minute of a 15-minute period, with Home Assistant readings 30 s late, the period can end about 80 W over the peak aim and over the limit: 3 530 W against a 3 450 W aim and a 3 500 W limit in a rig test with a 2 kW load added 45 s before the end. The peak guard sees the load too late to hold it back (SAFETY-133).
  • With Home Assistant readings 10 s late, the grid charge in a cheap run can swing by several kW. In a test the add-on asked for an 8.7 kW charge (9.6 kW on 0.20.0) (SAFETY-132).
  • A plan rebuilt while the battery levels are missing for a moment can drop a cheap run that has not started yet, so the battery does not charge that day. Seen in a replay of real prices, where it cost about EUR 0.40 of extra grid use; the Leftover solar change in 0.20.1 no longer triggers it in those replays, but the cause stays (OPT-37).
  • On a 5 s meter, Home Assistant readings 10 s late are still not caught, and readings 15 s late only after about 4 minutes. Control now follows the meter more gently, so less goes to the grid: in a simulation with a changing load, 340 Wh became 189 Wh at 10 s late and 363 Wh became 296 Wh at 15 s late. In a rig test, 15 s late exported 13 to 19 % more than before (SAFETY-136, SAFETY-134).
  • With readings 10 to 15 s late, the hold can end in the middle of the delay and the swing can come back. Seen in rig tests with a 2 s and a 5 s meter (SAFETY-134).
  • With readings 10 s late on a 1 s meter, the grid can still move between -422 and +340 W while the batteries are held. With a very long delay (30 to 90 s) the request can swing by about 900 W every few seconds. Seen in review tests (SAFETY-130).
  • A battery that answers very slowly (10 s or more) can be taken for late Home Assistant readings and held, and the page then blames Home Assistant. A GoodWe battery settles in about 6 s and this has not been seen on one (SAFETY-131).
  • The page row for late readings reads awkwardly in English ("Home Assistant readings show requests about N seconds late, so control holds the batteries"), and the Dutch text misses a word (UX-78).
  • When the add-on stops, a write that takes more than 2 s can still land after the 0 W, and the 30 s safety timer on the ESPHome board then sets 0 W. After the Stop button, a cycle that had already decided can send its share (560 to 670 W) just after the 0 W; the next cycle sends 0 W again (SAFETY-138).
  • In the last hour of a cheap run, Home can draw a battery's grid charge up to an hour past the time control stops charging it. It only draws too much, never too little (UX-79).
  • A tomorrow solar forecast sensor that reports just after midnight with yesterday's value can be used as the new day's forecast for up to 12 hours. This has not been seen on a site (OPT-39).
  • During a cheap-hours charge, at some 15-minute boundaries the add-on sends 0 W for one tick ("no trade (headroom)") and then goes on. Seen in a rig test, in 5 of 8 cases; once, with a frozen battery reading, it left a battery at 0 W until 23:45 (OPT-40).
  • During a real peak, a battery in its maintenance charge can switch between charging and giving, about 400 times in 45 minutes in rig tests. That means more wear, but no higher peak (MAINT-28).
  • While one battery's monthly cycle charges, the other battery's card on Home says "Saves its power for higher prices", and the plan still shows its grid charge in the cheap hours, though it will not charge (UX-80).
  • After a failed monthly cycle, the plan line shows the day the wait ends, such as 18 November, not when the cycle tries again, such as 19 November at 19:00 (MAINT-26).
  • At the start of a cheap window the peak guard can step once although the period is under the aim, and the step can land on a battery in its monthly charge. In a rig test it gave 1.7 to 2.3 kW for 6 s (SAFETY-140).
  • A grid charge at full power in the last seconds of a 15-minute period can run into the next period, where the peak guard cuts it back (SAFETY-139).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC

Brings `release/1.0` to `master` as add-on version 0.22.0 (master is 0.21.0, `5e00cbb`, live since 2026-11-08). Owner go 2026-11-14: "Go release". The cut PR #250 (`release-0.22.0` -> `release/1.0`) carries the version bump and the CHANGELOG; this PR is opened by the reviewer after #250 merges, and the owner merges it. **Why now:** inverter A's next monthly-cycle try is 2026-11-19 at 19:00 local and needs MAINT-24 and MAINT-25 live. **The deploy must land before 2026-11-19 19:00 local.** **Minor bump, no option change.** `config.yaml` options and schema (86 options), `app/validate.py`, `translations/`, `build.yaml`, `Dockerfile`, `requirements.txt` and `run.sh` are identical to master 0.21.0 (`git diff 5e00cbb..4c0122a` is empty for them; the cut changes only the version line). It is minor because control acts differently: a monthly charge and the other battery's grid charge (MAINT-24), the plan during the 7-day wait (MAINT-25), a slow meter (SAFETY-136) and a young 15-minute period (SAFETY-135). MAINT-20 part 2 and MAINT-21 change no behaviour. MAINT-28 (#247) was reverted (#249) and is not in this release; the tree of `4c0122a` equals the tree of `1b5f7c9`. ## What the deploy needs - Green `checks / unit (pull_request)` on this PR (a real run, not `skipped`), then the owner merges. Merging deploys nothing. - No tag has been made. The deploy is a separate step with its own owner go (`docs/RELEASE.md`): `ha store reload`, then the update of the add-on on the box. Not run by the dev agent. - Existing options need no change. Before the deploy, run `tools/predeploy_check.py --deployed-version 0.21.0` against a FRESH export of the live options (`.data.options`) on the merged tree: the copy used for the check below is from 2026-11-08 17:41. - `master` must still be `5e00cbb` when this opens; if it moved, stop and re-check. - Watch after the deploy: from the first cheap night, A's monthly charge at about 1.5 kW and B with no grid charge ("no trade (cycle)"); the Home plan line for the 7-day wait; the peak notifications for a few nights; a clean stop and restart of the add-on. - Rollback is a revert with a new bump (`docs/RELEASE.md`). ## Predeploy check `predeploy_check.py --deployed-version 0.21.0 --options <live-options-now.json .data.options>` in `goodwe-addon-checks:1`, `--network none`, read-only mounts: - before the bump (`4c0122a`): REFUSED, "the deploy would be a no-op" (version 0.21.0 on both sides, 44 runtime commits since the last version change). That is the expected result. - after the bump (`94214d6`; the merged head e79d403 differs from it only in CHANGELOG text): "live options pass this version's validator" and "CLEARED for deploy", rc 0. No warnings. - Limit: the options file is dated 2026-11-08 17:41 and is 6 days old. It shows that those options start 0.22.0; it says nothing about options changed since. Hence the fresh export above. ## Review result (REL-0.22.0 review) - **#250 merged** at `b700c0be922c17797a60305e027494003d76deaf` (parents `4c0122a` + `e79d403`, tree equal to `e79d403`), through the API, pinned to `e79d403` after a green `checks / unit (pull_request)` run 1654 (job 2805, pull_request, attempt 1, 12:54 to 13:20 CET). - Independent review of `634001c`: only `config.yaml` (version) and `CHANGELOG.md` changed; options, schema, translations, validator and option defaults are identical to master `5e00cbb`; every first-parent merge since 0.21.0 has a row; MAINT-28 is history only; the 0.21.0 and older sections are identical to master. Five wording fixes pushed as `e79d403` (SAFETY-136 row claimed a gain at 15 s late that the rig did not show; SAFETY-111 line said "4 nights, longer than 36 h"; MAINT-28 count; MAINT-25/26 next-try wording). Findings: #250 issuecomment-2608. **The deploy needs a fresh export of the live options first: the predeploy file is from 2026-11-08 17:41. The deploy must land before 2026-11-19 19:00 local.** ## CHANGELOG section ## 0.22.0 **A minor bump: a battery's monthly charge now gets the cheap night, the plan knows about the 7-day wait, a slow meter is followed more gently, and one peak-guard stop no longer costs a night charge its whole period. No option changes.** No option was added, removed or renamed, and no default changed (86 options). **Existing installs need no option change:** options that start 0.21.0 also start 0.22.0, and the Supervisor form is the same. It is a minor bump because control acts differently in the cases below; every saved setting still starts. **A battery's monthly charge gets the cheap night.** While one battery is in the charge part of its monthly cycle, the other battery no longer grid-charges ahead of it, and the charge aims at this month's highest peak, as the peak guard does, not at the lower limit. In a rig test with the live setup (two batteries, one in its monthly charge), the cycling battery charged at about 1.5 kW in every cheap quarter (13.3 kWh in a night) and the other battery bought nothing; before, it charged at about 225 W or not at all. The peak limit was kept. At the start of a month, before a peak above the limit has been seen, this charge is much slower ([[SAFETY-111]]). **The plan knows about the 7-day wait.** After a failed monthly cycle, Home no longer shows a cycle every night. It says the cycle waits, and shows the day the wait ends. The cycle tries again at the first start hour after the wait ([[MAINT-26]]). In a rig test no cycle was drawn during the wait, and none started. **One peak-guard stop no longer costs the night charge its period.** With battery modes on, a night grid charge that the peak guard stops early in a 15-minute period used to stay stopped until the period ended. It may now start again after a minute, once per period, and not in the last minute. A real overload is cut as fast as before. In a rig test of the 01:00 case the night's charge was 0.45 to 0.46 kWh, against 0.42 to 0.44 kWh before. The status says "grid charge paused, it tries again in a minute" while it waits. **A slow meter is followed more gently.** With a meter whose readings come about 4 s or more apart, such as a 5 s meter, control now takes smaller steps after a big change until the meter shows it. With Home Assistant readings 10 s late, less power goes to the grid when loads switch on and off: 44 % less in a simulation and 29 % less in a rig test. Meters of 1 or 2 s are left alone by design (the same commands in 63 simulation runs). With readings 15 s late the gain is smaller (19 % in a simulation), and a rig test sent more to the grid than before; see the known issues. **No change in behaviour:** the comments in the main program file are shorter, and the file is split into smaller files by job. A rig test saw the same start-up, stops and commands. Contents of this release, in merge order since 0.21.0 (`5e00cbb`): | | | |---|---| | `56fc41f` | The comments in the main program file are shorter and clearer. There is no change in behaviour ([[MAINT-20]], #224) | | `8508ac3` | The main program file is split into smaller files by job. There is no change in behaviour; a rig test saw the same start-up, stops and commands ([[MAINT-21]], #243) | | `a8ecf17` | With a 5 s meter, control follows the meter more gently after a change. With Home Assistant readings 10 s late, less power goes to the grid when loads switch on and off. Meters of 1 or 2 s are left alone ([[SAFETY-136]], #244) | | `02eac3b` | A night grid charge that the peak guard stops early in a 15-minute period may start again after a minute, once per period, instead of staying stopped until the period ends. A real overload is cut as fast as before. Battery modes only ([[SAFETY-135]], #245) | | `1b5f7c9` | While a battery's monthly cycle charges, the other battery no longer grid-charges ahead of it, and the charge aims at this month's highest peak, so it gets the cheap night. After a failed cycle, the plan no longer shows a cycle every night and says the cycle waits ([[MAINT-24]], [[MAINT-25]], #246) | | `f3f9483`, `4c0122a` | History only: MAINT-28 was merged (#247) and then taken out again (#249). The two cancel out and the code is the same as at `1b5f7c9`. MAINT-28 is not in this release ([[MAINT-28]]) | ### Known issues - A false "Battery is not keeping up" can still show when the peak guard pauses a maintenance charge ([[SAFETY-92]]). - A battery capped at a low limit with a perfectly flat reading can still trip the stale check ([[SAFETY-74]]). - A battery whose reading at rest is beyond about ±175 W is still not caught when it refuses ([[SAFETY-97]]). - The peak guard can still back off near the end of a 15-minute period when a meter reading is late, by less than 30 s. This is about the guard's discharge only; a grid charge is cut back since 0.19.2 ([[SAFETY-99]]). - Two inverters: the maintenance charge can switch on and off while the house is above the peak aim ([[SAFETY-88]]). - Two inverters: the "circulating" figure counts a battery's reading at rest ([[SAFETY-98]]). - Two inverters: a drain that starts during a big step in house use can send the other battery to full power, and about 1 kW goes to the grid for about 10 s ([[SAFETY-104]]). - Two inverters: when a battery comes back during a maintenance charge, the grid can step by several kW at once ([[SAFETY-101]]). - Two inverters: when a battery drops out during a solar surplus, the others can swing by up to 1.5 kW in a simulation ([[SAFETY-105]]). - Two inverters: after a drain ends, a change in house use can go unanswered for about 9 s, because some readings are old ([[SAFETY-106]]). - Two inverters: when one battery's level is missing, both batteries are held at 0 W, and for about 15 s the last command stays on ([[SAFETY-108]]). - Two inverters: if one battery's level stays missing for 12 h, the other battery's cycle can be marked failed, with a 7-day wait and a notice, though it is healthy. This comes from reading the code; it has not been seen on a site ([[MAINT-17]]). - When the 15-minute demand forecast sensor counts the battery's own charge, the maintenance charge runs at about half the power the peak limit leaves, so it takes about twice as long (seen on a site: about 1.2 kW instead of 2.5 kW; the peak limit was kept). At the start of a month, before a peak above the limit, that is about 2 kWh a night in a rig test: the cycle can reach its 36 h limit after about 4 nights and fail, and since 0.22.0 the other battery gets no grid charge on those nights ([[SAFETY-111]]). - One inverter: after a link drop, the battery's own safety stop to 0 W can be taken as a refusal, and the site stays at 0 W for up to 15 min ([[SAFETY-102]]). - After a Home Assistant restart, control still waits until Home Assistant has finished starting, which can take several minutes, though the readings are already there ([[SAFETY-115]]). - While Home Assistant is not answering, the page can list inverters as refusing commands and sensors as missing, and send you to Developer Tools, though they are fine ([[UX-37]]). - If Home Assistant hangs or stops, the page keeps saying "Waiting for Home Assistant" and never turns into an alarm ([[SAFETY-113]]). - Right after Home Assistant has started, the page can say for about 4 s that an inverter cannot be controlled, and the first command can come late ([[SAFETY-114]]). - After a Home Assistant restart, the controller status sensor can still say "running" while the add-on cannot control. It was seen once, and the cause is not known ([[SAFETY-116]]). - Battery modes on: after a planned top-up from the grid, the Batteries screen can say "Enough sun is forecast" ([[UX-32]]). - Battery modes on: the forecast line "we count on" can show more sun than the plan counts, because the plan takes house use off first ([[UX-33]]). - Battery modes on: before a planned top-up, the Today list shows a smaller "gives" amount than the plan will use; it grows as the charge happens ([[UX-34]]). - Two inverters, battery modes on: while the other battery is in its maintenance charge, a battery's card can say it charges from the grid while it stays at 0 W ([[OPT-18]]). - Two inverters, battery modes on: while a battery refuses to charge in a cheap run, the action log on the page gets about 8 lines an hour for its retries, and the page hint says "waiting for a better price" ([[UX-38]]). - A battery in a safety stop, or one the add-on cannot reach, can still be started for a cycle, and its drain then waits about 12 h and ends in a failed-cycle notice. This comes from reading the code; it has not been seen on a site ([[MAINT-19]]). - On the Settings page, the rows of the solar forecast lists are labelled "Phase 1", "Phase 2" instead of "Sensor 1", "Sensor 2" ([[UX-27]]). - In the solar forecast list, a blank or repeated row makes "source 2 of 2" count differently from the rows on the Settings page ([[UX-28]]). - The solar forecast message says "has no reading" also when the sensor shows a number that is too old or in the wrong unit ([[UX-29]]). - If you use only the forecast lists, two Settings notes sit on the single-sensor field, and one of them tells you to set that field ([[UX-30]]). - After a restart during a price outage, the "No prices" message can say "yet" when prices did come in earlier ([[UX-39]]). - On the new settings page, you cannot see whether a price service token is set. Check the Configuration tab ([[UX-41]]). - On the classic settings page, the first click on Save after editing a field can do nothing. Click again ([[UX-42]]). - A steady overload on a 1 s meter can warn, and send the Home Assistant notification, 2 or 3 times at the start of each 15-minute period instead of once. The peak limit is still kept ([[SAFETY-122]]). - A fixed tariff is priced once per hour, so a price window that starts or ends at :15, :30 or :45 gets the wrong price for part of that hour. Windows that start and end on the hour are right ([[TARIFF-23]]). - Step 6 of the setup wizard stays empty because of an error in the page, in English and in Dutch ([[UX-51]]). - On Dutch pages, refusal texts, the last-event line under the Home buttons, the installer details and the trading status sentences are still English, and a few Dutch texts read badly, such as "1 metingen geweigerd" ([[UX-52]]). - Some price texts say "hour" where the add-on acts on a 15-minute period, such as "cheapest hour" ([[UX-47]]). - After the add-on restarts, the Home price chart shows no price for the earlier part of today, and the setup check can say "12 of 24 hours", though the prices are known ([[UX-48]]). - With battery modes on and only batteries on "Leftover solar", the Home price chart can mark a cheap period where no battery charges ([[UX-49]]). - On a phone, the bars of the Home price chart are too thin to tap one 15-minute period. The arrow keys work ([[UX-50]]). - In the last second of a 15-minute period, being a few watts over the aim can make the peak guard ask a battery for full power for one second. When a charge ends on a period edge, the last-second give can fade out over about 10 s, and a rig test exported up to 7.4 kW for about 8 s ([[SAFETY-124]]). - During the night grid charge, a 15-minute period can end 6 to 14 W over the peak aim, because the battery charges harder than asked. This was seen in simulation; a rig test saw 2 to 8 W ([[OPT-35]]). - The test rig could not bring back the false peak alarm that [[SAFETY-126]] fixes, so that fix is shown in simulation only. Watch the notifications for a few nights ([[TEST-35]]). - The night cheap run is sized for the batteries' full charging power, not for what the peak limit leaves. Under a tight limit the run can end before the batteries reach their level, and Home can show a level they do not reach (79.9 % against a shown 95 % in a rig test) ([[OPT-25]]). - By day, a Leftover solar plan that has only a daily forecast figure counts the sun from now, not from when the figure was read. It counts about 22 % too much for a figure one hour old; the night plan is not affected ([[OPT-30]]). - The night charge can buy too much in a house that uses more at night than by day, such as one with a car charger or heat pump, and on a sunny day, because spare sun in one expensive hour does not count for the later ones ([[OPT-31]]). - A cheap run that has already started can overlap a new give run after a re-plan, so a battery can charge from the grid at the dearest price for part of an hour. This was seen in a test case, not on a site. Also in 0.19.2 ([[OPT-32]]). - If the solar forecast sensors stop updating before 17:00, as in winter or further north, there is no forecast from three hours after their last update until 20:00 ([[OPT-33]]). - With a forecast that gives only a daily total, the sun is spread an hour off on the two clock-change days after the change. The day's total is right ([[OPT-24]]). - The add-on learns the house's use only once a solar forecast is set, so a site that adds a forecast later waits one more day for its first plan ([[OPT-26]]). - Home says "No trading on price yet: no prices have arrived" when every battery is on Solar first, though the prices are shown ([[UX-55]]). - The Home plan header can say "Charges in the cheapest hour, gives power in the dearest" when Home draws no charge, and it says "hour" for 15-minute prices ([[UX-56]]). - While the peak guard holds back charging, Home hides the rest of the cheap run and gives no reason. The charge starts again when the guard lets go ([[UX-57]]). - With battery modes on and selling on, Home can draw a "Sells up to" block when the add-on judges the trade not worth it, and then it will not sell ([[UX-58]]). - Home, the plan and the Forecast tab stop drawing a battery's give at the trading floor, though control goes on giving down to the battery's cut-out ([[UX-59]]). - During a price outage, the Batteries screen says "Prices differ too little" and Home says "The price gap is too small" ([[UX-60]]). - With battery modes on and no meter, or a meter that is a Home Assistant entity, a grid charge is not possible and nothing says so ([[UX-61]]). - On the Batteries screen, a charge the add-on refused in a cheap run reads as "Saves its power for ...", for up to 15 minutes ([[UX-62]]). - A Leftover solar battery with no capacity set never charges from the grid when a solar forecast is set, and nothing warns about it ([[UX-63]]). - The "How the modes work" box on the Batteries screen does not describe Cheap hours only ([[UX-64]]). - With battery modes on, Settings still shows "Trade on the price spread", which the modes ignore. A site with modes on and no price source starts, and the Batteries screen says "No prices, so it follows the house" without pointing to Settings ([[UX-65]]). - With battery modes off and several equal prices in a row, Home and the Forecast tab draw one price quarter, while the add-on trades every quarter of the run ([[UX-67]]). - Late in the day, the Forecast tab's "now" line and last bars sit over the axis labels at the right edge ([[UX-68]]). - The Forecast tab's level curve charges at each battery's full rating. Under a peak limit the levels it draws are too high and a charge block can end early (54.6 % shown against 39.7 % in a rig test) ([[UX-70]]). - The peak forecast check says maintenance charging is not kept under the peak limit when no peak forecast is set, but since 0.19.2 it is ([[UX-73]]). - The peak guard row can show the same figure twice, such as "at this rate 3 500 W, over 3 500 W". Also in 0.19.2 ([[UX-74]]). - If the inverter's setpoint entity appears in Home Assistant after the add-on has started, the page keeps saying "Inverter control: not found in Home Assistant" and the batteries say "An alarm stopped control" until a restart, though control runs ([[UX-75]]). - In the last minute of a 15-minute period, with Home Assistant readings 30 s late, the period can end about 80 W over the peak aim and over the limit: 3 530 W against a 3 450 W aim and a 3 500 W limit in a rig test with a 2 kW load added 45 s before the end. The peak guard sees the load too late to hold it back ([[SAFETY-133]]). - With Home Assistant readings 10 s late, the grid charge in a cheap run can swing by several kW. In a test the add-on asked for an 8.7 kW charge (9.6 kW on 0.20.0) ([[SAFETY-132]]). - A plan rebuilt while the battery levels are missing for a moment can drop a cheap run that has not started yet, so the battery does not charge that day. Seen in a replay of real prices, where it cost about EUR 0.40 of extra grid use; the Leftover solar change in 0.20.1 no longer triggers it in those replays, but the cause stays ([[OPT-37]]). - On a 5 s meter, Home Assistant readings 10 s late are still not caught, and readings 15 s late only after about 4 minutes. Control now follows the meter more gently, so less goes to the grid: in a simulation with a changing load, 340 Wh became 189 Wh at 10 s late and 363 Wh became 296 Wh at 15 s late. In a rig test, 15 s late exported 13 to 19 % more than before ([[SAFETY-136]], [[SAFETY-134]]). - With readings 10 to 15 s late, the hold can end in the middle of the delay and the swing can come back. Seen in rig tests with a 2 s and a 5 s meter ([[SAFETY-134]]). - With readings 10 s late on a 1 s meter, the grid can still move between -422 and +340 W while the batteries are held. With a very long delay (30 to 90 s) the request can swing by about 900 W every few seconds. Seen in review tests ([[SAFETY-130]]). - A battery that answers very slowly (10 s or more) can be taken for late Home Assistant readings and held, and the page then blames Home Assistant. A GoodWe battery settles in about 6 s and this has not been seen on one ([[SAFETY-131]]). - The page row for late readings reads awkwardly in English ("Home Assistant readings show requests about N seconds late, so control holds the batteries"), and the Dutch text misses a word ([[UX-78]]). - When the add-on stops, a write that takes more than 2 s can still land after the 0 W, and the 30 s safety timer on the ESPHome board then sets 0 W. After the Stop button, a cycle that had already decided can send its share (560 to 670 W) just after the 0 W; the next cycle sends 0 W again ([[SAFETY-138]]). - In the last hour of a cheap run, Home can draw a battery's grid charge up to an hour past the time control stops charging it. It only draws too much, never too little ([[UX-79]]). - A tomorrow solar forecast sensor that reports just after midnight with yesterday's value can be used as the new day's forecast for up to 12 hours. This has not been seen on a site ([[OPT-39]]). - During a cheap-hours charge, at some 15-minute boundaries the add-on sends 0 W for one tick ("no trade (headroom)") and then goes on. Seen in a rig test, in 5 of 8 cases; once, with a frozen battery reading, it left a battery at 0 W until 23:45 ([[OPT-40]]). - During a real peak, a battery in its maintenance charge can switch between charging and giving, about 400 times in 45 minutes in rig tests. That means more wear, but no higher peak ([[MAINT-28]]). - While one battery's monthly cycle charges, the other battery's card on Home says "Saves its power for higher prices", and the plan still shows its grid charge in the cheap hours, though it will not charge ([[UX-80]]). - After a failed monthly cycle, the plan line shows the day the wait ends, such as 18 November, not when the cycle tries again, such as 19 November at 19:00 ([[MAINT-26]]). - At the start of a cheap window the peak guard can step once although the period is under the aim, and the step can land on a battery in its monthly charge. In a rig test it gave 1.7 to 2.3 kW for 6 s ([[SAFETY-140]]). - A grid charge at full power in the last seconds of a 15-minute period can run into the next period, where the peak guard cuts it back ([[SAFETY-139]]). 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
admin added 67 commits 2026-11-14 13:21:46 +01:00
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-20 part 2: main.py: keep whole sentences, no truncated warnings; refresh archive
checks / unit (pull_request) Failing after 24m55s
infra-pin / fresh (pull_request) Successful in 1s
3d31b679eb
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-20 part 2: main.py keeps the FCST-04 windowing caveat a test pins
checks / unit (pull_request) Successful in 25m56s
infra-pin / fresh (pull_request) Successful in 1s
2bbf88862a
Co-Authored-By: Claude <noreply@anthropic.com>
Nine conflicts in app/main.py, all in comments around new code: every code
line is release/1.0's, every comment follows the part 2 rule.

Co-Authored-By: Claude <noreply@anthropic.com>
Word for word in MAIN-orchestration-archive.md, keyed by the line at 16029d7.
No code change.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-20 part 2 r2: DECISIONS MAIN-orchestration-archive intro
checks / unit (pull_request) Successful in 25m14s
infra-pin / fresh (pull_request) Successful in 1s
f8df7cd860
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-20 part 2 review: comment-only fixes in main.py and the MAIN notes
checks / unit (pull_request) Successful in 30m13s
infra-pin / fresh (pull_request) Successful in 1s
bbbc89b235
- check_peak_floor_limited: restore the SAFETY-25 line (the reserve sentence is
  in messages.peak_reserve() on every branch, unreadable SoC included).
- Three status-page comments that no longer said anything (SAFETY-20 row,
  AC 5 delivery row, banner-vs-text) now say their rule in one line.
- tariff_refresh_wanted docstring: no orphan "TARIFF-24)" line.
- MAIN-orchestration.md: blank line before the first heading.
- MAIN-orchestration-archive.md: refilled intro; says how to find a section
  from a main.py pointer.

No code change: maint20_same_code.py 16029d7 -> SAME CODE, -OO same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Prepares the split of app/main.py's Controller into files by job, with no app
change. tools/main_files.py gives a test the text and the AST of main.py plus
the files split from it, and `live`, which sets a global in every file whose
functions read it (and raises when none does). Tests that read main.py's
source, and tests and replay tools that patch a main global (datetime,
compute, resolve, _mono, WRITE_BUDGET_S, ...), go through it, so they pass on
this layout and on the split one. test_split_patches.py fails when a test or
tool patches an app.main attribute that main.py no longer reads, or that
another file also reads. tools/maint21_same_ast.py proves every function,
method and module-level assignment of main.py at the base is found unchanged
in its new file.

Co-Authored-By: Claude <noreply@anthropic.com>
Moves why_raised, why_key, tariff_plan_row and arbitrage_row out of app/main.py unchanged. main.py imports them, so app.main still exports them. tools/maint21_same_ast.py: OK.

Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. InputsMixin: read_inputs, _read_unit, live_units and the other readers of the meter, packs, PV and forecast. tools/maint21_same_ast.py: OK.

Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. WritesMixin: write_setpoint, write_refusal, _write_unit, _hand_back, the refusal evidence, heartbeats and the site release/zero commands. tools/maint21_same_ast.py: OK.

Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. TradeMixin: trade sizing, the mode runs, the day plan, the peak floors, drain and charge limits, pack actions. tools/maint21_same_ast.py: OK.

Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. CycleMixin: cycle() and _shadow_settle(). cycle() is not cut up. tools/maint21_same_ast.py: OK.

Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. AlarmsMixin: the check_* alarms and the loop-alarm helpers. tools/maint21_same_ast.py: OK.

Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. StatusMixin: checks, status_payload, forecast_payload, publish, mqtt_command, handle_action, why_words. tools/maint21_same_ast.py: OK.

Co-Authored-By: Claude <noreply@anthropic.com>
Moves the methods named in the card and the constants only they read out of app/main.py unchanged. TasksMixin: run_control, run_tariff_refresh, fresh_tariff_plan and the store-migration grade. tools/maint21_same_ast.py: OK.

Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-21: comments only - trim restated notes, add DECISIONS pointers
checks / unit (pull_request) Successful in 25m17s
infra-pin / fresh (pull_request) Successful in 1s
902da3f723
No code change: tools/maint21_same_ast.py and tools/maint20_same_code.py HEAD~
both print OK, and the -OO code objects are the same.

- Trims 56 comment and docstring lines (2088 -> 2032) in app/main.py and the
  new files that only restated their MAIN-orchestration.md bullet, each cut to
  its first sentence and a pointer. What the cut comments said and the bullet
  did not is now in the bullet: why a SAFETY-02 latch is restored, why the
  SAFETY-30 site check is not armed at one inverter, the SAFETY-18 re-derive,
  the MAINT-05 one-config rule, the OPT-09 sizing rules, the "no plan" row,
  the 5 400 s age bound, and the guard being the only latch repair.
- Adds the missing DECISIONS pointers on the SMA-10, SAFETY-137 and SMA-2
  comments (FALLBACK_BUDGET_S, write_setpoint, _await_inflight,
  _release_last, the retry wait, the failed-release branch, release_zero_sent,
  the in-flight write, the stop_event hand-back, the SMA-2 read and driver).
- DECISIONS/SAFETY-32: the stale `main.py:932-936` now names
  Controller.pack_floor_soc.
- main.py's docstring and import block say where the Controller's parts are.

Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-21 round 2: keep the move pure - revert the added DECISIONS text, restore cut comments
checks / unit (pull_request) Successful in 26m32s
infra-pin / fresh (pull_request) Successful in 1s
58b6fa35f5
Router T241: the comments commit had put new rationale into
MAIN-orchestration.md and cut comment lines that the note at 56fc41f did not
hold.

- DECISIONS/MAIN-orchestration.md is the 56fc41f text plus the one sentence
  that the Controller is split into app/main.py and app/ctl_*.py.
- Comments whose content is not in a DECISIONS note at 56fc41f are back in the
  code verbatim: MAINT-05 one config, the SAFETY-02, SAFETY-42/34,
  SAFETY-30 site half, SAFETY-31 and SAFETY-18 latch blocks in __init__, the
  _grade_store_migration docstring and comment, the trade_power_w docstring,
  the rows.py docstrings.
- Kept: cuts of text already in the note, the SMA-10 / SAFETY-137 / SMA-2
  pointers, the SAFETY-32 line fix.

No code change: maint21_same_ast OK, maint20_same_code -OO same.

Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-21 review: two source scans read the split files too
checks / unit (pull_request) Successful in 31m44s
infra-pin / fresh (pull_request) Successful in 1s
8f0b8477e6
test_safety118's "size rule is gone" and test_validate's "no numeric
maintenance_* fallback" read app/main.py only; the code they guard can now
sit in app/ctl_*.py, where a regression passed unseen. Both read
main_files.source() for main.py now. Tests only; no app change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SAFETY-136: a gentler loop on a slow meter until the last move shows
checks / unit (pull_request) Successful in 26m18s
infra-pin / fresh (pull_request) Successful in 2s
c50e2279dd
On a meter whose readings come 4 s or more apart, the loop runs at half its
gain after a move of 150 W or more, until the meter has fallen by half of the
move or 2 cycles have passed. With readings 10 s late on a 5 s meter the export
falls 44 % over oven, kettle and hob, owner shape, steps; 1-2 s meters are
untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
SAFETY-136 round 2: tests for override and direction, true wording
checks / unit (pull_request) Successful in 26m2s
infra-pin / fresh (pull_request) Successful in 2s
0f9c119bcf
F1 the on-change case is tested as slow, F2 override and direction checks,
F3 the 3.75 s rule in the texts, F4 the quarter figures and the live note.
No behaviour change.

Co-Authored-By: Claude <noreply@anthropic.com>
SAFETY-135: a young 15-minute period does not cost the night charge the whole period
checks / unit (pull_request) Successful in 41m54s
infra-pin / fresh (pull_request) Successful in 2s
66d8777b39
With battery modes on, a grid charge the peak guard (or a measured headroom)
stops in the first 60 s of a period no longer latches off for the period: it
may start again once the period is 60 s old, if the guard is clear and the
headroom is over START_W. A stop after 60 s latches as before. In a young
period no charge starts while the batteries' give is still falling (over
START_W within 15 s), since the meter may not show that drop yet. The guard
itself is unchanged, so a real over-aim load is cut on the same cycle.

DECISIONS/SAFETY-135-young-period.md, test_safety135.py.

Co-Authored-By: Claude <noreply@anthropic.com>
SAFETY-135 round 2: the first guard stop in a period may restart once; tests and docs
checks / unit (pull_request) Successful in 29m42s
infra-pin / fresh (pull_request) Successful in 1s
4066624cce
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
MAINT-24: a started maintenance charge gets the cheap night
checks / unit (pull_request) Failing after 41m45s
infra-pin / fresh (pull_request) Successful in 2s
54a738dd87
While a pack is in its maintenance CHARGE phase the mode trade files no grid
charge for the other packs (why "cycle", mirror of SAFETY-78). The maintenance
shaping check and charge floor aim at peakguard.quarter_target_w, month peak
included (SAFETY-80). Tests through cycle(): test_maint24.py.

Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-24: keep the refusal probe beside a maintenance charge
checks / unit (pull_request) Failing after 45m44s
infra-pin / fresh (pull_request) Successful in 2s
0f5e513f1d
The mode trade still lets a SAFETY-73 probe (PROBE_W alone) go on while a pack
is in its maintenance CHARGE phase; test_opt20.py g7 pins it. ch_ch_maint
digest moved (B no longer grid-charges beside A's charge).

Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-24: test_maint24.py points at MUTATION.md
checks / unit (pull_request) Successful in 28m32s
infra-pin / fresh (pull_request) Successful in 1s
b44a648cab
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-24: read the last row without a bare index (test_named_absence)
checks / unit (pull_request) Successful in 26m37s
infra-pin / fresh (pull_request) Successful in 1s
6004bfad9a
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-24: test_peak_margin pins the maintenance checks to the guard's aim
checks / unit (pull_request) Successful in 29m32s
infra-pin / fresh (pull_request) Successful in 1s
b80cfa969f
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-25: the Home plan shows no cycle during the 7-day back-off
checks / unit (pull_request) Successful in 27m21s
infra-pin / fresh (pull_request) Successful in 1s
de0790e62d
homeview.maintenance_runs() ignored backoff_until(); it drew a cycle every
night of the wait. Now no run and a plain line (EN, NL). test_maint25.py.

Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-25: clean test_maint25.py, split its DECISIONS note
checks / unit (pull_request) Successful in 26m39s
infra-pin / fresh (pull_request) Successful in 1s
33cfc733d3
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-28: during a real peak the maintenance charge stays paused until the house is clear of the aim
infra-pin / fresh (pull_request) Successful in 2s
checks / unit (pull_request) Failing after 1h10m11s
8cd10ee6aa
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-28: the hold is for N=2 and up (SAFETY-87 N=1 pins), N=1 peak digest test
checks / unit (pull_request) Failing after 36m51s
infra-pin / fresh (pull_request) Successful in 2s
4f9aa5223f
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-28: reset the hold every cycle, test it; digests vs 1b5f7c9; DECISIONS numbers and mutants
checks / unit (pull_request) Failing after 36m49s
infra-pin / fresh (pull_request) Successful in 2s
92f0387415
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-28: hold only where MAINT-24 moved the aim (banked peak above the cap); test_safety119 pins stay
checks / unit (pull_request) Failing after 34m27s
infra-pin / fresh (pull_request) Successful in 2s
8640bfcbc1
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-28: test_maint28 points at MUTATION.md (test_bytecode)
checks / unit (pull_request) Failing after 34m49s
infra-pin / fresh (pull_request) Successful in 1s
23359a047d
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-28: keep the single shaping_suspended(.., maint_aim) call site (test_peak_margin pin); shaping_held takes its answer
checks / unit (pull_request) Successful in 32m46s
infra-pin / fresh (pull_request) Successful in 1s
edf30f345a
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-28: N=1 digest test with a banked peak (so the N=1 gate is tested)
checks / unit (pull_request) Successful in 34m0s
infra-pin / fresh (pull_request) Successful in 2s
bc5be4964e
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-28: re-pin test_safety84 n2_charge_banked (the charge no longer restarts in the 5000 W load)
checks / unit (pull_request) Successful in 43m17s
infra-pin / fresh (pull_request) Successful in 2s
f5bd2a3520
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-28: test_maint28 reads the replay clock directly (main_files.live parsed every app file per cycle): 269 s -> 28 s, same numbers
checks / unit (pull_request) Successful in 42m47s
infra-pin / fresh (pull_request) Successful in 2s
7edf8484d1
Co-Authored-By: Claude <noreply@anthropic.com>
MAINT-28: DECISIONS gives the measured reason for the uncapped give (review M-1); ctl_cycle comment one line + pointer (L-1)
checks / unit (pull_request) Successful in 27m38s
infra-pin / fresh (pull_request) Successful in 2s
e7706150e8
Co-Authored-By: Claude <noreply@anthropic.com>
Revert "MAINT-28: during a real peak the maintenance charge stays paused (#247)"
checks / unit (pull_request) Successful in 27m32s
infra-pin / fresh (pull_request) Successful in 2s
7bc8ad264b
Takes PR #247 (f3f9483) out of release/1.0 so the 19 Nov release ships
MAINT-24 + MAINT-25 without MAINT-28. The rig failed f3f9483
(reports/MAINT-28-test-report.md) and the fix-forward PR #248 review found
a High regression (reports/MAINT-28-fix-review-report.md). MAINT-28
continues on PR #248 after the release.

Co-Authored-By: Claude <noreply@anthropic.com>
Release 0.22.0: version and CHANGELOG
checks / unit (pull_request) Successful in 36m47s
infra-pin / fresh (pull_request) Successful in 1s
94214d609e
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
Release 0.22.0: CHANGELOG fixes (duplicate Known issues heading, missing full stop)
checks / unit (pull_request) Successful in 27m4s
infra-pin / fresh (pull_request) Successful in 1s
634001cb26
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bdqr7PiaHcBnzPituu2yTC
Release 0.22.0: CHANGELOG wording (SAFETY-136 row, SAFETY-111, MAINT-28, MAINT-26)
checks / unit (pull_request) Successful in 26m33s
infra-pin / fresh (pull_request) Successful in 2s
e79d403560
- SAFETY-136 row: the gain is at 10 s late; at 15 s a rig test sent more.
- SAFETY-111: the cycle needs more than 4 nights; the 36 h limit ends it.
- MAINT-28: rig counts were 393-468 flips per 45 min, not one figure.
- MAINT-26 / MAINT-25: the next try is the first start hour after the wait.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merge pull request 'Release 0.22.0' (#250) from release-0.22.0 into release/1.0
infra-pin / fresh (push) Successful in 2s
checks / unit (pull_request) Successful in 26m8s
infra-pin / fresh (pull_request) Successful in 1s
b700c0be92
Release 0.22.0 (#250)
admin merged commit 10ee2baf91 into master 2026-11-14 15:58:34 +01:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/goodwe-addon#251