Merge release/1.0 (0.15.0 + SAFETY-90) into maint-10-local-start-hour
Conflicts: main.py's maintenance import (base added CHARGE, HOLD; branch added site_zone) - both kept. DOCS maintenance table: base's longer drain-rate row kept, start hour row at 19 / local. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+77
-16
@@ -184,6 +184,9 @@ The label and description each setting shows on the Configuration tab, in tab or
|
||||
| Hardware · Inverter list | Usable battery capacity (kWh) (optional) | `inverters.capacity_kwh` | This battery's usable capacity from its datasheet. Fill it in on every inverter or on none. The add-on then shares the work by the energy each battery has left. |
|
||||
| Hardware | RS485 emergency stop is fitted | `estop_fitted` | Switch this on if the emergency stop is installed. It only changes a warning on the status page. |
|
||||
| Hardware | Battery modes | `battery_modes_enabled` | Give each battery an on and off switch and a mode, set on the Batteries screen. |
|
||||
| Hardware | Solar forecast, today (kWh) | `pv_forecast_entity` | Optional. A forecast sensor with today's remaining solar energy, such as Forecast.Solar or Solcast. A battery then buys only what the sun will not give. |
|
||||
| Hardware | Solar forecast, tomorrow (kWh) | `pv_forecast_tomorrow_entity` | Optional. The same forecast for tomorrow. It helps plan a cheap hour before midnight. |
|
||||
| Hardware | Trust in the forecast (%) | `solar_trust_pct` | Forecasts are often too sunny. At 70 % the add-on plans with 7 of every 10 kWh forecast. |
|
||||
| Hardware | Solar production entity (W) | `pv_entity` | Optional. Your solar inverter's power sensor, positive while producing. The add-on records it and does not use it for control. |
|
||||
| Hardware | Solar production entities, per inverter (W) | `pv_entities` | Optional. Use one row per solar inverter instead of “Solar production entity” when you have several. If one stops reporting, no total is recorded. |
|
||||
| Hardware | Reversed-sign solar entities | `pv_invert_entities` | Optional. Add each solar entity above that reads negative while producing, spelled the same way. Its sign is flipped before adding up. |
|
||||
@@ -522,7 +525,7 @@ Off by default. Turn it on, restart the add-on, and the Batteries screen (and Se
|
||||
- **On or off.** Off asks the battery for 0 W and starts no monthly cycle on it. A cycle that is running stops, and the add-on log says so. The peak limit may still use it, but only for what the other batteries cannot cover.
|
||||
- **Mode.**
|
||||
- *Solar first* charges from spare solar before the other batteries and covers the house at any hour. It never charges from the grid and never sells.
|
||||
- *Leftover solar* charges from the solar the others cannot take, never from the grid (until the solar forecast arrives). It saves its power for the expensive hours and covers the house then.
|
||||
- *Leftover solar* charges from the solar the others cannot take. With a solar forecast it may also charge from the grid, but only what the sun will not give (see below). It saves its power for the expensive hours and covers the house then.
|
||||
- *Cheap hours only* charges from the grid in the cheapest hours, saves its power for the expensive hours and covers the house then.
|
||||
- **Selling** is off until it can be judged on the price the grid pays (owner decision 2026-09-29, option A).
|
||||
|
||||
@@ -535,6 +538,16 @@ With battery modes on, these choices replace the *Charging and selling on price*
|
||||
- A carried (not yet published) price is never used. A run that has started runs to its end.
|
||||
- With no prices, or too small a gap, every battery follows the house.
|
||||
|
||||
**Solar forecast** (`pv_forecast_entity`, optional). A Home Assistant sensor with the solar energy still to come today, in kWh. These work: Forecast.Solar (`sensor.energy_production_today_remaining`), Solcast PV Forecast (`sensor.solcast_pv_forecast_forecast_remaining_today`) and Open-Meteo Solar Forecast (`sensor.energy_production_today_remaining`). Set `pv_forecast_tomorrow_entity` to the matching *tomorrow* sensor to plan a cheap run before midnight.
|
||||
- Before each cheap run the add-on asks: will the sun fill the batteries before the expensive hours? It counts on `solar_trust_pct` of the forecast (70 %), less what the house uses. Solar first batteries get that sun first.
|
||||
- *Leftover solar* then charges from the grid only the shortfall: what the house will draw from it in the expensive hours, less its share of the sun. Not up to the top level.
|
||||
- *Cheap hours only* ignores the forecast and fills to the top level.
|
||||
- *Solar first* never charges from the grid.
|
||||
- The house use is the add-on's own measurement (grid + battery power) per hour, over the last 7 days. It needs one day of it before the first plan.
|
||||
- The plan is checked every 15 minutes. A planned level moves only by 1 kWh or 10 %, so a wobbling forecast does not flip it.
|
||||
- No forecast, one older than 3 hours or from before midnight, or no house use yet: *Leftover solar* takes solar only and *Cheap hours only* fills to the top level. The Batteries screen says so.
|
||||
- A battery needs its capacity (`inverters.capacity_kwh`) for a plan.
|
||||
|
||||
**What always comes first.**
|
||||
- The peak limit, the safety stops and the monthly cycle come before a battery's mode.
|
||||
- A grid charge is sized so the 15-minute period stays under the peak limit less its margin (`peak_margin_w`). It starts only at 500 W or more. Once the peak limit stops it, it stays off for the rest of that 15-minute period.
|
||||
@@ -564,7 +577,7 @@ accumulator is doing:
|
||||
| `Loop integrator lead` | W | that value minus **the command it was computed against**, i.e. the command the previous cycle left standing |
|
||||
| `Loop reason` | — | the law's own word for this cycle |
|
||||
|
||||
`Loop reason` takes one of ten values:
|
||||
`Loop reason` takes one of eleven values:
|
||||
|
||||
| value | what happened |
|
||||
|---|---|
|
||||
@@ -575,8 +588,9 @@ accumulator is doing:
|
||||
| `slew-limited` | the command could not move as far as the integrator asked for; `slew_w` is what stopped it |
|
||||
| `saturated-freeze` | the inverter is not following, so the command's magnitude may fall but not rise |
|
||||
| `freeze-released` | the freeze had run out of plant-shaped explanations and the command was let move again |
|
||||
| `mode-bound` | the batteries' limits set the command, not the meter: their battery modes, or, with two or more inverters, a battery in its monthly maintenance |
|
||||
| `not-running` | control is stopped **and** shadow mode is off, so the loop did not run |
|
||||
| `battery refuses` | the battery gave less than 100 W for 12 cycles (a minute at a 5-second meter) while asked for more than 500 W, so the add-on asks 0 W in that direction (see below) |
|
||||
| `battery refuses` | the battery gave less than 100 W for 12 cycles and at least 22 s (a minute at a 5-second meter) while asked for more than 500 W, so the add-on asks 0 W in that direction (see below) |
|
||||
| `battery probe` | the add-on asks 200 W to test whether a refusing battery gives power again |
|
||||
|
||||
**When the battery refuses.** At its cut-out level the battery gives nothing,
|
||||
@@ -586,7 +600,11 @@ request, and the `Why` row reads *The battery refuses to give power: 0 W*. Every
|
||||
the battery gives 100 W or more, when the grid needs charging instead, and when
|
||||
control is stopped or started. 200 W is below the 250 W the stale-reading check
|
||||
needs, so a test never trips it. A battery that gives some power, even 100 W,
|
||||
is not refusing and keeps its request.
|
||||
is not refusing and keeps its request. Nor is a battery whose reading is
|
||||
still moving towards the request, such as one turning from charging to
|
||||
giving (SAFETY-94). "Gives" is counted from what the battery's reading shows
|
||||
at rest, which the add-on learns after two quiet minutes and keeps: some
|
||||
inverters show about -100 W at rest (SAFETY-97).
|
||||
|
||||
**With two or more inverters** the same rule works per inverter. An inverter
|
||||
whose battery gives less than 100 W for 12 cycles, and at least 22 s, while
|
||||
@@ -1626,7 +1644,7 @@ help - see "Why the tuning is what it is".
|
||||
| `maintenance_enabled` | false | Enable the monthly cycle |
|
||||
| `maintenance_interval_days` | 28 | Minimum gap between cycles |
|
||||
| `maintenance_start_hour` | 19 | Local hour a due cycle begins (see below) |
|
||||
| `maintenance_discharge_w` | 2000 | Drain rate (exports the surplus). Above `max_w` it is **clamped** to `max_w` — the drain phase SETs the setpoint and the arbiter bounds it, saying so on the status page (`limited by supervised(max_w)`). Warned about, not refused: the cycle still completes, just slower |
|
||||
| `maintenance_discharge_w` | 2000 | Drain rate (exports the surplus). The peak guard may ask for more (SAFETY-83; at N≥2 while another battery is at its peak reserve, SAFETY-87), and at N≥2 so may the house while another battery is at its peak reserve (SAFETY-91). Above `max_w` it is **clamped** to `max_w` — the drain phase SETs the setpoint and the arbiter bounds it, saying so on the status page (`limited by supervised(max_w)`). Warned about, not refused: the cycle still completes, just slower |
|
||||
| `maintenance_charge_w` | 2000 | Charge ceiling, capped again by peak headroom. Must stay **at or below `max_w`** or the cheap-window shaping (`hi = -maintenance_charge_w`) contradicts the supervised limit (`lo = -max_w`), the arbiter resolves it to **0 W**, and maintenance silently never charges in a cheap window — **the add-on refuses to start** |
|
||||
| `maintenance_soc_floor` | 11 | Drain target — stay just above the inverter's own floor. ⚠️ **This is not `peak_soc_floor`.** It is the depth the monthly recalibration deliberately goes to, once, on purpose; `peak_soc_floor` is the depth a money-saving discharge may never reach on any day. The right values differ (11 vs 20 as shipped), which is why they are two keys |
|
||||
| `maintenance_soc_target` | 99 | Charge target |
|
||||
@@ -1665,6 +1683,20 @@ maintenance schedule.
|
||||
With two or more batteries, the one being emptied gives its drain rate and the
|
||||
others only add power when the house needs more; none charges while it drains
|
||||
(SAFETY-78). Below the drain rate the rest is exported, as with one battery.
|
||||
If the house would still go over the peak limit, the battery being emptied
|
||||
gives more than its drain rate, with one battery too (SAFETY-83).
|
||||
A battery at its peak reserve gives last: the others give their full power
|
||||
first, and the battery being emptied rises above its drain rate before it
|
||||
(SAFETY-91).
|
||||
In a cheap window the battery being filled charges and the others only add
|
||||
charge; none gives while it charges. The charge is cut to what keeps the
|
||||
15-minute peak at the limit minus `peak_margin_w` (SAFETY-84).
|
||||
While a full battery rests after its charge (the hold), it gives just enough
|
||||
to keep the 15-minute peak at that aim if the house needs it; with two
|
||||
batteries the other one gives first. The hold still ends on its clock
|
||||
(SAFETY-86).
|
||||
When a charge ends, the other batteries take over one slew step a cycle,
|
||||
and none starts charging from the grid (SAFETY-93).
|
||||
|
||||
#### Capacity-tariff exposure of the grid-following loop
|
||||
|
||||
@@ -1734,10 +1766,17 @@ ceiling at the cap, so a broken instrument makes the guard stricter, never loose
|
||||
**When it cannot see, it forbids importing.** Three states are not evidence and
|
||||
are checked before the projection is graded: no open quarter (a fresh averager
|
||||
projects a confident 0 W, which is under every ceiling there is), a meter gap
|
||||
longer than `meter_max_age_s`, and SAFETY-18's frozen-meter conjunction. In all
|
||||
three the claim becomes a floor of **0 W**. That is the backstop for the frozen
|
||||
meter: the alarm arrives at `meter_freeze_s` (300 s) of a 900 s billing quarter
|
||||
and commands nothing, and this stops the import.
|
||||
longer than `meter_max_age_s` while it lasts, and SAFETY-18's frozen-meter
|
||||
conjunction. In all three the claim becomes a floor of **0 W**. That is the
|
||||
backstop for the frozen meter: the alarm arrives at `meter_freeze_s` (300 s) of
|
||||
a 900 s billing quarter and commands nothing, and this stops the import.
|
||||
|
||||
**After a gap it goes on (SAFETY-90).** The seconds the meter missed count at
|
||||
the higher of the peak limit and the import seen just before and just after
|
||||
the gap, with the batteries at 0 W. Seconds before the add-on's first reading
|
||||
count the same way, at the import seen first, and the page says part of the
|
||||
period was not measured. DECISIONS/SAFETY-90-peak-guard-after-a-gap.md has the
|
||||
bound.
|
||||
|
||||
**It trips, where the freeze detector only notifies,** and the captures are why.
|
||||
On the healthy reference day (2026-08-23) it engages on **zero** of 95 quarters.
|
||||
@@ -1800,7 +1839,9 @@ exactly like the SoC drift the monthly maintenance cycle exists to correct.
|
||||
It will discharge straight past this number and keep going until the pack's own
|
||||
BMS cut-out stops it. What you are promised by `peak_soc_floor: 20` is *"the
|
||||
capacity-peak guard will not spend the pack for money below 20 %"*, and
|
||||
**nothing more**. See below for what that costs on a real evening.
|
||||
**nothing more**. See below for what that costs on a real evening. With two
|
||||
or more batteries, one under it is used last: only once the others give
|
||||
their full power (SAFETY-91).
|
||||
- **When it binds you are told**, once per episode, with notification
|
||||
`goodwe_peak_soc_floor` and a red status-page row reading *Battery at its
|
||||
peak reserve* (`PEAK_GUARD_FLOOR_LIMITED` in the log): the quarter **will**
|
||||
@@ -1945,6 +1986,15 @@ over the rating is logged as a software fault, at `ERROR`, and it has its own
|
||||
notification budget.
|
||||
Why: [`DECISIONS/SAFETY-40-the-integrator-bound-is-not-a-bug.md`](DECISIONS/SAFETY-40-the-integrator-bound-is-not-a-bug.md).
|
||||
|
||||
The integrator bound counts only on a cycle the loop decided. While
|
||||
maintenance, the peak guard or a safety limit sets a battery to discharge, the
|
||||
same alarm fires only when that battery gives more than `saturation_w` less
|
||||
than the least it was told over the plant's settle time (at least 23 s), for
|
||||
`saturation_cycles` cycles. A charge is not graded: a full battery takes less
|
||||
as it tops up. At two or more inverters the loop does not wind while
|
||||
maintenance holds a battery.
|
||||
Why: [`DECISIONS/SAFETY-85-no-false-not-keeping-up.md`](DECISIONS/SAFETY-85-no-false-not-keeping-up.md).
|
||||
|
||||
### The frozen-meter alarm
|
||||
|
||||
A meter that has died is easy — it stops answering, the sample age climbs past
|
||||
@@ -3114,7 +3164,10 @@ The battery gives less power than the add-on asks for. This is normal for a
|
||||
short time, for example just after a large load switches on or the sun comes
|
||||
out. It is not a fault, and control keeps running. If it keeps coming back,
|
||||
check that the battery is switched on, and that it is not empty (when it should
|
||||
give power) or full (when it should charge).
|
||||
give power) or full (when it should charge). During the monthly maintenance or
|
||||
while the peak limit is guarded, it only shows when the battery gives
|
||||
clearly less than it was told. The status page counts how many times it
|
||||
happened.
|
||||
|
||||
### Power request too high
|
||||
|
||||
@@ -3149,6 +3202,10 @@ and what to do:
|
||||
- **A battery cut-out level is not known.** Fill in “Battery cut-out level”
|
||||
for each inverter in the settings.
|
||||
|
||||
With several inverters, a battery at its peak reserve does not give for the
|
||||
peak and the others do. This alarm then comes only when the others cannot
|
||||
keep the period under the limit alone. Control keeps running meanwhile.
|
||||
|
||||
The peak reserve only applies to keeping the peak down. Normal home use can
|
||||
still draw the battery below it, down to the battery cut-out level, where the
|
||||
battery switches itself off.
|
||||
@@ -3215,8 +3272,9 @@ section without a link. The documentation tab adds no heading anchors, so the
|
||||
link cannot jump to the section.
|
||||
|
||||
**Battery is not keeping up.** The grid-following loop's integrator reached its
|
||||
anti-windup bound (`integrator_max_w`, SAFETY-40). No command went over the
|
||||
rating. The log line is a WARNING with reason code `OUTPUT_CLAMP`. See *The
|
||||
anti-windup bound (`integrator_max_w`, SAFETY-40), or a battery that another
|
||||
owner set to discharge gave less than it was told (SAFETY-85). No command went
|
||||
over the rating. The log line is a WARNING with reason code `OUTPUT_CLAMP`. See *The
|
||||
winding-loop alarm*.
|
||||
|
||||
**Power request too high.** A computed command above a limit was cut before it
|
||||
@@ -3244,7 +3302,9 @@ state of charge is unread, or when an inverter's `bms_soc_floor` (label
|
||||
“Battery cut-out level”) is missing, because the reserve is graded against it.
|
||||
Charging from the grid stays blocked meanwhile. With several inverters the
|
||||
notification names the battery at its limit (SAFETY-32), each graded against
|
||||
its own cut-out level. ⚠️ **The reserve binds the peak guard only
|
||||
its own cut-out level. That battery is held (weight 0 while the site gives)
|
||||
and the guard's need goes to the others, up to their rating; the alarm fires
|
||||
only if they miss the limit at full power for `ALARM_HOLD_S` (SAFETY-87). ⚠️ **The reserve binds the peak guard only
|
||||
(SAFETY-25).** Self-consumption has no such floor. See *What `peak_soc_floor`
|
||||
does not protect*.
|
||||
|
||||
@@ -3382,7 +3442,7 @@ for a new install*.
|
||||
| Meter reading may be stuck | See *Meter reading may be stuck* under Notifications. |
|
||||
| Month peak | This month's highest 15-minute average, when it happened, and how close it is to your peak limit. Amber from 80 %. |
|
||||
| 15-minute periods measured this month | How many 15-minute periods the add-on has measured and saved this month. |
|
||||
| Peak guard, Load too high for battery, Battery at its peak reserve | What the add-on does to keep your peak down. *Idle, heading for …* is where this 15-minute period is heading. *Waiting for meter readings* after a restart means the battery does not charge until the add-on can measure the period again. |
|
||||
| Peak guard, Load too high for battery, Battery at its peak reserve | What the add-on does to keep your peak down. *Idle, heading for …* is where this 15-minute period is heading. *Waiting for meter readings* after a restart means the battery does not charge until the add-on can measure the period again. A red peak row is an alarm while control keeps running, so the banner shows its title without *Not ready*. |
|
||||
| Battery level, Battery power, Grid power | The reading of that entity. *No usable reading* means the entity is missing, or its reading is too old. Check it in Developer Tools before replacing it. |
|
||||
| Inverter control | Whether the inverter can be controlled, and how much it can charge and give. |
|
||||
| *name*: battery power reading stopped changing | The reading did not change while the add-on asked for a change. It may be stuck, or the battery may be refusing. The add-on sets the inverters to 0 W until it changes. |
|
||||
@@ -3422,7 +3482,8 @@ reason code, and its full figures are on the DEBUG line right after it and in
|
||||
…`, and the peak guard's full reason is on the DEBUG line after its ERROR.
|
||||
- ***name*: battery power reading stopped changing** is SAFETY-37/56/57. The
|
||||
ERROR line says when it clears and names `STALE_READING`. The full reason,
|
||||
with both possible causes, is on the DEBUG line after it.
|
||||
with both possible causes, is on the DEBUG line after it. The 35 s counts
|
||||
only while the command asks the reading to move (SAFETY-94).
|
||||
- **Meter: no reading for …** (red): the add-on logs one WARNING when the
|
||||
meter goes quiet, "No meter reading for …", and an INFO line when readings
|
||||
return, at most once every 10 minutes. Past `stale_input_s` the **Why**
|
||||
|
||||
Reference in New Issue
Block a user