Merge release/1.0 into SAFETY-18 - CHANGELOG only
MAINT-04 (#10) landed on release/1.0 mid-review, so both branches had prepended a stanza. config.yaml and DOCS.md auto-merged clean; only the CHANGELOG needed a hand. Both stanzas are kept verbatim, MAINT-04's Unreleased above the new 0.5.2 - no text from either author edited. MAINT-04 is unreleased and 0.5.2 is being cut here, so a release manager may want to fold the two; that is an editorial call, not a reviewer's. Full suite green on the merged tree: arbiter, control, loop, maintenance, p1, validate, watchdog, freeze. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015VXjA3LWccN1rJsk3okyPC
This commit is contained in:
@@ -1,5 +1,86 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
**Cheap-window maintenance charging was unreachable at the shipped defaults, and
|
||||
now the config that makes it unreachable is refused (MAINT-04).** `config.yaml`
|
||||
shipped `max_w: 2000` against `maintenance_charge_w: 2500`. The cheap-window
|
||||
shaping files `Claim.limit(hi = -maintenance_charge_w)` against the supervised
|
||||
SAFETY limit's `lo = -max_w`, so `lo > hi` — the arbiter's clause 3, correctly
|
||||
resolved to **0 W** and logged as a CONTRADICTION. **Maintenance silently never
|
||||
charged in a cheap window on a default install**, and the only evidence read
|
||||
like a bug in the arbiter rather than like the two numbers an operator set.
|
||||
Measured against the real arbiter, loop asking +500 W:
|
||||
|
||||
```
|
||||
max_w=2000 hi=-2500 -> +0 W contradiction=True
|
||||
max_w=2500 hi=-2500 -> -2500 W limited by supervised(max_w)
|
||||
max_w=3000 hi=-2500 -> -2500 W limited by maintenance(cheap-window charge)
|
||||
```
|
||||
|
||||
`validate_startup_config()` now refuses `maintenance_charge_w > max_w` (rule 6),
|
||||
the same shape as CFG-01's `sign_disagree_w >= max_w`: a number the operator set,
|
||||
overridden by a layer they cannot see. Equality is reachable (`-max_w` is a
|
||||
single valid point) and deliberately not refused. Verified by starting the
|
||||
add-on on the pre-fix pair: exit 78, the refusal banner, no traceback.
|
||||
|
||||
**`maintenance_discharge_w` was checked, not assumed, and it is NOT the same
|
||||
case.** The shaping is not symmetric. The drain phase does not shape the loop,
|
||||
it OWNS the actuator — `Claim.set(P_MAINTENANCE, +discharge_w)` — and a SET is
|
||||
*clamped* by the limits above it rather than contradicted by them. 2500 against
|
||||
a 2000 W ceiling emits **+2000 W**, and the arbiter's own `explain()` prints
|
||||
`maintenance -> 2000 W, limited by supervised(max_w)` every cycle. Nothing is
|
||||
silent and energy still moves; the cycle completes slower (13.8 kWh usable at
|
||||
2000 W is ~6.9 h against the 720 min drain timeout). **Warned about, not
|
||||
refused.**
|
||||
|
||||
**The shipped defaults changed so the validator does not reject them.**
|
||||
`maintenance_charge_w` and `maintenance_discharge_w` are now **2000**, at the
|
||||
shipped `max_w`. `max_w` was deliberately NOT raised instead: it is the
|
||||
supervised safety ceiling and raising a default upward ships more power to every
|
||||
new install. Raising `max_w` (the reference install runs 5000) is what unlocks a
|
||||
faster maintenance cycle — raise it first, then these. `test_validate.py` now
|
||||
reads `config.yaml` itself and asserts the shipped numbers pass the rule, so a
|
||||
default that the validator refuses cannot ship again.
|
||||
|
||||
### ⚠️ Upgrading an existing default install: the add-on may refuse to start
|
||||
|
||||
An install still carrying `maintenance_charge_w: 2500` against `max_w: 2000`
|
||||
now meets rule 6 and **exits 78 with the refusal banner** instead of starting.
|
||||
That is the intended behaviour — the configuration was already broken, it was
|
||||
just silent about it — but an add-on that will not come back after an update
|
||||
needs the fix stated here rather than only in the log.
|
||||
|
||||
**The fix, either way: set `maintenance_charge_w` to `2000` or lower, OR raise
|
||||
`max_w` to `2500` or higher** (Settings → Add-ons → GoodWe RS485 Controller →
|
||||
Configuration), then start it. The banner in the log says the same thing and
|
||||
names both numbers.
|
||||
|
||||
Whether you hit this at all depends on whether the Supervisor kept the old
|
||||
value, and **both cases are covered above because we did not verify which one
|
||||
applies here.** The Supervisor merges an add-on's *stored* options over the
|
||||
defaults in `config.yaml`, so:
|
||||
|
||||
- **A key the operator never edited** re-derives from the new `config.yaml` and
|
||||
becomes 2000 on upgrade. Non-event, nothing to do.
|
||||
- **A key the operator did set** persists at 2500 and the add-on refuses to
|
||||
start until it is changed. Note this includes anyone who used the
|
||||
Configuration page's YAML editor, which writes the whole options block back —
|
||||
so "I never touched that field" is not a guarantee the old value is gone.
|
||||
|
||||
⚠️ **This is reasoned from the Supervisor's documented merge behaviour, not
|
||||
measured on the live install.** The reference install's stored options could not
|
||||
be read (`/api/hassio/` refuses a long-lived token) and probing it was out of
|
||||
scope — it regulates a real battery. It runs `max_w: 5000` and is unaffected
|
||||
either way.
|
||||
|
||||
⚠️ **Known, deliberately left, and worth a follow-up:** `main.py` and
|
||||
`maintenance.py` still fall back to **2500** when the key is absent from the
|
||||
options dict, while `config.yaml` and `validate.py` now say 2000. Unreachable on
|
||||
a Supervisor install (every key is always supplied) and reachable only from
|
||||
tests/`sim`. The real fix is one source of truth for option defaults, which is
|
||||
bigger than this ticket and touches files another ticket is in.
|
||||
|
||||
## 0.5.2
|
||||
|
||||
**A meter frozen at a wrong-but-fresh value now raises an alarm. It still is not
|
||||
|
||||
Reference in New Issue
Block a user