OPT-11 P3: price runs per battery
checks / unit (pull_request) Successful in 13m45s
infra-pin / fresh (pull_request) Successful in 1s

Cheap and expensive hours as runs of consecutive firm price slots
(priceruns.py); each battery's role per tick from its mode (modetrade.py):
grid charging for Leftover solar / Cheap hours only, never Solar first;
holding for the expensive runs; selling per battery (pack_sell) above the
spread and the floor. The site trade is sized inside the modes (loop bound)
and the peak guard's headroom; a run edge reseeds the loop. Cheap hours only
and selling are enabled on the Batteries screen and in the HA select
(appended). The site Why row no longer says "held back" when the peak limit
pushes power up (F2). Modes off: byte-identical to 7a00f12 on the trade path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Claude dev agent
2026-09-29 02:37:44 +02:00
parent 7a00f12c76
commit 902524ae5e
19 changed files with 2202 additions and 113 deletions
+12 -8
View File
@@ -996,7 +996,8 @@ check("modes off: every configuration announces byte-for-byte what ed739d4 "
MODES = packmode.MODES
ON1, SUB1 = announce_p2(["inverter-a"], modes=MODES)
m, c, a = report("roster of one, modes on", ON1)
_opt = '["Solar first", "Leftover solar"]'
# P2 announced the first two; OPT-11 P3 appended "Cheap hours only".
_opt = '["Solar first", "Leftover solar", "Cheap hours only"]'
MODE_N1 = [
("homeassistant/select/goodwe_ctl_unit_inverter_a_mode/config",
'{"name": "Inverter A Mode", "object_id": "goodwe_inverter_a_mode", '
@@ -1062,16 +1063,19 @@ check("each HA mode option is the page's English name for that mode, in "
"both places the page names it",
all(mqtt.MODE_LABELS[k] == _en["home_words"]["modes"][k]
== _en["home"]["batteries"]["choice"][k]["name"] for k in MODES))
_later = dict(mqtt.MODE_LABELS, cheap_hours="Cheap hours only")
# OPT-11 P3 added `cheap_hours`: against P2's two modes (7a00f12), only the
# options list changes, by one entry at its end; no topic, id or label moves.
_p3 = mqtt.mode_payloads(NAMES2, MODES)
_orig = mqtt.MODE_LABELS
mqtt.MODE_LABELS = _later
mqtt.MODE_LABELS = {k: v for k, v in _orig.items() if k != "cheap_hours"}
try:
_p3 = mqtt.mode_payloads(NAMES2, MODES + ("cheap_hours",))
_p2 = mqtt.mode_payloads(NAMES2, MODES[:2])
finally:
mqtt.MODE_LABELS = _orig
_p2 = mqtt.mode_payloads(NAMES2, MODES)
check("...(simulated P3: adding `cheap_hours` changes only the options list, "
"by one entry at its end)",
check("...P3 appended `cheap_hours`: against P2's list only the options "
"change, by one entry at the end, and P2's two labels are unchanged",
MODES[:2] == ("solar_first", "leftover_solar")
and _p2[0][1]["options"] == ["Solar first", "Leftover solar"] and
[t for t, _ in _p3] == [t for t, _ in _p2]
and all({k: v for k, v in x.items() if k != "options"}
== {k: v for k, v in y.items() if k != "options"}
@@ -1261,7 +1265,7 @@ BAD = [
(f"{T_A}/use/set", b"true"), (f"{T_A}/use/set", b"1"),
(f"{T_A}/use/set", b""),
(f"{T_A}/mode/set", b"solar_first"), # a key, not the option
(f"{T_A}/mode/set", b"Cheap hours only"), # not offered before P3
(f"{T_A}/mode/set", b"Sell only"), # not an offered mode
(f"{T_A}/mode/set", b"solar first"),
(f"{T_A}/mode/set", "Sοlar first".encode()), # Greek omicron
(f"{T_A}/mode/set", b"Solar first\x00"),