[1.5.0b3] - 2026-09-21
Added
- Excluded devices no longer push grid import above the contracted power: an excluded device is hidden from the controller by design, but the breaker still sees it. When the excluded load alone would exceed "Maximum contracted power", the battery now covers just the excess. Always on, no setting. Peak shaving remains the opt-in economic layer below it.
- Zonneplan dynamic pricing: use hourly, quarter-hourly or legacy electricity tariff forecasts from Zonneplan One through the shared import/export price parser, with tax-inclusive EUR/kWh normalization. Thanks to @hprax
- High price discharge (#270): opt-in deliberate export into a price peak, only when the spread clears the existing Minimum arbitrage margin, which now governs both buying and selling.
- "Advanced settings" toggle on the Control tab: the predictive-charging card now shows only its switches and status rows by default; its fine-tuning sliders (price thresholds, arbitrage margin, forecast margins, SOC floor, pre-discharge/reserve limits) are one click away behind a per-browser toggle in the card's own header.
Changed
- The predictive-charging card is now split into groups: divider lines separate base settings, charge pricing and each discharge feature, which now keeps its switch, its sliders and its status row together.
- The predictive-charging setup forms now only ask for sensors: every threshold, toggle and limit they duplicated is already a live switch or slider on the dashboard, so Dynamic Pricing went from twenty-one fields to five. Nothing is lost: the same settings are one click away on the Control tab, and changing them there no longer reloads the integration.
- The two export-power sliders are gone; both features now use the fleet's discharge power: "High price discharge export limit" and "Pre-discharge export limit" both asked for the same thing, and the system-wide discharge limit already caps it. To export less, lower that limit (or drive it from an automation). A migration drops both entities.
- The pre-discharge reserve SOC now defaults to 20%: it defaulted to 0%, which let anti-curtailment empty the fleet down to each battery's own minimum for a solar forecast that may never arrive. Installs that never enabled pre-discharge get the new default; if you had set a value, it is kept.
- "Predictive grid charge margin (%)" removed; the solar safety margin now defaults to 5% of your battery capacity: the percentage inflated the energy deficit after it was computed, so it hedged hardest on cloudy days with little solar and barely at all on sunny days, when an optimistic forecast actually costs you. The kWh "Solar forecast safety margin" does the same job where the risk is — it discounts the forecast itself — and its help text now says so. An automatic migration drops the percentage and deletes its entity, so update any automation that used it. New installs start with that 5% already showing on the slider; on upgrade your own margin is left untouched, so if it is still at 0 you have to raise it by hand.
dp_price_discharge_controlandrt_price_discharge_controlmerged into oneprice_discharge_controlswitch (#270): Dynamic Pricing and Real-Time Price modes are mutually exclusive, so the two entities always gated the same behaviour. Existing installs keep their currententity_idand ON/OFF state via an automatic migration; if you had switched predictive mode before and both entities still exist, the one for the inactive mode is removed — update any automation that referenced it.- Marstek block reads now cover every adjacent register, not the handful in a hand-written table (#361): reading a contiguous span in one request was introduced with a table somebody wrote out by hand, so the spans nobody had looked at kept costing one request each — on a Venus D the four MPPT powers were four separate requests every two seconds. The table is now derived from the registers the integration polls anyway, by the same rule: only registers that are already adjacent and on the same schedule are grouped, so a span still never reaches across an address the battery does not implement. Measured on a Venus D, the fast poll drops from 15 requests to 11 and a full pass from 47 to 41, which is breathing room for the weak v3 Modbus stack and for anything else sharing the battery's single connection.
Fixed
- Vacation mode no longer freezes Home Assistant (#499): with no vacation night learned yet, the away baseline asked the consumption profile for a forecast, whose fallback asked the tracker for its daily average, which is that same baseline. The pair recursed until Python gave up, so every read of the vacation switch or the expected-consumption sensor blocked the event loop for seconds.
- Price-only controls no longer appear on time-slot installs: high price discharge, price-aware surplus hold and the discharge reserve only run under Dynamic Pricing, but their switches, sliders and status rows were showing in Time Slot and Real-Time Price mode. They are now registered only in Dynamic Pricing mode.
- Price-aware surplus absorption is reachable from the dashboard: its toggle and minimum-saving slider existed only in the options flow, so the feature was invisible. Its status sensor is renamed "… Status" so the two entities no longer share a name.
- The discharge reserve is reachable from the dashboard (#270): its toggle and minimum-saving slider existed only in the options flow, so it was the only one of the six economic predictive-charging features with no dashboard control. Its status sensor is renamed "… Status" so the two entities no longer share a name.
- A Venus D no longer loses a reply every five minutes: the battery stops answering for about four seconds on a five-minute clock — 138 times over eleven hours, longest 4.46 s, measured at the Modbus proxy, so it is the device and not the network. The per-attempt read timeout was three seconds, so it expired inside every one of those stalls; the retry then put a duplicate request on the wire and the battery answered both, leaving one reply nobody was waiting for. That is the
received pdu without a corresponding requestandtransaction_id mismatchpair in the log, twelve times an hour. The Venus D now waits six seconds per attempt, which covers the stall with room to spare. No values were being lost either way — this removes the wasted round trip and the noise. Unchanged for v3 and vA, where the stall has not been measured. Thanks to @sphings79. - A Marstek v3/A/D no longer waits out a timeout on a register it does not implement: the firmware answers such a read with a nine-byte rejection whose length field says one byte more, and pymodbus waits for a byte that is never sent. The repair for it has been in the integration for a while and was never called: pymodbus takes that hook as a constructor argument and hands it to its transaction manager, while the integration set it on the client object afterwards, where nothing reads it. Measured against a battery that sends the frame, such a read took 9 s — timeout times retries — and now costs milliseconds. Nothing changes for a battery reached through a Modbus proxy, which parses the frame itself. Thanks to @sphings79.
- A Huawei inverter's "no data" markers no longer reach the control loop: a register the inverter cannot supply is answered with an all-ones or max-int marker, which was published raw — a 6553.5% SOC, a 2.1 GW battery power. Those readings are now dropped and the last good value kept. Thanks to @sphings79.
- A Huawei LUNA2000 can now be set to its real 5% discharge floor: the soft SOC cutoff sliders used a Venus D's hardware floors (12-50% / 50-100%) on every driver without cutoff registers, so Huawei's own 0-20% / 90-100% windows were out of reach. Each driver now declares its own range. Thanks to @sphings79.
- Predictive charging now covers the night through to sunrise (#410, #344): every layer that decided to buy grid energy stopped at midnight, so the hours between midnight and sunrise belonged to no plan and the battery reached the morning peak empty. If you raised the solar safety margin or the grid charge margin to cover the night by hand, lower them again. The "Guaranteed Minimum SOC" help text now describes what that setting actually does — a floor on the current SOC, not a morning target.
- The discharge reserve now holds energy for pre-dawn price peaks (#410): it stopped reserving at midnight, on the grounds that tomorrow's sun would refill the battery anyway. There is no sun before dawn, so a peak at 06:00 was left to the grid. The reserve now reaches the next sunrise, and still never holds energy for tomorrow's evening peak. Only affects the opt-in "Discharge reserve" (#400).
- Dynamic Pricing re-plans when tomorrow's prices are published: the 00:05 plan can only see today, so energy for the small hours was booked into today's slots even when the next day's were cheaper. Once the provider publishes (~13:00 CET), the remaining day is re-planned. Once a day, and never while a charge slot is running.
- A Hoymiles HiBattery 4020 X now reports its own solar production (#467): the driver dropped the PV power the battery publishes, so with no external solar sensor the dashboard showed no Solar node and every PV charge was logged as a grid charge. Both DC sources are now published as Solar Power — the MPPT inputs and a microinverter on the off-grid port. AC-coupled models (MS-A2, 1920/4020 AC) are unaffected. Thanks to @TheOops.
- A manual forced charge/discharge survives a Modbus stall (#477): Marstek V150 firmware can drop forced mode to None / 0 W while its Modbus stack is stuck, and in manual mode nothing wrote it back. The manual loop now re-asserts the user's Charge/Discharge once the battery answers again. Thanks to @Remueb.
- Dynamic Pricing no longer books dearer slots after a morning SOC drop (#472): the overnight-drop re-plan reused the evening top-up, which skipped the cheap midday slots already listed by the 00:05 plan, booked the next-cheapest ones and then armed all of them. A SOC drop now runs a full remaining-day re-evaluation, and the evening top-up picks from every slot and arms only the ones it chose. Thanks to @benediktarnold.
- A battery past its predictive target keeps absorbing solar surplus (#470): during a grid-charge slot the per-battery target also blocked solar charging, so the house exported while that battery sat idle. The target now limits grid energy only: the battery takes measured export up to its normal ceiling, and shows as
battery_solar_only_chargeinstead of charge-blocked. Thanks to @RobtoCopter. - Peak shaving that holds the battery overnight no longer triggers a guaranteed-minimum-SOC grid charge: in Time Slot mode the planner assumed the battery would cover all the load before sunrise. While peak shaving holds it, the planner now counts only the load above the peak limit. Releasing or engaging peak shaving inside a charging window triggers a re-evaluation. No-discharge time slots that apply to every battery are now projected the same way, with no battery drain at all.
- The weekly 100% charge now reserves a cheap slot after sunset (#489): the midnight plan netted the weekly gap against the forecast surplus, so a sunny forecast reserved nothing and a day that underdelivered was left buying at 21:45. The weekly day now books the cheapest slot after the solar window closes, sized for the whole gap; if the sun does fill the pack, the pre-slot re-evaluation cancels it and nothing is bought. The evening top-up also sizes to 100% on that day as a last resort.
- Predictive charging now plans against the peak shaving limit (#502): peak shaving caps grid import, so it caps grid charging, but every estimate ignored it — the notification announced a charge power the battery would never reach, and the schedule booked too few hours at too low a cost. Thanks to @alainsch.
- The daily operation graph says why a flat afternoon forecast is flat: with no forecast energy left for the day it showed the generic "the fallback estimate is unavailable", because the reason behind it had no label. It now reads "No solar energy is forecast for the rest of the day", and disappears at sunset instead of an hour later.