SAFETY-10: the whole decision path runs and nothing reaches the inverter
checks / unit (pull_request) Successful in 7m58s
checks / unit (pull_request) Successful in 7m58s
Every optimiser defect this project has found was invisible from outside while it was happening, and each was paid for at the meter first: SAFETY-45's 28 charge onsets an hour against a binding peak_cap_w, TARIFF-14's wrong quarter-hour for 45 minutes in every 60, SAFETY-46's 598 Wh spent to gain 310 Wh. There was no way to watch what the optimiser WOULD do without letting it do it. `shadow_mode` runs strategies, arbiter, clamps and guards, records the decision with its reason, and commands nothing. ⚠️ IT WRITES. It writes 0 W, on the ordinary heartbeat, through the ordinary single writer. The inverter holds its last command forever - it has no meter timeout - so "commands nothing" implemented as "stops writing" would leave whatever was standing when shadow mode came on standing for as long as it was left on, under a page saying nothing is being commanded. That is the failure this repository keeps finding. Refreshing 0 W is also what firmware/goodwe-master.yaml's 30 s stale-input watchdog requires of us; going quiet would keep that board tripped for the whole run, which is an alarm being used as a mode. ⚠️ IT IS NOT A SECOND WRITE PATH. No claim, no transport, no arithmetic of its own. It is a substitution inside Controller._write_unit() - the single choke point every write already reaches - in the same form as the three above it, and it is the LAST of them, so SAFETY-28's gate and SAFETY-03's clamp still grade the number the decision path produced. ⚠️ THE DEFAULT IS `auto`, NOT `true`, AND THAT IS THE CARD'S REAL CONTENT. The Supervisor composes options as {**config.yaml defaults, **stored user options}, so a new key with a new default reaches EVERY EXISTING INSTALL on the next update - including the one regulating a real battery. A boolean default-on would have stopped it silently. There is no mechanism by which one key can default differently for a new install and an existing one, so "default-on for new" is a value the add-on RESOLVES: `auto` is shadow while control is STOPPED and live once it is STARTED. A fresh install ships `auto_start: false` and comes up observing; an install that is controlling is started and does not change by one watt. THE FAILSAFES. Sign watchdogs (SAFETY-02/30): INERT - they grade Inverter.standing_w(), which shadow mode holds at 0 W. Fed the shadow decision they would disagree with a meter that never saw it and LATCH A TRIP THAT SURVIVES INTO LIVE OPERATION AND A RESTART. Clamp (SAFETY-03), write gate (SAFETY-28), meter freeze (SAFETY-18), peak guard: OBSERVE. Circulation (SAFETY-31) and delivery (SAFETY-42): observe and may legitimately latch, because their evidence is measured power. The operator stop is the one deliberate change: enforced at the wire rather than as a P_SAFETY claim, which would otherwise make every recorded decision read "stopped". ⚠️ RECORDED LIMITATION: _shadow_settle() re-seeds the carried command to what stands on the wire, which makes the exit safe by construction - and therefore the loop leg's shadow decision is ONE SLEW STEP, not a trajectory. A shadow run answers "what would it command now", not "what would it have done next". The strategies are unaffected; all three defects above are visible from one. Graded on the writes and on the rendered surface, never on the flag: 22 mutations, no survivors, including one that lets a write escape and one that goes silent instead of commanding 0 W. Closed loop on TEST-23 stage 3's rig, same hour twice: live drives the pack to -2000 W and SoC 50.000 -> 62.331 %; shadow commands 0 W throughout, SoC stays 50.0 exactly, and the arbitrage decision is identical in action, instant, signed power, reason and arbiter winner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BwRLw7eKYj2byVdwB6kbjU
This commit is contained in:
@@ -601,6 +601,94 @@ behind it would trip the firmware watchdog on a system that is working fine.
|
||||
| `site_sign_disagree_w` | 300 | **Two or more inverters only.** Threshold for the site-wide check - the sum of the commands against the meter. The same 300 W as `sign_disagree_w`, for the same reason: near zero the sign is noise |
|
||||
| `site_sign_disagree_s` | 325 | **Two or more inverters only.** How long the site's total command and the meter must stay opposed before every inverter is parked. Longer than `sign_disagree_s` because the command has to cross zero over a rail that is the sum of the roster, at a `slew_w` that is not - measured at 110 s with one inverter and 215 s with two |
|
||||
| `delivery_hold_s` | 185 | **Two or more inverters only.** How long one inverter may stand at a discharge share it is not producing - short by more than `saturation_w`, with no reason this add-on can read - while at least one sibling is **not** in that state, before it is named. **It reports; it does not act.** Nothing is commanded differently and the site goes on running short by that unit's share |
|
||||
| `shadow_mode` | `auto` | **Run every decision and command nothing.** `auto` = shadow mode while control is stopped, ordinary control once you press Start; `on` = shadow mode always; `off` = ordinary control. See *Shadow mode* below |
|
||||
|
||||
#### Shadow mode (`shadow_mode`, SAFETY-10)
|
||||
|
||||
Shadow mode runs the **whole** decision path — the price strategy, the capacity
|
||||
guard, the maintenance shaping, the grid-following loop, the arbiter that ranks
|
||||
them, SAFETY-28's authorisation gate and SAFETY-03's clamp — and **sends nothing
|
||||
to the inverter**. It is how you find out what this add-on *would* do on a site
|
||||
before you let it do it.
|
||||
|
||||
Every defect in the optimiser that this project has found was invisible from
|
||||
outside while it was happening: a price lookup answering for the wrong
|
||||
quarter-hour, a trade against a binding capacity cap that ran 28 charge onsets
|
||||
in an hour, an SoC ceiling churning 598 Wh to gain 310 Wh. Each one was paid for
|
||||
at the meter first and noticed afterwards. Shadow mode is the surface that
|
||||
would have shown all three on the day.
|
||||
|
||||
**What "commands nothing" means, exactly.** Every inverter is held at **0 W**,
|
||||
written by the ordinary single writer and refreshed on the ordinary heartbeat.
|
||||
It is not silence. ⚠️ **The inverter holds its last command forever** — it has
|
||||
no meter timeout — so a mode that simply stopped writing would leave whatever
|
||||
was standing when you switched it on standing for as long as you left it on,
|
||||
under a page saying nothing was being commanded. Refreshing 0 W is also what
|
||||
the ESP32's 30 s stale-input watchdog requires of us: going quiet would put
|
||||
that board into a *tripped watchdog* state for the whole run, which is an alarm
|
||||
being used as a mode.
|
||||
|
||||
**The three values.**
|
||||
|
||||
| value | what happens |
|
||||
|---|---|
|
||||
| `auto` | **the shipped default.** Shadow mode while control is **stopped**; ordinary control once control is **started**. A new install ships `auto_start: false`, so it comes up in shadow mode and observes before it ever commands anything. An install that is already controlling is started, so nothing about it changes |
|
||||
| `on` | shadow mode **always**, whatever the *Start / stop control* button says. This is the one to pick to watch an armed site for a day — the loop runs, arbitrage decides, the peak guard files its claims, and the battery does not move |
|
||||
| `off` | ordinary control |
|
||||
|
||||
⚠️ **Existing installs are not changed.** The default is `auto` rather than a
|
||||
boolean `true` precisely because a boolean would have changed them: the
|
||||
Supervisor composes an add-on's options as *its defaults, overlaid with your
|
||||
stored options*, so a new key with a new default reaches every install on the
|
||||
next update. `shadow_mode: true` would have silently stopped a battery that is
|
||||
regulating today, and the only symptom would be a battery that stopped doing
|
||||
anything.
|
||||
|
||||
⚠️ **Under `auto`, pressing *Start* leaves shadow mode.** That is the point of
|
||||
`auto` — the button means "control the battery". Set `on` if you want the
|
||||
decisions recorded while the loop runs.
|
||||
|
||||
**What you get out of it.** The status page carries a red-amber banner saying
|
||||
the battery is not being controlled, a `Shadow mode` row saying which rung
|
||||
decided, what it **would have commanded** and why, and the `Why` row with the
|
||||
arbiter's own sentence. The action log gets one line each time the decision
|
||||
changes — the winner or the watts — rather than one per cycle, so a 40-row log
|
||||
still contains the context of what it found. MQTT reports `status: shadow`.
|
||||
|
||||
**The failsafes.** The sign watchdogs (SAFETY-02/30) are **inert**: they grade
|
||||
what this controller has standing on the wire, and that is 0 W, so they cannot
|
||||
arm. That is deliberate and it is the single most important decision in this
|
||||
feature — fed the shadow decision instead they would disagree with a meter that
|
||||
never saw it, and **latch a trip that survives into live operation and a
|
||||
restart**. The clamp (SAFETY-03), the write-authorisation gate (SAFETY-28), the
|
||||
frozen-meter alarm (SAFETY-18) and the capacity guard all **observe**: they run
|
||||
on the number the decision path produced, so a shadow run can tell you the
|
||||
clamp would have engaged. The circulation (SAFETY-31) and delivery (SAFETY-42)
|
||||
watchdogs observe and may legitimately latch, because their evidence is
|
||||
*measured* power, not a command. The operator stop is enforced at the wire
|
||||
instead of as a claim, so that the recorded decision is the real one.
|
||||
|
||||
⚠️ **A latch that was already up stays up.** Shadow mode does not clear
|
||||
anything. The banner names any standing latch and goes red for it, because a
|
||||
latch that is up while the controller is inert is up the moment it stops being
|
||||
inert.
|
||||
|
||||
⚠️ **Maintenance is unchanged.** The maintenance state machines are ticked on
|
||||
every cycle whether control is started or not, so they already run against a
|
||||
battery that does not move on a stopped install. Shadow mode inherits that
|
||||
exactly; it neither improves it nor makes it worse.
|
||||
|
||||
⚠️ **What a shadow run will NOT tell you.** The controller's carried command is
|
||||
re-seeded to the 0 W standing on the wire at the end of every shadow cycle —
|
||||
that is what makes turning shadow mode off safe, because the loop then resumes
|
||||
from where the battery actually is. The consequence is that the
|
||||
**grid-following loop's** recorded decision is **one slew step** and stays
|
||||
there: a shadow run answers *"what would it command now"*, not *"what
|
||||
trajectory would it have followed"*, because the trajectory depends on a
|
||||
battery responding and none did. The **strategies** are unaffected — price
|
||||
arbitrage, the capacity guard and maintenance all ask for a fixed power from
|
||||
inputs that do not depend on what the battery did — so a shadow run does show
|
||||
you the trades, the peak-guard engagements and the SoC-limit behaviour.
|
||||
|
||||
#### The sign-disagreement failsafe
|
||||
|
||||
|
||||
Reference in New Issue
Block a user