Changelog¶
Changelog¶
[1.8.4] - 2026-05-27¶
Changed¶
- Weekly full charge completion decoupled from delta-V measurement: Weekly full charge now declares completion as soon as every battery has reached the pause voltage (
max_cell_voltage ≥ 3.58 V), restoring registers and arming hysteresis immediately. Previously, completion required the 60-second diagnostic delta-V measurement to finish first, which could fail if voltage sagged below 3.58 V under the reduced 95 W taper load before the timer elapsed. The 60-second measurement still runs as a best-effort diagnostic; if it did not finish before completion, a one-shot snapshot is captured at completion time under phasetop_charge_taper_complete.
Fixed¶
- Charge hysteresis not activating when max SOC = 100% with voltage taper: When the charge tapper blocked charging at 3.58 V, the BMS never reported 100% SOC, so the hysteresis activation condition (
current_soc ≥ max_soc) was never met and the tapper cycled indefinitely without engaging hysteresis. Hysteresis now also activates when the tapper is paused at top voltage and the charge target is 100% (either configuredmax_soc = 100%or weekly full charge active). Active cell balance mode is explicitly excluded.
[1.8.3] - 2026-05-26¶
Added¶
- Active balance mode: Added a per-battery active balancing mode that charges at 95 W to 3.58 V, waits 60 s to measure
delta_V, cycles with 25 W discharge to 3.48 V, repeats untildelta_V <= 0.03 V, persists its phase across restarts, and performs a final 25 W discharge to 3.42 V before completing. - Dynamic Pricing: EPEX Spot support (e.g. aWATTar): New price integration option for sensors that expose hourly prices under a
dataattribute as a list of{start_time, end_time, price_per_kwh}entries (€/kWh). Reuses the same cheap-hour scheduling and price-based discharge gating as the existing Nordpool/PVPC/CKW integrations. - Dynamic Pricing: ENTSO-e Transparency Platform support: New price integration option for sensors that expose prices under
prices_today/prices_tomorrowattributes as a list of{time, price}entries (€/kWh, ISO 8601 timestamps with timezone). Supports both hourly and 15-minute slots — slot end times are inferred from the next entry's start. Reuses the same cheap-hour scheduling and price-based discharge gating as the other price integrations.
Changed¶
- Charge 100% revamped: Normal 100% charging now uses a voltage-only profile. It throttles to 95 W from
max_cell_voltage >= 3.48 V, then stops charging at 3.58 V and waits 60 s to record thedelta_Vreading. Charging is left stopped at that voltage and the normal SOC/charge logic decides when charging is allowed again — no forced discharge in this path. - Dynamic Pricing: integration selector switched to dropdown: The price integration picker in the setup and options flows now renders as a compact dropdown instead of a vertical radio list, keeping the form short as more providers are added.
- Balance sensor names regrouped under a
Balance - …prefix: The five cell-balance diagnostic sensors (Cell Delta,Balance Status,Balance Trend,Last Balance Reading,Delta Average (4 readings)) were renamed across all six translation files so they sort together in the HA UI:Balance - Cell Delta (at 100%),Balance - Status,Balance - Trend,Balance - Last Reading, andBalance - Delta Average (4 readings).Cell Deltaalso gained the(at 100%)qualifier to make explicit that the reading is captured at full charge. Translation keys andentity_ids are unchanged.
Fixed¶
- Venus A manual-mode power entities still capped at 1200 W:
MAX_POWER_BY_VERSION["vA"]was already raised to 1500 W for the config flow and PD calculations, but theset_charge_power,set_discharge_power,max_charge_power, andmax_discharge_powernumber entities inNUMBER_DEFINITIONS_VAwere still hard-capped at 1200 W. Raised to 1500 W so the manual-mode sliders match the configured hardware limit. - System SOC weighted by battery capacity: The
System SOCaggregate sensor now weights each battery's SOC by itsbattery_total_energy, so mixed-capacity systems report the real stored-energy percentage instead of a simple average across batteries. - Disconnected batteries reported in manual mode: The Non-responsive Batteries diagnostic sensor now also reports batteries that become unreachable through Modbus polling failures, so unplugging a battery's Ethernet/Modbus adapter is surfaced even while Manual Mode skips automatic power commands.
- Multi-battery split-load hold uses wall-clock time: The split-load minimum runtime now expires after 120 seconds of real time instead of 48 PD write cycles. If the PD controller remains inside deadband when the hold expires, it now performs a one-time rebalance to release the extra battery instead of leaving it latched until the next correction outside deadband.
- Single-battery idle state after zero-power selection: The load-sharing selector now clears active battery state and split-load hold counters before the single-battery fast path when requested power is 0 W. This prevents one-battery systems from retaining load-sharing state intended for multi-battery split holds while the controller is intentionally idle.
[1.8.2] - 2026-05-15¶
Added¶
- High-SOC charge taper (Still under testing): During any charge, the controller now caps each battery's charge allocation to 500 W from 95% SOC and 100 W from 98% SOC, while still respecting the existing per-battery and system power limits.
Changed¶
- Cleaner debug logging for polling and Modbus reads: Normal debug logs no longer emit one line per skipped sensor, disabled entity, raw Modbus register read, or unchanged aggregate power calculation. Coordinator polling now produces compact per-cycle summaries, while maintainer-level raw Modbus and per-sensor detail remain available behind internal debug switches.
- More useful power-control debug summaries: The PD controller now logs a single
Power planline with the control mode, grid reading, target, error, previous/requested/allocated power, selected batteries, allocation, active charge/discharge blockers, and active setpoint offsets/overrides. Per-battery writes now include explicit requested-vs-readback ACK details for force mode, charge power, discharge power, and reported battery power. - Reduced repeated rate-limiter noise: The PD rate-limiter message is now emitted only when limiting first becomes active, changes direction, or the requested change materially changes, instead of repeating every stable control cycle under sustained surplus or demand.
[1.8.1] - 2026-05-12¶
⚠️ Breaking Change — Dynamic Pricing discharge threshold priority¶
When price-based discharge control is enabled, a configured max_price_threshold now acts as the discharge threshold. If it is empty, Dynamic Pricing falls back to the automatically calculated daily average. The setup/options help text and documentation were updated to describe this priority clearly.
Added¶
- Solar forecast reserve discharge blueprint: Added an optional blueprint that controls the per-battery Allow Discharge switches so the system keeps a configurable night reserve (for example 50 % SOC) unless the remaining solar forecast is high enough to recharge from minimum SOC back to that reserve. This lets users dump energy in the morning when solar production is expected while preserving backup capacity and a healthier overnight SOC when the forecast is weak.
Fixed¶
- Peak shaving conserving-mode discharge loop: Peak shaving now evaluates conserving decisions against the base PD target without reusing its previous capacity-protection override. This prevents below-limit household load from being misclassified as solar surplus and causing short discharge/stop cycles.
- Charge delay SOC setpoint latch survives integration reloads: The charge-delay setpoint hysteresis state is now persisted and restored for the same day. Reloading the integration no longer resets
_delay_setpoint_reachedtoFalseand re-enters "Charging to setpoint" when SOC is still above the hysteresis resume threshold. The Charge Delay diagnostic sensor also restores its same-day latch/unlock state as a fallback.
[1.8.0] - 2026-05-11¶
Added¶
- Unified charge/discharge blocker registry: The PD controller now uses explicit runtime blocker registries for charge and discharge decisions. Blockers can be global (system-wide) or scoped to an individual battery, and are exposed on the Integration Status diagnostic sensor via
charge_blockers,discharge_blockers,battery_charge_blockers, andbattery_discharge_blockers. Existing restrictions such as charge delay, time slots, price-based discharge control, EV charger no-telemetry handling, and per-battery user blocks now share the same decision path. - Per-battery Allow Charge / Allow Discharge switches: Each battery now exposes two software controls to include or exclude it from automatic PD charging or discharging independently. The switches persist in the config entry as
allow_chargeandallow_discharge, default to enabled for existing installations, and do not write Modbus registers directly. Disabling a direction stops that battery if it is currently active and lets the next PD cycle reallocate power to the remaining eligible batteries. - System-wide charge/discharge power caps: Advanced PD Controller settings now include an
Enable system power limitscheckbox plus optionalSystem Max Charge PowerandSystem Max Discharge Powersliders. With the checkbox off, the previous behavior is kept and the runtime slider entities are not created; with it on, any positive cap limits the combined power across active batteries while still allowing an individual battery to use its own full limit when the rest of the system is idle. The Configuration Summary sensor reports whether the feature is enabled plus both configured totals and effective capped totals for support diagnostics.
Changed¶
- Peak shaving peak limit range extended: The peak limit selector in setup/options and the runtime number entity now accepts
500 Wto10000 Win100 Wsteps. - PD blocker enforcement before deadband and stale-sensor early returns: Active charge/discharge commands are now stopped immediately when a matching global or per-battery blocker appears, even if the grid sensor is inside deadband or has not updated. This prevents stale commands from continuing after a feature or user switch has blocked that direction.
- SOC limits and charge hysteresis are now visible in the blocker registry: Per-battery charge blocking caused by configured maximum SOC or active charge hysteresis is now recorded in
battery_charge_blockers, and discharge blocking caused by minimum SOC is recorded inbattery_discharge_blockers. The top-levelcharge_blockedanddischarge_blockedattributes on Integration Status report the effective system state, so they becometruewhen every known battery is blocked in that direction, even if the reason is per-battery rather than global. - Hourly net balance uses the charge blocker registry for block reasons: Positive hourly-balance compensation now reads the unified charge blockers for reasons such as solar charge delay, charge time-slot restriction, or EV pause, while keeping its existing local checks for charge hysteresis and max SOC.
Fixed¶
- Peak shaving conserving mode no longer sends discharge commands below the peak limit: When SOC is below the peak-shaving threshold but the estimated house load is still below the configured peak limit, the controller now targets the current grid level and immediately stops any existing discharge command. This prevents the battery from trying to discharge during normal consumption and being incorrectly marked as non-responsive.
- Dynamic-price unit guidance and CKW parser compatibility: Setup/options help text now asks for thresholds in the real sensor scale (
€/kWhfor Nordpool/PVPC,CHF/kWhfor CKW), avoiding the old cent/Rappen guidance. The CKW slot parser also accepts CKW-derived entries that expose the total price asvalueorintegratedin addition toprice, without converting from Rappen. - CKW price-based discharge block in Dynamic Pricing: The discharge blocker no longer reads CKW's all-prices sensor state as the current price. That sensor may expose the number of slots (for example
96) as its state and the real 15-minute prices in thepricesattribute, so the blocker now derives the active slot price fromprices. Dynamic Pricing uses the configured max price threshold for discharge blocking when present, otherwise it uses the daily slot average.
[1.7.6] - 2026-05-09¶
Changed¶
- Integration Status diagnostic sensor refreshed: The status sensor now surfaces newer runtime features and blockers instead of falling back only to generic charging/discharging states. It can now report price-based discharge blocking, hourly net balance compensation/caps/blocks, peak-shaving actions, EV charger no-telemetry pauses, cell-balance holds, and backup/offgrid cooldowns. Diagnostic attributes were expanded with the active setpoint, price mode state, hourly balance snapshot, capacity-protection details, EV pause state, backup cooldowns, and non-responsive batteries.
- Configuration Summary diagnostic sensor refreshed: The hidden support sensor now omits battery IP addresses and ports, and adds the configuration that matters for issue triage: household consumption sensor, manual/predictive override state, total configured battery power, backup off-grid thresholds, predictive safety margin, balance monitor, charge-delay SOC setpoint, hourly balance parameters, PD target grid power, and EV charger no-telemetry flags on excluded devices.
- Dynamic pricing: discharge block evaluated reactively per cycle, like real-time price mode:
_apply_price_discharge_block()no longer uses the pre-scheduledselected_slotsto decide whether to block discharge. The slot list still governs grid charging (when to actively pull energy from the grid), but the discharge decision now comparescurrent_priceagainst the threshold (average_price_sensorif configured, otherwisemax_price_threshold) on every control cycle, identical to real-time price mode. This eliminates two blind windows where the previous slot short-circuit could not fire because_dynamic_pricing_schedulewasNone: the ~15 s gap after a Home Assistant restart inside a cheap slot (the schedule lives only in memory and is rebuilt by the startup evaluation), and the 5-minute gap each midnight between Phase 3 daily reset and the 00:05 evaluation. In both gaps the slot-based block silently went unset and a transient sensor reading abovemax_price_threshold(slot-boundary lag, float precision) was enough to let the PD controller initiate discharge against an actually-cheap price. With the change, DP and RT now share the exact same per-cycle discharge logic; the only behavioural difference between the two modes is how they decide when to grid-charge (DP: pre-scheduled cheap slots; RT: reactive price crossing). - Hourly net balance: added smart meter requirement notice: The feature description now includes a warning that hourly net balance control is only beneficial when using electricity contracts with smart meters that perform hourly net balance calculation. Without hourly netting at the meter level, battery setpoint adjustments do not translate to cost savings. Notice added to all language translations (EN, ES, DE, FR, NL).
- PD Target Grid Power range extended to ±2500 W: The slider in the setup and options flows (Advanced PD Controller) and the number entity limits now accept values from −2500 W to +2500 W (previously −500 W to +500 W).
Fixed¶
- Charge delay estimated unlock time drifted minute by minute: The Charge Delay sensor's estimated unlock time used a legacy consumption projection based on daylight hours, while the real unlock condition used the configured consumption window. When the estimate fell into the past but the real condition still kept the delay active, the displayed time followed the current clock minute by minute (e.g. 12:52, 12:53, 12:54) until the actual unlock. The estimator now uses the same consumption-window proration as the live delay logic, and stale "unlock now" estimates are ignored unless the real energy-balance condition is also met.
- Multi-battery split-load minimum runtime added: The symmetric activation/deactivation deadband prevented threshold chatter, but the PD controller could still react strongly immediately after another battery joined and push the next requested power below the deactivation side of the band. That made the extra battery bounce between idle and a large share of the load under high, bursty household demand. When the selector decides that more than one battery should participate, the whole split is now held for ~2 minutes and the timer is refreshed whenever the split-load condition is met again. The hold is cleared only when the requested power goes to 0 W or the controller switches direction.
- Multi-battery selection oscillates near the activation threshold: The previous hysteresis logic only guarded deactivation (removing the last added battery if power dropped slightly below threshold), but had no guard on activation. In practice this caused batteries to rapidly turn on and off when load hovered near the crossover point. Replaced with a symmetric deadband: Case A (deactivation) — a previously-active battery dropped by the greedy loop is re-added unless power has fallen clearly below
activation_threshold − hysteresis_gap; Case B (activation) — a newly-added battery is removed again unless power has risen clearly aboveactivation_threshold + hysteresis_gap. Both cases cover all batteries inprevious_active, not just the last one selected. - Charge delay SOC setpoint oscillation: When the SOC setpoint was enabled and the delay was active, any brief drop below the setpoint (due to home consumption) caused the system to immediately re-enable charging back to the setpoint, creating a charge/block oscillation loop. Fixed by adding a 3 % hysteresis band: once the setpoint has been reached and the delay becomes active, charging to the setpoint only resumes if the SOC falls at least 3 % below the configured setpoint threshold (
_delay_setpoint_reachedflag, reset daily). - Hourly net balance: remove closed-hour history: The Balance Neto sensor no longer exposes the
historyattribute and the manager no longer persists closed-hour entries. Only the current-hour accumulator is kept across restarts. - Hourly net balance: clear offset state when disabled: Turning off the Hourly Net Balance switch now clears both the controller setpoint offset and the manager's internal visible offset state (
offset_w,theoretical_offset_w, and block reason). The same cleanup is used when the feature is disabled through options, manual mode, or outside active slots. - Peak shaving takes priority over hourly net balance: The PD controller now refreshes hourly net balance and peak shaving setpoint overrides before deadband and first-execution handling. This prevents an hourly net balance discharge command from being kept alive after peak shaving becomes SOC-limited; stale consumption readings also force a recalculation instead of maintaining an existing discharge while peak shaving is active.
- Hourly net balance: restore import compensation discharge: Net import above the hourly target now produces a negative setpoint offset again, allowing the battery to discharge/export during the remaining minutes of the hour to bring the net balance back toward target. This fixes the regression where the offset was clamped to zero and the PD controller could keep charging despite accumulated hourly import.
[1.7.5] - 2026-05-08¶
Added¶
- Hourly Net Balance (
feature/hourly-net-balance): New feature that tracks grid import and export within each civil hour and adjusts the PD setpoint offset in real time to drive the net energy toward a configurable target (default 0 Wh). The offset is calculated as-(deficit_Wh / remaining_h), saturated at a configurable maximum, with optional ramp-in during the first minutes of each hour and a hysteresis band to prevent micro-adjustments every cycle. Applies only within configured discharge time slots (or 24/7 when none are defined); the offset is cleared automatically outside slots and in manual mode. Capacity Protection overrides take priority automatically via the existing setpoint registry (priority=10). State is persisted to Home Assistant storage and restored after a restart (mid-hour accumulator restored only if still in the same civil hour). When the feature is enabled a single diagnostic sensor Balance Neto is added (see below). Configuration is available in both the initial setup flow and the options flow under the new "Hourly net balance" section. - Balance Neto sensor: Single diagnostic sensor that consolidates all hourly balance information. Its state is the net energy for the current hour in kWh (positive = net export to grid, negative = net import). Attributes:
status(compensating_import / compensating_export / capped / compensation_stopped / out_of_slot / idle),offset_w(active setpoint correction),imp_wh/exp_wh(import and export breakdown),target_net_wh,remaining_min,source(see below),hour_iso, andcharge_block_reasonwhen compensation is blocked. - External net balance sensor autodiscovery: On startup the integration looks for
sensor.balance_neto(configurable list inconst.py → EXTERNAL_NET_BALANCE_CANDIDATES). If found, it uses that sensor as the source for hourly net tracking instead of the built-in trapezoidal integration of the grid power sensor, which is subject to polling gaps. The sensor type is detected automatically from itsunit_of_measurementandstate_class: cumulative sensors (total/total_increasing) use a snapshot-at-hour-start approach; measurement sensors are read directly; power sensors (W/kW) fall back to trapezoidal integration. If the external sensor is not present or goes unavailable, the integration falls back to the trapezoidal method transparently. The active source is visible in thesourceattribute of the Balance Neto sensor. Sign convention: positive external sensor value = net export to grid. - Setpoint offset registry: New two-layer system for dynamic PD target control with a fixed reference of 0 W (zero grid flow). Features that need to adjust the PD target can now register either additive offsets (summed together to form the default target) or absolute overrides (highest priority wins, replaces the additive sum entirely). The user's
target_grid_powerpreference is registered as an additive offset (user_target), and capacity protection uses an absolute override with priority 10. Public API:set_setpoint_offset(),remove_setpoint_offset(),set_setpoint_override(),remove_setpoint_override(),compute_active_target(). This provides a clean extension point for future features like hourly net balance or price-based arbitrage. - Setpoint diagnostics on Integration Status sensor: The
Integration Statussensor now exposessetpoint_active,setpoint_offsets, andsetpoint_overridesas extra state attributes, showing the effective PD target and which features are contributing to it.
Changed¶
- Capacity protection migrated to setpoint overrides: Capacity protection no longer directly modifies the internal
active_targetvariable. It registers an absolute override via the new setpoint registry, allowing proper composition with other features. Behaviour is unchanged — when capacity protection is active, its override (priority 10) takes full control of the PD target.
Fixed¶
- Dynamic pricing: discharge blocked unconditionally inside selected cheap slots: Previously
_apply_price_discharge_block()comparedcurrent_price(from the sensor entity state) against_dp_daily_avg_price(averaged from thepricesattribute). When the provider only exposes end-of-day slots all at the same price (e.g. CKW at 23:00 with 4 × 0.2167 CHF/kWh slots), these two values are nominally equal but can diverge due to float precision differences between the sensor state string and the attribute entries, makingcurrent_price > thresholdTrue and leaving the block unset. Fixed by short-circuiting the price comparison when_is_in_dynamic_pricing_slot()is True: the slot was already identified as cheap during evaluation, so discharge is blocked unconditionally (unless override is active). - Dynamic pricing: discharge threshold outside cheap slots ignored
average_price_sensor: Outside selected cheap slots,_apply_price_discharge_block()usedmax_price_thresholddirectly, while real-time price mode usedaverage_price_sensor(if configured) and fell back tomax_price_threshold. This made the effective discharge threshold differ between modes even though the intended behaviour is identical — both should allow discharge only when the current price exceeds the threshold. Fixed by applying the same threshold resolution in DP outside-slot path:average_price_sensorvalue if available, otherwisemax_price_threshold. - CKW price unit in notifications showed "Rp/kWh" instead of "CHF/kWh":
_get_price_unit()returned"Rp/kWh"for the CKW integration but prices are stored in CHF/kWh. Corrected to"CHF/kWh"so slot prices and costs in persistent notifications display the correct unit. - Maximum price threshold cannot be cleared in the options flow: The
max_price_thresholdfield in both the dynamic pricing and real-time price options steps usedvol.Optional(key, default=old_value). When a user cleared the field and submitted, the HA frontend omitted the key and voluptuous restored the old value via itsdefault, making the field impossible to blank. Fixed by switching todescription={"suggested_value": old_value}, which pre-fills the field visually without affecting validation when the field is cleared. - Battery falsely excluded as non-responsive at min-SOC: When the polling SOC reported a value just above
min_soc(e.g. 30.1 % with min_soc = 30 %), the battery passed the_get_available_batteries()filter and received a discharge command. The internal BMS, however, had already reached its discharge cutoff and blocked power delivery (0 W). The feedback check interpreted this as a non-delivery and, after 3 consecutive cycles, excluded the battery. Fixed by adding a guard beforerecord_non_delivery: ifcurrent_soc ≤ min_soc + 1 %, the 0 W output is treated as expected BMS protection behaviour rather than a fault, and the non-delivery counter is not incremented.
[1.7.4] - 2026-05-02¶
Added¶
- Live charge hysteresis control per battery: A new number entity (
Charge Hysteresis Percent) is now exposed for each battery that has charge hysteresis enabled. The value can be changed at runtime from the battery's configuration menu without reloading the integration. Range expanded from 5-20% to 5-50%.
Changed¶
- Charge hysteresis maximum increased to 50%: The config flow slider now allows values up to 50% (previously 20%), giving more flexibility for batteries with aggressive BMS cutoff behavior.
- Centralized price-based discharge block computation: The
_price_based_discharge_blockedflag is now computed once per cycle in a dedicated_apply_price_discharge_block()method before mode dispatch, instead of being set inside each price handler (dynamic pricing and real-time price). This guarantees the flag is always set even when a handler returns early (override active, cheap-slot active, max_soc transition), preventing PD discharge under cheap prices in those edge cases. - More specific blocking reason in logs: When charging is not allowed, the log message now distinguishes between "charge delay active" and "time slot configuration" so users can immediately understand why charging was blocked.
- Venus A maximum charge/discharge power updated to 1500 W:
MAX_POWER_BY_VERSION["vA"]was set to 1200 W, which underestimated the hardware limit. Updated to 1500 W so the config flow slider and all power calculations reflect the correct physical maximum.
[1.7.3] - 2026-04-29¶
Fixed¶
- Real-time price mode charges outside the configured charge time slot:
_handle_realtime_price_predictive_chargingactivated grid charging based solely on price, ignoringapply_to_chargeslot restrictions. Fixed by checking_is_operation_allowed(is_charging=True)before starting a session and every cycle while charging is active. - Real-time price mode discharges during cheap-price periods after grid charging completes: When predictive grid charging finished it set
first_execution=True, causing the PD controller's first-execution path to start discharging without checking_price_based_discharge_blocked. Fixed by adding the price-based discharge block check to the first-execution path. - BMS cuts off charging at 99 % during weekly full charge (or with max_soc = 100 %): Some cells stop accepting charge before the SOC register reaches 100 %, leaving the integration in forced-charge mode indefinitely. Fixed by detecting BMS cutoff via
battery_power ≤ 10 Wandinverter_state == Standbyat SOC ≥ 99 %. After five consecutive cycles (~10 s) the battery is treated as full, triggering normal completion (register restore, hysteresis re-enable, state persist). The same check also prevents useless charge allocations in_get_available_batteries()for the general max_soc = 100 % case. - Charge delay sensor showed
target_soc: nullwhen charging was already allowed:_is_charge_delayed()computedtarget_socafter several early-return paths, so the attribute was never set when the delay was skipped. Moved the computation before the early returns so the value is always present. - No indication in the charge delay sensor that the delay was intentionally skipped on the weekly full charge day: When the balance monitor bypasses the delay on the full charge day, the sensor showed
"Charging allowed"— indistinguishable from a normal unlock. A new stateskipped_full_charge_dayhas been added with translations for EN, ES, DE, FR and NL. - Weekly full charge does not complete after HA restart or integration update: On restart,
async_setup_entrywritesmax_socback to the hardware cutoff register before the persisted state is loaded, sohandle_registers()incorrectly assumed the 100 % register was already in place and skipped the write. Fixed by using the in-memory_weekly_charge_saved_max_socdict as a session proxy — empty on every restart — to force a re-apply of the 100 % cutoff. The status field is now also persisted and restored so the sensor shows the correct state immediately after restart.
Changed¶
- Multi-battery activation threshold is now dynamic based on configured power: The second battery was always activated at 60 % of the configured capacity, which was only correct at the 2500 W default. The threshold is now derived from efficiency crossover points (1500 W discharge / 1750 W charge) expressed as a fraction of the configured max, clamped to [50 %, 95 %]. Users at the 2500 W default see no change in discharge and a slightly later charge activation (70 % instead of 60 %). Five new constants added to
const.py.
[1.7.2] - 2026-04-28¶
⚠️ Breaking Change — Target Grid Power¶
The per-timeslot "Target Grid Power" field has been removed.
Previously each discharge time slot had its own independent Target Grid Power offset (the watt setpoint the PD controller regulated the grid to during that slot). That per-slot field no longer exists.
It has been replaced by a single global "PD Target Grid Power" setting that applies for the entire integration, regardless of which slot is active. The new control is:
- Configurable in the Options → Advanced PD Controller flow.
- Exposed as a live number entity (
number.marstek_venus_system_pd_target_grid_power) that can be changed at runtime without reloading the integration. - Default: 0 W (net-zero grid regulation, same as the previous default).
Action required on upgrade: If any of your timeslots had a non-zero Target Grid Power, the value has been reset to 0 W. Open Options → Advanced PD Controller and set the new global value to your desired setpoint.
Fixed¶
- Total Charging/Discharging Energy sensor periodically resets to near-zero: The battery firmware occasionally returns a corrupt partial value when a Modbus read of the 32-bit energy counter coincides with an internal firmware update of that register. The read technically succeeds (no error logged), but the decoded value can be a fraction of the real counter (e.g. ~50 kWh instead of ~491 kWh). Because the sensor has
state_class: total_increasing, Home Assistant interprets the drop as a meter reset and permanently corrupts Energy Dashboard statistics. Fixed by adding a monotonic guard in the coordinator: for anytotal_increasingsensor, a new reading that is positive but less than 90 % of the last known value is silently discarded. Drops to exactly 0 (daily counter midnight reset, factory reset) are still accepted. Applies tototal_charging_energy,total_discharging_energy,total_daily_charging_energy, andtotal_daily_discharging_energy.
Changed¶
- Entity unique IDs and device identifiers now include the Modbus port: Until now entity unique IDs followed the pattern
{host}_{key}and device identifiers were(DOMAIN, host). This made it impossible to register two batteries reachable at the same IP on different Modbus ports — a setup that exists when several Venus units share a single bridge or NAT mapping. Both the unique ID and device identifier now include the port:{host}_{port}_{key}and(DOMAIN, "{host}_{port}"). The change is applied acrosssensor,binary_sensor,switch,number,select,button,balance_sensorsandcalculated_sensors. - Config entry version bumped to 2 with automatic migration: An
async_migrate_entryhandler renames existing entity unique IDs from{host}_{key}to{host}_{port}_{key}and updates the device identifier in the device registry. Home Assistant automatically populates theprevious_unique_idfield on each migrated entity, so long-term statistics (energy dashboard, history graphs) keep linking to the same entity without interruption. The migration is idempotent and runs once per config entry on first load with the new version.
[1.7.1] - 2026-04-26¶
Added¶
- Live max/min SOC controls for v3/vA/vD batteries: Batteries on firmware v3, vA and vD do not expose a Modbus register for the charging/discharging cutoff capacity, so until now
max_socandmin_soccould only be edited through the options flow (with an integration reload). Each affected battery now gets two number entities —Charging Cutoff CapacityandDischarging Cutoff Capacity— that update the software-enforced limits read by the PD controller in real time and persist the new value in the config entry. v2 batteries are unaffected: they keep using the existing hardware-backed entities. The translation keys and unique IDs match the v2 ones, so the entity names and entity IDs remain consistent across hardware variants.
Fixed¶
- Charge delay unlocked prematurely by
energy_balancedue to consumption being prorated against daylight hours: The energy-balance check in_should_delay_chargecomputedremaining_consumption_kwh = (avg_consumption / daylight_hours) * hours_to_t_end, dividing the daily total byt_end − t_start. This implicitly assumed the entire day's consumption happens during daylight, which inflated the projected midday consumption by roughly 24 / daylight_hours (≈1.7× in summer) and madenet_solar_for_batterylook much smaller than it really is. With a high household consumption (~38 kWh/day in the reported case), the inflated estimate pushednet_solarbelow the safety threshold (energy_needed × DELAY_SAFETY_FACTOR) even when the remaining solar production was clearly enough to top off the battery — triggering a spurious unlock at midday and allowing grid charging the rest of the day. Theavg_consumptionvalue is in fact already scoped to the consumption window for both data sources: whenhousehold_consumption_sensoris configured,accumulate_household_consumptiononly integrates whileis_in_consumption_window()is true (24h if nocharging_time_slotis set, otherwise the hours outside the slot, only on slot days); without that sensor, the captured value istotal_daily_discharging_energy + _daily_grid_at_min_soc_kwh, both of which only grow during the same window. Fixed by prorating against the actual consumption-window duration: two new helpersget_consumption_window_hours_per_day()andconsumption_window_hours_in_range(from_h, to_h)were added toConsumptionTracker, and the formula is nowavg_consumption × remaining_window_hours / window_hours_per_day. With no slot configured this collapses to a uniform 24h distribution; with a slot it correctly excludes the grid-charging window from both numerator and denominator. The displayedremaining_consumption_kwhattribute is consequently lower and more realistic, and the energy-balance unlock now triggers only when the remaining solar genuinely cannot cover the gap. - Spurious warning on v3 weekly full charge: Each time a weekly full charge activated on a v3 battery, a
WARNING-level log entry appeared stating that software enforcement would be used (instead of the hardware cutoff register). This is expected behaviour for v3 hardware and does not indicate a problem. The message has been downgraded toDEBUGso it only appears when debug logging is explicitly enabled. - Alarming
WARNINGwhen the daily consumption capture had nothing to record: On days not covered bycharging_time_slot.daysthe household accumulator legitimately stays at 0 — the integration only accumulates outside the charging slot, and skips entire days that aren't in the slot's day list. The 23:55 capture would then logWARNING: household accumulator too low (0.00 kWh), skipping, which sounded like data loss. In realityget_dynamic_base_consumption()already self-heals: on the next predictive-charging cycle it runs an opportunistic recorder backfill that replaces the day's default sentinel (DEFAULT_BASE_CONSUMPTION_KWH = 5.0) with the real value from the household sensor history. The message has been downgraded toINFOand reworded to make the self-healing behaviour explicit, so the log entry no longer suggests something is broken.
Changed (internal)¶
__init__.pyandcoordinator.pysplit into focused modules: Pure code-movement refactor with no behavioural change. Roughly 1,500 lines were extracted from__init__.py(which had grown to ~5,660 LOC) and ~95 lines fromcoordinator.py, replicating the existingbalance_monitor.pyextraction template — the controller still owns the public attributes that sensors and switches read, while the new modules own the logic, persistentStores, and lifecycle. New files:non_responsive_tracker.py— non-responsive battery detection and 5-minute exclusion windows.alarm_notifier.py— alarm/fault bit-delta detection and HA persistent-notification formatting (extracted from the coordinator; the previous-bitmask state lives with the notifier now).weekly_full_charge.py— weekly full charge state, persistence and register-write orchestration; bundled persistence format (weekly + delay_unlocked + solar_t_start) preserved for backward compatibility.consumption_tracker.py— consumption history, daily energy accumulators (household + solar production), solar-timing detection (t_start/t_end/solar-noon), recorder backfill, and the 23:55 local-time daily capture. TheDEFAULT_BASE_CONSUMPTION_KWHsentinel, timezone-aware recorder queries, and the rule that_capture_from_historyreplaces default entries instead of appending are all preserved.
[1.7.0] - 2026-04-25¶
Added¶
- Per-device enable/disable switch for excluded devices: Each excluded device now gets a dedicated
{Device} – Enabledswitch entity. Turning it off removes the device from all power calculations (charge offset, adjustment, and EV-charger state checks) without deleting it from the configuration. The state is persisted in config entry data and survives HA restarts. Useful for temporarily pausing a device (e.g. a car charger that is unplugged for days) without having to re-enter the options flow. - Cell balance monitor: New optional feature (enabled in the weekly full charge configuration step) that tracks the voltage spread between the strongest and weakest cell after each weekly full charge. When enabled, the battery is held at rest for 15 minutes after reaching 100 % SOC so the cell voltages can settle to their true open-circuit values; then a formal reading is taken. Thresholds are fixed at 50 / 100 / 150 mV (green / yellow → orange / red). An orange reading triggers an additional 2.5-hour passive balancing hold before a follow-up reading; red on two consecutive full charges fires a "possible degraded cell" alert.
- Opportunistic readings: Outside the weekly full charge day, if the battery reaches 100 % SOC and power is already below 50 W, a lightweight reading is taken without holding discharge — useful on days with heavy solar generation. Limited to once every 24 hours.
- Five new sensor entities per battery:
Cell Voltage Delta (mV),Balance Status(green / yellow / orange / red),Delta Trend(rising / stable / falling),Last Balance Read(timestamp), and4-Week Average Delta (mV). Values are restored from persistent storage on HA restart. - Rising-trend notification: If the 4-week rolling average of formal readings exceeds 75 mV and the trend is rising, a persistent notification is sent to prompt the user to monitor battery health.
- Discharge blocking during OCV wait: While waiting for the cell voltages to settle, the battery is prevented from discharging so that the reading reflects true open-circuit conditions. Discharge resumes automatically once the reading is complete (or after the 2.5-hour orange hold).
- Balance history persistence: All readings (formal, follow-up, opportunistic) are stored in a per-entry JSON store and survive HA restarts and reloads.
- WiFi Signal Strength sensor: New diagnostic sensor (register 30303, dBm) available for all battery versions. Disabled by default.
- User Work Mode select: New select entity (register 43000) available for all battery versions, with options
Manual,Self Consumption(anti feed-in), andAI Optimization(trade mode). Disabled by default. Fully translated into EN, ES, DE, FR, and NL.
Removed¶
- Excluded Devices diagnostic sensor: The
Excluded Devicessensor (which reported the count and details of excluded devices as attributes) has been removed. The same information is now directly visible through the per-device{Device} – Enabledand{Device} – Solar Surplusswitch entities added in this release.
Changed¶
- Predictive charging: grid-only SOC target: When predictive charging activates, the battery is no longer charged all the way to
max_socfrom the grid. Instead, the integration calculates how much solar will remain after covering expected household consumption (solar surplus) and charges from the grid only the portion solar cannot cover:
solar_surplus = max(0, solar_forecast − estimated_consumption)
grid_charge = max(0, gap_to_max − solar_surplus)
target_soc = current_soc + grid_charge / capacity × 100
where gap_to_max is the kWh distance from the current SOC to max_soc. Solar output in excess of household demand charges the battery the rest of the way during the day. If solar surplus equals or exceeds the gap, no grid charging is needed and the trigger condition (energy_deficit > 0) already prevents it. Applies to all three modes (time slot, dynamic pricing, real-time price).
In systems with multiple batteries at different SOC levels, the grid charge is distributed proportionally to each battery's individual gap to max_soc. A battery further from full receives a larger share of the grid charge; a battery already close to full relies mostly on solar for its remainder. This avoids overloading any single battery from the grid and minimises total grid import.
- Options menu label: "No-discharge time slots" → "Discharge time slots": The menu entry in the options flow was misleading — it read as a window where the battery is prevented from discharging, but the feature actually defines the only windows where discharging is allowed. The label now matches the step title and description already used inside the step. Updated in all six translation files (
en,es,de,fr,nl,strings). - WiFi Status and Cloud Status moved to diagnostic category: Both binary sensors now appear under Diagnostics in the HA device page. The
category: diagnosticfield was already present in their definitions but was not being read by the binary sensor platform — fixed by applying the same pattern already used for regular sensors. - Version and connectivity sensors disabled by default: Software Version, BMS Version, EMS Version, Comm Module Firmware, WiFi Signal Strength, WiFi Status, and Cloud Status are now disabled by default across all hardware variants. They can be enabled individually from the entity registry if needed.
- Charge Hysteresis binary sensor now translated: The sensor was previously named
{Battery} Charge Hysteresis Activein hard-coded English. It now uses a translation key (charge_hysteresis) andhas_entity_name, so the name is rendered in the user's language without redundant "Active" suffix. Updated in all six translation files. - Backup Offgrid Threshold moved from Configuration to Controls: The number entity is no longer grouped under Configuration in the HA device page and appears directly in the main controls section.
- Cell voltage sensors always display 3 decimal places: Max Cell Voltage and Min Cell Voltage previously dropped trailing zeros (e.g.
3.2 Vinstead of3.200 V). The display precision is now set to 3 decimal places, matching the measurement resolution. - Several per-battery sensors moved to diagnostic category: The following sensors are now grouped under Diagnostics in the HA device page: Fault Status, Alarm Status, Round-Trip Efficiency, Battery Cycle Count, Cell Delta, Delta Average (4-week), Balance Status, Delta Trend, and Last Balance Read.
- Integration Status sensor moved to diagnostic category: The sensor is now grouped under the Diagnostic section in the HA device page, keeping the main entity list focused on operational entities.
- Time Slot switch now translated: The switch was previously named
Time Slot {N}in hard-coded English. It now uses a translation key (time_slot) with a{slot_number}placeholder, so the name is rendered in the user's language. Updated in all six translation files (en,es,de,fr,nl,strings). - Solar Surplus switch renamed and translated: The switch was previously named
Solar Surplus – {device}(hard-coded English). It is now{device} – Solar Surplus, using a translation key (excluded_device_solar_surplus) with a device placeholder. The name inversion ensures all switches for the same excluded device sort together in the HA entity list —{device} – Enabledimmediately followed by{device} – Solar Surplus— instead of all Solar Surplus switches grouping away from the Enabled ones. All six translation files (en,es,de,fr,nl,strings) have been updated. - Number entity names aligned with feature prefixes: Several number entities were renamed so that related entities sort together in the HA entity list, examples:
delay_safety_margin_min:Charge Delay Margin→Charge Delay Safety Margindelay_soc_setpoint:Charge Delay SOC→Charge Delay SOC Setpoint
Fixed¶
- Price-based discharge control ignored while battery is in steady-state discharge: Even after the handler correctly set
_price_based_discharge_blocked, the enforcement at line 5121 could be skipped by the deadband or stale-sensor early-returns in the main control loop. When the system was in equilibrium (grid ≈ 0 W, battery actively discharging) and the price dropped below the daily average, the flag was set but the function exited through the deadband path before applying it — leaving the battery discharging until the load changed enough to exit the deadband. Fixed by inserting a dedicated enforcement block immediately after the predictive-charging dispatcher, before any sensor-dependent early-returns. - AC Offgrid Power wraps to ~65000 W when solar panels are connected to the backup port: On firmware 148+, the battery reports negative values on the AC Offgrid Power register when solar panels feed power through the backup port. The register was decoded as
uint16, causing a 16-bit wraparound (e.g. −100 W → 65436 W). This falsely triggered the backup-active guard, stopping PD control entirely. Fixed by decoding the register asint16for v2 and v3 hardware variants. - Price-based discharge control bypassed in dynamic pricing mode: When the system was inside a cheap-slot window but decided not to activate grid charging (informational schedule with no deficit, charge delay active, or pre-evaluation skip), the handler returned early before setting
_price_based_discharge_blocked. The battery would then discharge even with "Discharge only when price exceeds daily average" enabled. Fixed by converting the three early-return paths into fall-through branches so the discharge control block always runs. enabled_by_default: falsewas ignored: Modbus register entities (sensor, select, binary sensor, switch, number, button) did not propagate theenabled_by_defaultflag from their definition to the HA entity registry. All Modbus entity classes now set_attr_entity_registry_enabled_defaultfrom the definition, so entities markedenabled_by_default: falseare correctly disabled in the registry for new installations.- User Work Mode displayed wrong option due to battery firmware bug: The battery's Modbus register for user work mode returns an incorrect value on readback. The integration now uses a persistent shadow state — the last value written is stored in config entry data and used for display instead of the polled register value. The shadow survives HA restarts. The register is still written correctly so the battery operates in the selected mode.
- Predictive charging starts despite switch being off (time slot mode): When the predictive charging switch was turned off during an active time slot, charging stopped correctly. However, when the slot ended, the override flag was silently reset to
Falsein memory — so on the next slot cycle the switch appeared on again and charging started. The auto-reset on slot exit has been removed; the override now persists until the user explicitly turns the switch back on. - Predictive charging starts despite switch being off (real-time price mode): The real-time price handler never consulted
predictive_charging_overriddenbefore activating charging. If the price dropped below the threshold while the switch was off, charging would start regardless. The handler now checks the override at the top of every cycle and stops any active charging immediately if it is set. - v3 batteries: weekly full charge interrupted after HA restart when charge delay was enabled: After a Home Assistant restart on the weekly full charge day,
_charge_delay_unlockedwas correctly restored from persistent storage but was then immediately overwritten toFalseby the daily-reset block, because_charge_delay_last_dateis an in-memory variable that always starts asNoneafter a restart. For v2 batteries this had no visible effect (the hardware cutoff register at 100 % remained set regardless of software state), but for v3 batteries the software-enforcedeffective_max_socdropped back to the user's configured limit, stopping the charge mid-way. Fixed by only resetting_charge_delay_unlockedon a genuine day change (_charge_delay_last_date is not None); on the first cycle after a restart the restored value from storage is preserved. - Dynamic pricing: Nordpool prices off by factor 100: The Nordpool price parser was dividing sensor values by 100, based on a wrong assumption that the integration reports in ct/kWh. The Nordpool integration reports directly in €/kWh (e.g. 0.072), so the stored slot prices were 100× too small (0.00072). This caused cheapest-slot selection to be correct (relative order unchanged) but made the
max_price_thresholdfilter never trigger, and broke the price-based discharge control — which compares the live sensor price (€/kWh scale) against the daily average computed from slots (was ct/kWh scale). Fixed by removing the division. - Excluded devices not subtracted from household consumption history: The daily household energy accumulator (used by predictive grid charging to estimate typical consumption) integrated the raw household consumption sensor reading without accounting for excluded devices. Devices marked as included in consumption sensor (i.e. the home sensor sees them, but the battery is configured to ignore them) were counted toward the consumption the battery was expected to cover, causing predictive charging to overestimate demand and charge from the grid unnecessarily. Fixed by applying the same per-device correction at accumulation time: power from
included_in_consumption=Truedevices is subtracted from the reading and power fromincluded_in_consumption=Falsedevices (not visible to the home sensor but covered by the battery) is added. The same correction is now applied in the historical backfill path (_backfill_household_from_history), which queries each excluded device's recorder history for past days and adjusts the integrated value accordingly — so consumption history populated after a restart is also accurate.
[1.6.6] - 2026-04-16¶
Added¶
- PD controller advanced step in initial config flow: The setup wizard now includes a final step for advanced PD controller tuning, matching what was already available in the options flow. After the peak shaving step, users are asked whether they want to configure the PD parameters (Kp, Kd, deadband, max power change, direction hysteresis, minimum charge/discharge power). Choosing "No" applies the defaults automatically; choosing "Yes" opens the parameter form. This ensures expert users can tune the controller at first installation without needing to re-enter the options flow afterwards.
- Options flow menu: The options flow now uses a menu instead of a linear wizard, allowing users to jump directly to any section — sensors, batteries, time slots, excluded devices, predictive charging, weekly full charge, solar charge delay, peak shaving, or PD controller — without stepping through unrelated screens.
- Solar forecast safety margin: A configurable kWh buffer can now be added to the consumption forecast when deciding whether to charge from the grid. Useful when your solar forecast integration (e.g. forecast.solar with multiple string arrays) tends to be optimistic. Set in the predictive charging config step for all three modes (time slot, dynamic pricing, real-time price) and also adjustable at runtime via the new Solar Forecast Safety Margin number entity under Marstek Venus System — changes take effect immediately without restarting. Defaults to 0 kWh (no change to existing behaviour). Capped at total battery capacity as a guardrail.
Changed¶
- Peak shaving SOC threshold minimum lowered to 20 %: The minimum selectable value for the peak shaving SOC threshold has been reduced from 30 % to 20 %, both in the setup/options flow slider and in the corresponding number entity (
number.marstek_venus_system_capacity_protection_soc_threshold). This allows configuring peak shaving to activate at lower SOC levels than previously permitted.
Fixed¶
- Weekly full charge mid-charge abort restored 100% instead of original max SOC: When the user changed the weekly full charge day (or disabled the feature) while a v2 battery's cutoff register was already set to 100 %, the hardware restore wrote 100 % back instead of the user's configured limit. Root cause: each coordinator data refresh reads back the
charging_cutoff_capacityregister and updatescoordinator.max_socfrom it — so once the register was set to 100 %,max_socbecame 100 % and the restore code read that same 100 % value. Fixed by saving each coordinator's originalmax_socinto_weekly_charge_saved_max_socimmediately before writing the 100 % register, and using that saved value (with a fallback tocoordinator.max_soc) in both the mid-charge abort path and the normal completion restore path. - Weekly full charge day change via options flow had no immediate effect: Changing the weekly full charge day (or enabling/disabling it) in the options flow updated the stored config but not the in-memory controller variables (
weekly_full_charge_day,weekly_full_charge_enabled), because those were only read at startup. The hot-reload listener (update_pd_parameters) was missing these fields. As a result,_is_weekly_full_charge_active()kept checking against the old day and never returned True until HA restarted — meaning charge hysteresis was not overridden and batteries would not start charging. Fixed by addingweekly_full_charge_enabledandweekly_full_charge_dayto the hot-reload path. When the day changes, the completion flags are also reset so the new day starts fresh. - Weekly full charge not stopped when day changed mid-charge: If the weekly full charge was already running (hardware cutoff register written to 100 % for v2 batteries) and the user changed the charge day (or disabled the feature) via the options flow, the hardware register was never restored. The battery would continue charging toward 100 % via hardware even though the software no longer considered a weekly charge active. Fixed by setting a
_weekly_charge_needs_restoreflag when a mid-charge abort is detected (registers written, charge not yet complete, day changed or feature disabled). On the next controller cycle_handle_weekly_full_charge_registers()detects the flag, restores the cutoff register tomax_socon v2 batteries, and clears the flag. Additionally,_save_weekly_charge_state()is now called immediately after the registers are set to 100 %, so that an HA restart mid-charge still exposesregisters_written = Truefrom storage, allowing the abort logic to trigger correctly when the day is subsequently changed. - Charge hysteresis threshold still incorrect after weekly full charge (regression from 1.6.1 fix): The 1.6.1 fix introduced
_hysteresis_base_socto track the actual SOC that triggered hysteresis, but the weekly-charge completion handler re-enabled hysteresis (_hysteresis_active = True) without setting_hysteresis_base_soc. If the battery SOC had already dropped belowmax_socby the time the next controller cycle ran, the normal capture logic (if current_soc >= max_soc) did not fire, leaving_hysteresis_base_soc = Noneand falling back tomax_socas the base — reproducing the original bug (threshold = 70 % instead of 90 %). Fixed by setting_hysteresis_base_socto the current SOC (~100 % after a full charge) at the same point the completion handler re-enables hysteresis. - Clearing optional entity selector fields in options flow had no effect: Clicking the × button to remove a configured sensor (e.g. Daily average price sensor, Solar forecast sensor, Household consumption sensor) and saving appeared to clear the field in the UI, but the old value was silently restored. Root cause: HA validates
user_inputagainstdata_schemausing voluptuous, andvol.Optional(key, default=current_value)refills any absent key with the default — so a cleared entity selector (which sends the key as absent) was treated as "unchanged". Fixed by replacingdefault=withdescription={"suggested_value": ...}for all clearable entity selector fields in the options flow. The UI still pre-fills with the existing value, but clearing it now correctly persistsNoneto the config.
[1.6.5] - 2026-04-15¶
Fixed¶
- Predictive charging slot blocked normal battery discharge: When the predictive grid charging time slot overlapped with the normal discharge window, the integration sent conflicting commands to batteries every cycle — first idle (0 W) from the predictive handler, then discharge from the PD controller. Batteries could not ramp up, triggering the non-responsive detection after 3 consecutive failures and excluding them from the pool for 5 minutes. This affected three scenarios: (1) the 5-minute entry wait before evaluation, (2) the user override, and (3) after all batteries reached max_soc. Fixed by never sending idle commands from the predictive charging handler when
grid_charging_activeis False — PD control handles normal operation instead. When charging completes (max_soc reached) or the user overrides,grid_charging_activeis deactivated and PD re-initializes cleanly on the same cycle with no idle gap. The same fix was applied to the dynamic pricing override path.
[1.6.4] - 2026-04-14¶
Added¶
-
Integration Status sensor: New sensor (
sensor.marstek_venus_system_integration_status) showing at a glance what the integration is currently doing. It reflects the highest-priority active mode and updates every poll cycle. Possible states:Charging from Grid(predictive grid charging active),Weekly Full Charge(charging to 100 %),Charge Delayed,Waiting for Solar,Charging to Setpoint,Capacity Protection,No-Discharge Window(inside a configured time slot),Charging,Discharging,Standby(within deadband, no action needed),Manual Mode, andInitializing. Fully translated into EN, ES, DE, FR, and NL. -
Dynamic pricing — evening re-evaluation: A new late-day check activates once per day when solar production is winding down (1.5 h before the estimated T_end, or at 16:00 on days with no detected solar start). If the batteries have not reached their target SOC and the remaining solar is insufficient to cover the gap, the integration searches for cheap price slots between now and midnight and schedules them for grid charging. New slots are merged into the existing morning schedule if one exists, or a new schedule is created. A dedicated persistent notification ("Predictive Charging: Evening re-evaluation") is sent listing the slots added and the estimated deficit. This catches the common scenario where the morning forecast was optimistic (more clouds or more household consumption than expected) and the batteries end up under-charged by end of day.
-
Solar production accumulator: When the household consumption sensor is configured, the integration now integrates real-time solar production throughout the day (
Solar_W = House_W + Battery_Net_W − Grid_W). The accumulated value (solar_production_today_kwh) is exposed as a diagnostic attribute onbinary_sensor.marstek_venus_system_predictive_charging_activeand is used by the charge delay logic to compute remaining solar more accurately: instead of the sinusoidal fraction-of-day estimate, it subtracts actual production from the forecast (remaining = forecast − produced_so_far). The accumulator survives restarts and reloads via the same persistent storage used by the household consumption accumulator. -
Household consumption sensor: New optional power sensor (W or kW) that can be configured at the main sensor step. When set, the integration integrates the sensor's power reading over time to compute daily household energy consumption (kWh) during the solar+battery window (i.e. outside the configured charging time slot). This replaces the previous estimation method — which derived consumption from battery discharge + grid import at min SOC — with real measured data. The improvement is most visible in weeks with high solar production, where the old method systematically underestimated demand and could skip necessary grid charging.
-
Predictive charging and charge delay automatically use the real consumption data when the sensor is configured. No additional setup is needed — the switch between sources is transparent.
- New diagnostic attributes on
binary_sensor.marstek_venus_system_predictive_charging_active:consumption_source(household_sensororbattery_discharge),household_consumption_sensor(entity ID),household_consumption_battery_window_kwh, andhousehold_accumulator_date. - The accumulator survives HA restarts and reloads via persistent storage (previously used binary sensor attributes, which were lost on config entry reload).
- Backfill on startup queries the recorder for the configured sensor's history to fill in consumption data for past days, using the same time-window filter.
- Documentation updated with a step-by-step guide on how to create the household consumption sensor as a Template helper (combining grid, solar, and battery power readings).
Fixed¶
- Dynamic pricing notification showed wrong "needed" value: When no grid charging was required, the "Available ≥ X kWh needed" line always displayed the same value on both sides due to a broken formula (
abs(deficit) + consumptionalgebraically equalstotal_availablewhen no deficit exists). Fixed to show the actual average consumption. A second notification path (no price slots found) also omitted the numeric consumption value entirely ("≥ needed"); fixed to include it.
[1.6.3] - 2026-04-12¶
Fixed¶
- Peak shaving ignored excluded devices with "included in consumption": When a special device was configured with "consumption is included in the household consumption sensor", its power was subtracted from the grid reading before peak shaving evaluated the house load. This meant peak shaving never saw the device's consumption and would not discharge the battery to shave peaks caused by it. Peak shaving now calculates the estimated house load using the real grid reading (including excluded devices) and overrides the device exclusion when actively shaving or conserving, so the battery discharges to keep total grid import below the configured limit regardless of device exclusion settings.
- Max price threshold "expected str" error on reconfigure: Reopening the predictive charging config (dynamic pricing or real-time price) after a threshold was already saved failed with "expected str". The stored value was a
float, but theTextSelectorschema expected astrdefault. Fixed by converting the stored value tostr()before passing it as the schema default.
[1.6.2] - 2026-04-10¶
Fixed¶
- Max price threshold rejected small decimal values: The
NumberSelectorinput for the max price threshold silently rounded values like0.0008to0due to browser-level precision loss in the HTML number input. Replaced with aTextSelector(free-text field) that preserves the exact value entered. Applies to both dynamic pricing and real-time price modes in the setup and options flows. Existing installations that stored a rounded value will need to re-enter the correct threshold in the options flow. Note: HA's attribute display in entity cards may round the value visually (e.g.0.008shown as0.01), but the full-precision value is stored correctly and can be verified in Developer Tools → States. - Dynamic pricing config flow did not advance: The config flow for dynamic pricing mode failed to proceed past the pricing step because the
NumberSelectorcould not handle comma-separated decimals used in European locales. TheTextSelectorreplacement accepts both comma and dot as decimal separators.
[1.6.1] - 2026-04-10¶
Changed¶
- Peak shaving renamed from "Capacity Protection": The peak shaving feature has been renamed across all UI strings and translations (EN, ES, DE, FR, NL) to better reflect its actual function — limiting grid import peaks by discharging the battery only when consumption exceeds a configured threshold. The previous name "Capacity Protection" was misleading, suggesting the feature protects the battery's physical capacity. Internal configuration keys are unchanged, so existing installations are not affected.
Fixed¶
- Charge hysteresis threshold incorrect after weekly full charge: After a weekly full charge day completed (battery reaching 100 %), the hysteresis logic switched back to the configured max SOC (e.g. 80 %) as its reference instead of the 100 % target actually reached. This caused the re-charge threshold to be set at 70 % (80 % − 10 % hysteresis) rather than the expected 90 % (100 % − 10 %), blocking the battery from recharging even when well below the intended threshold. Fixed by tracking the SOC level that activated the hysteresis (
_hysteresis_base_soc) and using it — rather thanmax_soc— as the base for the threshold calculation. The tracked value is cleared when the hysteresis deactivates, so normal charging days are unaffected. - Excessive state updates in HA database (#96): Removed
force_update: Truefrombattery_powerandac_powersensor definitions across all battery versions (v2, v3, vA, vD). With this flag enabled, HA recorded a new database entry on every poll cycle (~every 2 seconds) even when the value hadn't changed, generating ~43,000 state entries per sensor per day and causing significant database growth. Now HA only records a state change when the value actually differs from the previous one. - Aggregate sensors double-triggering state writes: Aggregate sensors (multi-battery setups) had both
should_poll = Trueand coordinator listener callbacks, causing redundant state writes on every poll cycle. Changed toshould_poll = Falsesince the listener callbacks are sufficient.
[1.6.0] - 2026-04-07¶
[!IMPORTANT] Breaking change — solar forecast sensor: The integration now uses today's solar forecast instead of tomorrow's. If you have the Solar Charge Delay or Predictive Grid Charging features configured, you must update the forecast sensor field to point to your integration's today sensor (e.g.
sensor.solcast_pv_forecast_forecast_today) instead of the tomorrow sensor. The stored overnight forecast is no longer used and will be ignored on upgrade.
Added¶
- Solar charge delay SOC setpoint: New optional feature that splits the morning charge into two phases. A dedicated checkbox enables it; when enabled, a slider (12–90 %, default 50 %) sets the target SOC. Below the setpoint the battery charges freely without any delay applied; once all batteries reach the setpoint the solar delay logic activates as usual. This guarantees a minimum charge level on deeply discharged batteries before the solar energy decision is made, while still maximising self-consumption for the remaining charge. The minimum is 12 % — the Venus battery minimum discharge SOC. Configurable during setup and from the options flow; the setpoint value is also exposed as a number entity (Charge Delay SOC Setpoint) on the system device card for runtime adjustment.
Changed¶
- Solar forecast now uses today's live value: The integration no longer captures and stores tomorrow's forecast at 23:00. Instead, it reads the configured sensor live on every evaluation. Today's forecast is updated multiple times throughout the day by most solar forecast integrations (Solcast, Forecast.Solar, etc.), becoming progressively more accurate as actual weather conditions develop. This removes the 23:00 nightly capture task and eliminates the stale-forecast problem that occurred after an HA restart or when the nightly window was missed.
- Charge delay threshold is now a dynamic energy balance: The previous fixed threshold (forecast < 1.5 × battery capacity → unlock) has been replaced with the same energy-balance calculation used by predictive charging:
(usable_energy + forecast) < avg_daily_consumption. This takes the current battery SOC into account — on a partly-charged morning the threshold is effectively lower — and uses real historical consumption data instead of an arbitrary capacity multiplier. The balance is only recalculated when the forecast value changes by more than 0.05 kWh, avoiding unnecessary computation on every controller cycle. - Charge delay re-evaluates forecast changes in real time: Because the forecast sensor is now read live, any intra-day update is automatically picked up. If the forecast degrades to the point where the energy balance turns negative (grid charging needed), the delay unlocks immediately and morning charging starts. If the forecast improves while the delay is still active, the system keeps waiting for solar. Once the delay is unlocked, it stays unlocked for the day.
- Predictive charging (Time Slot mode) — initial evaluation delayed 5 minutes: The energy balance evaluation no longer runs at the exact instant the slot boundary is crossed. Instead the controller waits 5 minutes with the battery in idle before evaluating. This avoids a race condition when the slot starts at 00:00: at that moment the forecast sensor may still be reporting yesterday's final value before the integration resets it for the new day. The 5-minute hold ensures the sensor has updated before the charging decision is made.
- Predictive charging (Time Slot mode) — pre-evaluation notification removed: The notification sent 1 hour before the slot start has been removed. A single notification is now sent at the moment the evaluation fires (5 minutes into the slot), reporting the actual decision: charge or no charge needed.
[1.5.4] - 2026-04-06¶
Added¶
- Configurable backup offgrid load threshold: Each battery now has a user-configurable threshold (0–500 W, default 50 W) that determines when an offgrid load is treated as an active backup event. Batteries with small permanent loads on the offgrid port (e.g. a PoE switch, router, or cameras) were previously excluded from PD control indefinitely because any non-zero offgrid reading triggered backup mode. The threshold can be set during initial setup (per-battery limits page in the config flow) and adjusted at any time via a Backup Offgrid Threshold number entity on the battery device card — no reconfiguration required. Changes take effect immediately and survive restarts.
- EV charger without power telemetry (new excluded-device option): A new checkbox — EV charger without power telemetry — is available when configuring an excluded device. When checked, the selected sensor is treated as a state sensor rather than a power sensor. The controller monitors it and reacts when the state changes to any recognised charging string (
Charging,Cargando,Cargando VE,Cargando Vehículo, and case-insensitive variants). On detection, the battery enters a 5-minute full pause (both charge and discharge set to 0 W, PD state frozen) so the vehicle receives the maximum available current without competition. Once the pause expires the battery may still charge from solar surplus but will never discharge while the EV remains in a charging state. Normal operation resumes automatically when the sensor leaves the charging state.
Fixed¶
Active Batteriessensor always showingIdlewhen only one battery is available:_select_batteries_for_operationreturned early for the single-battery case without updating the_active_charge_batteries/_active_discharge_batteriestracking lists, so the diagnostic sensor always saw empty lists and displayedIdleregardless of actual charge/discharge state.- Backup exclusion logic not applied consistently during shutdown: The
async_unload_entryshutdown handler used a hardcoded!= 0check for the offgrid power sensor instead of the per-battery threshold, meaning batteries with small permanent offgrid loads would incorrectly skip shutdown register writes and be left in an uncontrolled state on integration unload.
Thanks to @hdcasey for reporting and fixing the first two issues.
[1.5.3] - 2026-04-05¶
Added¶
- Proactive battery alarm notifications (v2 only): The integration now monitors the
Alarm Status(register 36000) andFault Status(register 36100) registers every 5 seconds and sends a Home Assistant persistent notification the moment a new alarm or fault bit is set. The notification is titled "Battery Fault/Warning: \<name>" and lists both the newly triggered conditions and all currently active ones. When all alarms and faults clear, the notification is automatically dismissed. Notifications are scoped per battery (separate notification ID per battery name) so multi-battery setups report each device independently. v3, vA and vD batteries do not expose these registers via Modbus and are not affected. - System Alarm Status sensor (
sensor.marstek_venus_system_alarm_status, v2 only): New sensor on the Marstek Venus System device that aggregates the alarm state across all batteries. State isOKwhen no conditions are active,Warningwhen one or more alarm bits are set but no fault bits, andFaultwhen at least one fault bit is active on any battery. Theextra_state_attributesdictionary exposes per-battery detail — each key is the battery name and the value is a list of active condition labels (e.g.[Fault] BAT Overvoltage,[Alarm] Fan Abnormal Warning) — so the exact source and nature of each event is visible without opening individual battery sensors. The sensor is only populated for v2 batteries. - Shared alarm/fault bit-description constants (
FAULT_BIT_DESCRIPTIONS,ALARM_BIT_DESCRIPTIONSinconst.py): The 32-bit description maps previously duplicated inline insideSENSOR_DEFINITIONSare now standalone module-level dicts. The existing per-batteryAlarm StatusandFault Statussensors reference these constants, eliminating the duplication and making future updates to alarm labels a single-point change. - Solar Surplus switch per excluded device: Each excluded device now gets a dedicated switch entity (
Solar Surplus – <device name>) that toggles theallow_solar_surplusflag at runtime without entering the options flow. When ON, the battery yields solar surplus to that device (e.g. EV charger) instead of charging itself — solar goes to the device first and the battery will not discharge to power it either. When OFF, the battery charges normally with any available solar surplus. The switch is controllable from HA automations, enabling priority changes based on schedules, battery SOC, EV connection state, or any other condition.
Changed¶
- strings.json now in English: The base translation file (
strings.json) has been converted to English to serve as the proper fallback language for Home Assistant installations using a language without a dedicated translation file.
Fixed¶
- German translation fix: Corrected
round_trip_efficiency_totallabel from "Gesamte Hin- und Rückfahreffizienz" to "Gesamtwirkungsgrad".
[1.5.2] - 2026-04-04¶
Added¶
- Configuration Summary diagnostic sensor: New hidden sensor (
Configuration Summary) on the Marstek Venus System device that exposes the complete integration configuration as entity attributes. Intended for support purposes — enable it from the entity registry, then share the state card to provide a full picture of the system setup at a glance. Attributes are organised in sections: general (grid sensor, meter inversion, solar forecast sensor), per-battery settings (name, IP, version, power limits, SOC thresholds, hysteresis), time slots, predictive charging (mode, time slot, contracted power, price sensor and thresholds), weekly full charge, charge delay, capacity protection, PD controller parameters, and excluded devices. The sensor is disabled by default and categorised as diagnostic. - Backup function exclusion from PD control: When the Backup Function switch is enabled on a battery and the AC Offgrid Power sensor reports a non-zero value, that battery is automatically excluded from PD controller writes. Having the switch on alone is not sufficient — the battery must actually be providing offgrid power. No power commands, force mode changes, or configuration register writes are sent while both conditions are met. A 5-minute cooldown is applied after the offgrid load drops back to 0 W, keeping the battery excluded until the window expires to avoid sending commands immediately after a backup event ends. Turning the switch off clears the cooldown immediately. The battery continues to be polled normally so all read-only sensors remain up to date. This applies to all write paths: normal PD control, predictive grid charging, weekly full charge register writes, and the shutdown sequence. The AC Offgrid Power sensor (register 32302) has been added to v3, vA, and vD battery definitions so that the two-condition check works across all versions.
[1.5.1] - 2026-04-01¶
Added¶
- Price-based discharge control for Dynamic Pricing and Real-Time Price modes: New optional checkbox in both pricing mode configurations that restricts battery discharge to periods when the current electricity price exceeds a configurable threshold. When enabled, the battery only discharges if the live price is strictly above the threshold (fixed
max_price_thresholdor a daily average price sensor). If discharge time slots are also configured, both conditions must be met — the price check acts as an additional gate on top of the existing time window restriction. Dynamic Pricing mode gains a new optionaldp_average_price_sensorfield (equivalent to the existingaverage_price_sensorin Real-Time Price mode) to support a dynamic discharge threshold. - Grid meter kW auto-detection and inverted sign support: The controller now automatically detects if the grid meter sensor reports in kW (via its
unit_of_measurementattribute) and converts to Watts internally — no user action required. A new "Inverted meter sign" toggle has been added to the initial setup and options flow for meters that use the opposite sign convention (positive = export, negative = import).
[1.5.0] - 2026-04-01¶
Added¶
- New sensors for all battery versions (v2/v3/vA/vD): Added device information sensors (
device_name,sn_code,software_version,bms_version,vms_version,ems_version,comm_module_firmware,mac_address) where supported by each battery version. Added cell voltage sensors (max_cell_voltage,min_cell_voltage) for v3/vA/vD. Added WiFi and Cloud connectivity binary sensors (wifi_status,cloud_status) for all versions. - Battery Cycle Count sensor (v3/vA/vD): Direct register-based cycle count sensor reading from the battery firmware for v3, vA, and vD batteries.
- Calculated Battery Cycle Count sensor (all versions): New derived sensor (
battery_cycle_count_calc) available for all battery versions, calculated as(total_discharge + total_charge) / 2 / battery_capacity. Provides cycle count estimation for v2 batteries that lack a direct register, and a cross-check for other versions. - Dynamic Pricing Mode for Predictive Grid Charging: New charging mode that automatically selects the cheapest hours of the day to charge the batteries from the grid. Supports Nordpool, PVPC (ESIOS REE, Spain), and CKW (Switzerland). Configured through a new
predictive_charging_modestep (choose between Time Slot or Dynamic Pricing) and adynamic_pricing_configstep (price integration type, price sensor, optional max price threshold, and ICP contracted power). - Automatic cheapest-hour selection: The controller calculates the energy deficit each day at 00:05 using the effective charge power (
min(ICP, total battery charge capacity)), then picks the cheapest hours from the price forecast. When no deficit exists, the cheapest equivalent hours are still selected as informational reference. - Max price threshold filter: Optional ceiling that prevents charging even during cheap hours if prices exceed the configured limit. Unit matches the sensor (€/kWh for Nordpool/PVPC, CHF for CKW).
- Daily 00:05 evaluation with retry logic: Evaluation runs just after midnight when price data for the day is already available. If price data is still unavailable at 00:05, the controller retries every 15 min within the first hour of the day. A
no_price_dataerror is surfaced in the config flow if the selected sensor lacks the expected attributes. - Startup evaluation on integration restart: If Home Assistant restarts after the 00:05 window and no evaluation has been done yet for the current day, the controller runs a one-time evaluation automatically during startup (after a 15-second delay to allow the data coordinator to complete its first poll). This ensures the daily schedule is always built, even when HA is restarted mid-morning. The evaluation only considers slots up to 23:59 of the current day — tomorrow's slots are left to the normal 00:05 evaluation.
price_data_statusdiagnostic attribute: Thepredictive_charging_activebinary sensor exposes aprice_data_statusattribute showing whether the price sensor is being read correctly:ok (N slots),sensor_unavailable,no_slots, ornot_evaluated.predictive_charging_activebinary sensor — dynamic pricing attributes: Always exposes the full evaluation result —charging_neededflag, selected hours with individual prices, average price, estimated cost, and evaluation timestamp. The schedule persists throughout the day it applies to (not cleared at midnight).- Real-Time Price mode for predictive grid charging: New third charging mode that reads the current electricity price every controller cycle (~2.5 s) and activates or deactivates grid charging immediately when the price crosses a configured threshold. Unlike Dynamic Pricing (which pre-selects the cheapest hours at 00:05), this mode requires no overnight evaluation and no price forecast — it reacts purely to the live price. Supports any HA sensor that exposes the current period price as its state (PVPC, Nordpool, CKW, or any other integration). Optionally accepts a daily average price sensor as a dynamic threshold instead of a fixed value, and evaluates the same solar/battery energy balance as other modes before starting to charge.
- Solar forecast sensor optional in predictive charging: The solar forecast sensor field in both Time Slot and Dynamic Pricing configuration steps is now optional. Users without solar panels can leave it empty — the system will activate grid charging whenever the battery's usable energy is insufficient to cover expected daily consumption (same conservative logic already used when the sensor is unavailable or reports an error). Users with solar panels should still configure it so the system only charges when the forecast is insufficient.
- Improved daily consumption estimate when battery reaches min SOC: The system now tracks grid energy imported during periods when all batteries are at minimum SOC and the battery would otherwise be discharging (within a configured discharge slot, or always if no slots are defined). This unmet demand is accumulated in a new sensor (
Grid at Min SOC, kWh, resets at midnight) and added to the battery discharge when capturing the daily consumption figure used by predictive charging. This prevents the 7-day rolling average from underestimating consumption on days where the battery ran out before midnight, which previously caused the system to charge less than needed the following day. Grid import during intentional grid charging (predictive/dynamic pricing) is excluded from the accumulator. - Mid-day re-evaluation for dynamic pricing slots: When multiple cheap slots are selected at 00:05, the system now re-evaluates 1 hour before each subsequent slot whether charging is still needed. If the battery is sufficiently charged (solar + current SOC covers expected consumption), the slot is silently skipped. If charging is still needed, a notification is sent confirming the slot will activate. Re-evaluations are skipped automatically when a previous slot is still actively charging (back-to-back slots), and the per-day state is reset at midnight alongside the main schedule.
Fixed¶
- SOC and power limit sliders not persisted across restarts (complete fix): Changes to
Min SOC,Max SOC,Max Charge Power, andMax Discharge Powersliders were written to the battery hardware registers and updated in memory, butconfig_entry.datawas never updated. On every HA restart,async_setup_entryoverwrote the hardware registers with the original setup values, discarding any runtime changes. The partial fix in 1.4.0 (syncing coordinator attributes from polled register values) was ineffective because the startup sequence writes the stale config values to hardware before the first poll completes. Values are now persisted toconfig_entry.dataimmediately when a slider is changed, using the same pattern as the RS485 switch preference. - RS485 switch state not persisted across restarts: Disabling the RS485 Control Mode switch and restarting Home Assistant would re-enable it automatically. The user's preference is now saved to the config entry and restored on startup. The initial RS485 enable during integration load and the reconnection re-enable are both skipped when the user has explicitly disabled the switch.
- v3/vA/vD batteries not accepting write commands: Inter-register write delay in the atomic power write sequence was hardcoded to 50 ms (v2 timing). v3/vA/vD firmware requires a minimum of 150 ms between consecutive Modbus messages; writes now use the version-specific timing from
MESSAGE_WAIT_MS, matching the delay already applied to polling reads. - Non-responsive battery cooldown capped at 30 min: The exponential backoff for excluded batteries could grow to 30 minutes after repeated failures. The cap is now 5 minutes so a temporarily unresponsive battery is retried more frequently.
- Dynamic pricing schedule cleared at midnight: The
_dynamic_pricing_evaluated_datewas stored as the evaluation date (day N), causing the schedule to be wiped at midnight — before any of the selected slots (day N+1 morning) could activate. The evaluated date is now stored as the date the slots belong to, so the schedule persists until the end of that day. - Charging hours underestimated when ICP > battery charge capacity:
_calculate_charging_hours_neededused onlymax_contracted_power(ICP) as the effective charge power. If the total battery charge capacity is lower than the ICP, the actual charge rate is limited by the batteries, not the ICP — resulting in too few hours being selected and the battery not reaching the target SOC. The calculation now usesmin(ICP, total battery charge capacity). Estimated cost in the schedule and notifications is also corrected accordingly. - Consumption history always showing 6 days instead of 7: The cleanup filter used a strict
>comparison (d > today − 7), which excluded the entry for exactly 7 days ago. Replaced date-based cutoff with a trim to the 7 most recent entries ([-7:]) so the window is always exactly 7 entries. - Consumption history gaps filled with real-data average: When the startup backfill cannot find recorder data for a day (HA was down, or daily discharge was below the 1.5 kWh representativeness threshold), the missing entry is now filled with the average of the real entries already in the history instead of the fixed 5.0 kWh default. This prevents artificial inflation of the consumption estimate. On first run when no real data exists yet, 5.0 kWh is still used as a conservative bootstrap.
- Modbus read/write operations now time out on half-open sockets:
async_read_registerandasync_write_registernow wrap their underlying pymodbus calls inasyncio.wait_for(timeout=...)using the client's configured timeout (default 10 s). Previously, a hung write or read on a half-open socket would block indefinitely, preventing the coordinator from recovering after a TCP connection drop.asyncio.TimeoutErroris now treated the same asConnectionException, triggering the existing reconnect-and-retry path. - Battery SOC hidden by efficiency sensor in device card: The Round-Trip Efficiency sensor shared the
batterydevice class with the Battery SOC sensor, causing Home Assistant to display the efficiency percentage instead of the SOC in the top-right corner of the device card. The device class has been removed from the efficiency sensor.
Changed¶
- Predictive charging notifications reformatted: Both time-slot and dynamic pricing notifications now use a consistent emoji-based layout (🔋 battery, ☀️ solar, 📊 consumption, ⚡ deficit, ⏰ timing). All notifications show the effective charge power as
min(ICP, battery capacity)W (ICP: XW, batteries: YW). When dynamic pricing finds no deficit, the notification is clearly labelled as informational ("No charging will activate") and shows the cheapest reference hours without implying grid charging will occur. - Clarifying note on solar forecast sensor in secondary configuration steps: In the Time Slot, Dynamic Pricing, and Charge Delay configuration steps, the solar forecast sensor field now includes a note explaining that it is not required if the sensor was already configured in the initial setup step — it will be used automatically. The German, French, and Dutch translations of the Dynamic Pricing step were also missing this field entirely; it has been added.
Removed¶
- Unused
too_lowtranslation error key: Removed the orphanedtoo_lowerror string (previously defined in all translation files but never raised by the Python code) from EN, ES, DE, FR, and NL translations. user_work_moderemoved from vA/vD batteries: Theuser_work_modeselect entity (register 43000) is not supported on vA and vD hardware and has been removed from their definitions.
[1.5.1] - 2026-04-01¶
Added¶
- Grid meter kW auto-detection and inverted sign support: The controller now automatically detects if the grid meter sensor reports in kW (via its
unit_of_measurementattribute) and converts to Watts internally — no user action required. A new "Inverted meter sign" toggle has been added to the initial setup and options flow for meters that use the opposite sign convention (positive = export, negative = import).
[1.4.1] - 2026-03-24¶
Added¶
- Support for up to 6 batteries: The battery count slider in both the initial setup and options flow now allows selecting 1–6 batteries (previously capped at 4). No architectural changes were required as the control loop and power distribution are fully dynamic.
- Non-responsive battery detection: The control loop now detects when a battery acknowledges a discharge command (registers written correctly) but fails to deliver power. After 3 consecutive cycles with actual output below 10% of the commanded value, the battery is excluded from the active pool with a warning log entry. It is automatically retried after a cooldown period that doubles on each repeated failure (5 → 10 → 20 → 30 min cap) and resets to 5 min after a successful delivery cycle. This prevents a single non-responsive battery from destabilising the PD controller and causing the remaining batteries to oscillate.
- Non-Responsive Batteries diagnostic sensor: New sensor on the Marstek Venus System device showing which batteries are currently excluded due to non-responsive behaviour. State is
Nonewhen all batteries are healthy, or a comma-separated list of excluded battery names. Attributes expose per-battery details: exclusion status, cooldown duration, and remaining cooldown minutes. Available in EN, ES, DE, FR, NL.
[1.4.0] - 2026-03-21¶
Added¶
- Unified Solar Charge Delay: New dedicated config step (
Charge Delay) that replaces the previous weekly-charge-specific delay flag. When enabled, the delay applies every day: charging is held back while solar production is forecast to cover the required energy, and unlocked automatically once the energy balance tips. The target SOC is 100% on the configured weekly full charge day, or the configuredmax_socon all other days. Discharge in configured time slots is unaffected. - Unified charge delay config step: Two new steps in both ConfigFlow and OptionsFlow — a gate step (
Configure charge delay) and a configuration step with aSafety margin (hours)slider (1–5 h, step 0.5 h, default 1 h) and an optionalSolar forecast sensorfield (only shown if not already configured in the predictive charging step). - Charge Delay Status diagnostic sensor: New
Charge Delay Statussensor on the Marstek Venus System device, replacing the separate weekly and solar delay sensors. State reportsIdle,Waiting for solar,Delayed (~HH:MM est.),Charging allowed, orDisabled. Attributes exposetarget_soc,safety_margin_min,forecast_kwh,solar_t_start,solar_t_end,energy_needed_kwh,remaining_solar_kwh,remaining_consumption_kwh,net_solar_kwh,charge_time_h,estimated_unlock_time, andunlock_reason. Populated on every control cycle, not only when a charge attempt is gated. - Accurate estimated unlock time: The
estimated_unlock_timeattribute is now calculated as the earliest of two triggers — the time-backup threshold (T_end − charge_time − safety_margin) and the energy-balance crossing point, found via a binary search (40-iteration bisection, <1 s precision) on the sinusoidal solar production model. On good solar days, this reflects the energy-balance unlock ~1–2 hours earlier than the conservative time-backup estimate. - Capacity Protection Mode (Peak Shaving): New feature that conserves battery energy when SOC drops below a configurable threshold (30–100%). Instead of discharging to cover all household consumption, the battery only discharges to offset consumption that exceeds a configurable peak limit (2500–8000W). When house load is below the limit, the battery stays idle; when it exceeds the limit, the battery discharges only the excess. Solar charging continues unaffected. Configurable in both initial setup and options flow, with a runtime toggle switch and adjustable number entities for SOC threshold and peak limit.
- Charge Delay switch: New
Charge Delayswitch on the Marstek Venus System device to enable/disable the charge delay feature at runtime without reconfiguring the integration. Only visible when charge delay is configured. - Capacity Protection switch: New
Capacity Protectionswitch on the Marstek Venus System device to enable/disable the feature at runtime without reconfiguring the integration. State persists across restarts. - Capacity Protection Active diagnostic sensor: New
Capacity Protection Activebinary sensor (diagnostic) that turns ON when the protection is actively intervening (SOC below threshold). Attributes expose real-time diagnostic data:avg_soc,soc_threshold,peak_limit_w,estimated_house_load_w,action(shaving/conserving/charging/idle/disabled),original_target_w, andadjusted_target_w. - Capacity Protection number entities:
Capacity Protection SOC ThresholdandPeak Limit Protectionnumber entities on the system device for runtime tuning without reconfiguration. Only visible when the feature is enabled. - Charge Delay Margin number entity: New
Charge Delay Marginslider on the Marstek Venus System device to adjust the safety margin at runtime without reconfiguring the integration. Displayed in hours (1–5 h, step 0.5 h); stored internally in minutes. - Entity name and state translations: All system-level entities (switches, sensors, binary sensors) now use Home Assistant's translation system. Entity names (
Manual Mode,Charge Delay,Discharge Window, etc.) and sensor state values (Idle,Disabled,Charging allowed,Waiting for solar,Delayed,Active,Inactive, etc.) are now displayed in the user's configured HA language. Supported languages: English, Spanish, German, French, Dutch.
Removed¶
- Force Full Charge button removed: The
Force Full Chargebutton has been replaced by the newCharge Delayswitch (see above).
Changed¶
- Solar T_start detection rewritten: The mechanism that detects when solar production begins (used by the Charge Delay feature) has been replaced. The previous approach accumulated daily battery charging energy and triggered at a 0.1 kWh threshold — unreliable because grid charging energy was included in the counter. The new primary mechanism triggers when grid power ≤ 0 W and total battery power ≤ 0 W simultaneously (solar is covering at least the full house load with no battery contribution). A new astronomical fallback kicks in 30 minutes after the estimated sunrise (calculated from HA latitude, longitude, and day of year) if the primary condition has not fired, handling high-consumption days where grid power never reaches zero.
- Solar forecast corrected by 15 % before use: A conservative 15 % reduction is applied internally to the captured solar forecast before it is used by the Charge Delay and Predictive Charging algorithms. The raw sensor value is still shown in the
forecast_kwhdiagnostic attribute; only the internal calculation uses the adjusted value. - Weekly full charge config simplified: The
weekly_full_charge_configstep now contains only the day-of-week selector. The delay toggle, safety margin, and solar forecast sensor have been moved to the new dedicatedCharge Delaysteps, which apply to both the weekly 100% charge and the daily max_soc charge. - Solar forecast captured every night: The forecast capture at 23:00 now runs every night (previously only the night before the weekly charge day). The stored value is used the next morning by the delay logic, ensuring the forecast is always from the previous evening — before the sensor resets to the next day's data at midnight.
- Delay uses stored forecast: The charge delay algorithm no longer reads the solar forecast sensor live. It uses the value captured the previous night, which corresponds to the current day's production. Live reads would return the next day's forecast after midnight.
Fixed¶
- Feature entities disappearing after disabling switch: The
Charge Delaysensor andCapacity Protectionswitch and status sensor were only registered at startup when their respective feature was enabled. Disabling the switch persisted the disabled state to config, so after a restart those entities would no longer appear. Registration now checks whether the feature is configured (key exists in config entry) rather than whether it is currently enabled, matching the pattern already used by theCharge Delayswitch. Affected entities:Charge Delaysensor,Capacity Protectionswitch,Capacity Protection Activebinary sensor. - Charge Delay sensor renamed: The
Charge Delay Statussensor has been renamed toCharge Delayacross all supported languages (EN, ES, DE, FR, NL) for brevity. - Configuration changes not surviving restart: Changes to
Min SOC,Max SOC,Max Charge Power, andMax Discharge Powervia the UI were written to the battery's Modbus registers but not persisted toconfig_entry.data. After a restart, the coordinator re-initialised from the original setup values. The coordinator now syncs these attributes from the polled register values after every data refresh, treating the hardware as the source of truth. Separately, the enabled/disabled state of theCharge DelayandCapacity Protectionswitches was being saved correctly, but the related entities were not registered when the feature was disabled — making the saved state effectively invisible after a restart. This is resolved by the entity registration fix described above. - Solar forecast sensor not shown in initial setup: The solar forecast sensor field was missing from the first configuration step. It is now included as an optional field alongside the consumption sensor, making it available to both Predictive Grid Charging and Charge Delay without requiring re-entry in each feature's step.
- Safety Margin description and defaults incorrect in README: The documented range (10–120 min) and default (40 min) did not match the actual implementation (30–180 min, default 180 min). The description incorrectly stated it was an extra buffer for underperformance; the correct meaning is "minutes before sunset by which charging must be complete — higher values unlock charging earlier".
- V3 Battery SOC register reverted: Register 34002 (scale 0.1, introduced in v1.3.0) is not supported on Venus C v3 batteries and caused incorrect readings. SOC is now read again from register 37005 (scale 1, precision 1 decimal) as in versions prior to 1.3.0.
- Delay evaluation too infrequent: The charge delay status was only computed when the PD controller attempted to charge the battery. On days where the battery stayed in equilibrium (deadband), the
Charge Delay Statussensor remained stale and showedtarget_soc: Unknown. Delay evaluation now runs proactively on every 2-second control cycle regardless of charging activity.
[1.3.4] - 2026-03-17¶
Improved¶
- Stale sensor detection for PD controller: The control loop now detects when the consumption sensor hasn't updated between cycles (common with sensors reporting every 5s or slower) and skips PD recalculation to avoid acting on stale data. Sensor history is only populated with real readings, preventing duplicate values from diluting the moving average. The derivative term now uses the actual elapsed time between sensor updates instead of a fixed 2s, eliminating derivative spikes that caused oscillation with slow sensors. A safety valve forces recalculation (proportional only, derivative suppressed) if the sensor stops updating for ~30 seconds.
[1.3.3] - 2026-03-17¶
Fixed¶
- Hassfest manifest validation: Removed unsupported
iconfield frommanifest.jsonand addedrecordertoafter_dependenciesto declare the integration's usage of the recorder component.
[1.3.2] - 2026-03-17¶
Changed¶
- Min charge/discharge power slider range increased: The maximum value for
Min Charge PowerandMin Discharge Powerin both the PD Advanced options flow and the number entities has been raised from 500W to 2000W, allowing higher idle thresholds for systems with large PV Systems.
[1.3.1] - 2026-03-12¶
Fixed¶
- System SOC decimal precision for v3 batteries: The
System SOCaggregate sensor now displays one decimal place when any battery in the system is a v3/vA/vD model, matching the higher-resolution SOC readings provided by those batteries.
[1.3.0] - 2026-03-12¶
Added¶
- Venus A and Venus D battery support: New battery models
A(Venus A, max 1200W) andD(Venus D, max 2200W) for hybrid inverter setups. Both models share the same Modbus register map and include MPPT power sensors (mppt1–mppt4, enabled by default) for monitoring solar input channels. - Dynamic power slider limits in config flow: The battery configuration wizard now adapts the charge/discharge power sliders to the selected model's maximum (Ev2/Ev3: 2500W, A: 1200W, D: 2200W). The battery setup step has been split into two screens: connection details (name, IP, port, model) and power limits.
- Battery model version labels updated: Version labels in the configuration flow now read
Ev2,Ev3,A, andDfor clarity. - Weekly Full Charge Delay (Solar-Aware): New optional feature that delays the weekly 100% charge until solar production is forecast to be insufficient. Instead of charging to 100% from midnight, the system evaluates the solar forecast and only unlocks the full charge when remaining solar energy won't cover household consumption plus the energy needed to reach 100%. Uses a sinusoidal solar production model with T_start detection from actual battery charging data and solar noon calculated from Home Assistant's configured longitude. Includes configurable safety margins and automatic fallback for days with no forecast data.
- Solar forecast capture for delay feature: When the delay feature is enabled, the integration captures the next-day solar forecast at 23:00 and persists it across restarts using HA Store, ensuring the forecast is available on the target day.
- Weekly Full Charge diagnostic sensor: New
Weekly Full Chargediagnostic sensor on the Marstek Venus System device showing the current charge status (Idle,Waiting for solar,Delayed (HH:MM est.),Charging to 100%,Complete). Attributes expose full calculation details: forecast kWh, solar T_start/T_end, energy needed, remaining solar/consumption, net solar, charge time estimate, estimated unlock time, and unlock reason. - Force Full Charge button: New button on the Marstek Venus System device to trigger an immediate 100% charge on any day, bypassing the weekly schedule and delay logic. Resets automatically on day change.
- Configurable safety margin for delay feature: The delay safety margin (time buffer before estimated end of solar production) is now configurable in both config and options flow (10-120 minutes, default 40 min). Previously hardcoded at 40 minutes.
Changed¶
- System Charge/Discharge Power uses AC power: The
System Charge PowerandSystem Discharge Poweraggregate sensors now read from each battery'sAC Powerregister instead ofBattery Power, reflecting the actual AC-side power flow. - V3 Battery SOC register upgraded: V3 batteries now read SOC from register 34002 (scale 0.1, precision 2 decimals) instead of 37005 (scale 1, precision 1 decimal), providing higher resolution readings.
- Removed unused Charge to SOC entity: The
charge_to_socnumber entity (register 42011) was not used by any integration logic and has been removed from both V2 and V3 definitions. - Translation files completed: Added missing
apply_to_chargefield translations to EN, DE, FR and NL. Added missingenable_weekly_full_charge_delay,solar_forecast_sensoranddelay_safety_margin_mintranslations to DE, FR and NL (both config and options flow).
Fixed¶
- RS485 control mode not re-enabled after reconnection: When a battery's TCP connection was lost and re-established (e.g., WiFi drop, options flow reload), RS485 control mode was not re-enabled. The battery silently ignored all power commands until a manual restart.
async_reconnect_fresh()now automatically re-enables RS485 after every successful reconnection, with a user-override flag to respect manual RS485 disabling via the switch entity. - First battery RS485 disabled after options flow reload: On reload, the first battery attempted to reconnect before the V3 firmware released the previous TCP slot, causing the initial connection to fail. RS485 was only enabled on successful connection, leaving the first battery uncontrolled until health monitoring reconnected (without re-enabling RS485). Added a 1-second retry delay for failed initial connections.
- Individual battery Stored Energy sensor not visible: The
MarstekVenusStoredEnergySensorentities were created but immediately discarded due to alambda entities: Nonecallback. Sensors are now properly registered through the sensor platform setup. - Consumption history not populated without predictive charging: The daily consumption capture (needed for the delay feature's average calculation) was only scheduled when predictive charging was enabled. Now also scheduled when the weekly full charge delay is enabled.
- Grid charging falsely triggering solar T_start detection:
total_daily_charging_energyincludes grid charging energy, which could falsely indicate solar production start. T_start detection now only activates after 07:00 to avoid overnight grid charging interference.
[1.2.1] - 2026-03-06¶
Fixed¶
- Max charge/discharge power changes from UI ignored by control loop: Changing
Max Charge PowerorMax Discharge Powernumber entities wrote to the Modbus register but did not update the coordinator attributes used by the PD controller. The initial config flow values were used forever. Changes now take effect immediately.
[1.2.0] - 2026-03-04¶
Added¶
- Solar surplus mode for excluded devices: New
allow_solar_surplusoption in excluded device configuration. When the battery is charging, no adjustment is applied — the PD controller sees real grid power and naturally reduces charging to leave solar for the device. When the battery is discharging, full exclusion applies so the battery won't drain to power the device. Recommended for high-consumption devices like EV chargers. - Native config entities: Exposed key configuration parameters as Home Assistant entities, eliminating the need to run the full Options Flow wizard for routine adjustments:
- PD controller number entities: Kp, Kd, deadband, max power change, direction hysteresis, min charge/discharge power — all hot-reloadable without integration restart.
- Max Contracted Power number entity: Editable from the UI when predictive charging is enabled.
- Weekly Full Charge Day select entity: Pick the balancing day directly from the UI.
- Time Slot switches: Enable/disable individual no-discharge time slots on the fly.
- Excluded Devices Config sensor: Read-only diagnostic showing the number of excluded devices, with per-device details (sensor entity, included_in_consumption, allow_solar_surplus) as attributes.
- Discharge Window diagnostic sensor: Real-time sensor showing whether the system is currently inside an allowed discharge time slot. Displays "Active (Slot N)", "Inactive", or "No slots". Attributes include all slot configuration details (schedule, days, enabled, apply_to_charge, target_grid_power). Replaces the per-slot Time Slot Info sensors.
- Battery load sharing: Intelligent battery selection that uses the minimum number of batteries needed to keep each one operating in its optimal efficiency zone. Based on the Venus efficiency curve, batteries activate when total power exceeds 60% of combined capacity (peak efficiency ~91% at 1000-1500W). Features:
- Discharge priority: Highest SOC first (drain fullest battery).
- Charge priority: Lowest SOC first (fill emptiest battery).
- SOC hysteresis (5%): Active battery stays selected until another exceeds it by 5% SOC.
- Energy hysteresis (2.5 kWh): Tiebreaker uses lifetime energy with 2.5 kWh advantage for active battery, balancing long-term wear.
- Power hysteresis (±100W): Activates 2nd battery at 60% capacity threshold, deactivates at 50% to prevent ping-pong with fluctuating loads.
- Applies to all modes: normal PD control, solar charging, and predictive grid charging.
- Active Batteries diagnostic sensor: Real-time sensor showing which batteries are currently active in load sharing. Displays "Discharging: Venus 1", "Charging: Venus 2", or "Idle". Attributes include per-battery SOC, lifetime discharged/charged energy, and active battery counts. Only created for multi-battery setups.
Improved¶
- Modbus TCP connection management: Overhauled the Modbus connection lifecycle to prevent permanent battery disconnection (especially on V3, which only accepts one TCP connection). Reconnection now creates a fresh pymodbus client instance every time — closing the old socket first (sending TCP FIN to release the battery's connection slot), then connecting with
reconnect_delay=0to disable pymodbus's internal auto-reconnect which grew exponentially up to 300 seconds. Added coordinator-level connection health monitoring: after 3 consecutive failed poll cycles a fresh reconnection is triggered; after 5 failures, polling is suspended for 2 minutes to avoid flooding unreachable batteries. Normal 1.5s polling resumes automatically on recovery. The PD control loop now skips unreachable batteries viacoordinator.is_availableinstead of writing to dead connections. - Automatic reconnection in Modbus retry loops: When a
ConnectionExceptionorModbusIOExceptionoccurs during a read or write operation, the client now immediately attempts to create a fresh TCP connection instead of retrying on the dead socket. If reconnection succeeds, the operation is retried once; if it fails, retries are aborted immediately. This dramatically reduces recovery time after WiFi drops — the integration reconnects on the first failed operation instead of waiting for 3 poll cycles. - Immediate unavailability detection: The coordinator now marks a battery as unavailable (
_is_connected = False) on the first poll cycle where all reads fail, instead of waiting for 5 consecutive failures. The control loop stops sending writes to unreachable batteries immediately, preventing log noise and wasted Modbus operations. - Connection error log reduction:
ConnectionExceptionandModbusIOExceptionerrors during read/write operations are now logged at DEBUG level instead of ERROR with full traceback. "Failed after N attempts" messages also downgraded to DEBUG. This eliminates the 60,000+ error log entries that occurred during a WiFi disconnection event.
Changed¶
- Minimum charge/discharge power moved to PD controller settings:
min_charge_powerandmin_discharge_powerare now global PD controller parameters instead of per-time-slot settings. They apply uniformly regardless of the active time slot. Existing installations will use the default (0 = disabled) until reconfigured via Options → PD Advanced. - Predictive Charging switch logic inverted: The switch is now ON when predictive charging is enabled (default) and OFF when overridden/paused. Previously, it was an "Override" switch with inverted semantics. New unique_id (
_predictive_charging) — the old_override_predictive_chargingentity should be manually deleted from HA. - Entity reorganization: Restructured the Marstek Venus System device page for clearer HA UI layout:
- Controls: Manual Mode, Predictive Charging (inverted logic), Time Slot switches, Weekly Full Charge Day select — all without
EntityCategoryso they appear in the Controls section. - Diagnostic: Discharge Window sensor (new), Predictive Charging Active binary sensor.
- Configuration: PD controller parameters only (Kp, Kd, deadband, etc.).
- Removed per-slot Time Slot Info sensors and Predictive Charging Config sensor: Replaced by the single Discharge Window diagnostic sensor. Old entities (
*_time_slot_N_info,*_config_predictive_charging_slot) should be manually deleted from HA. - Max charge/discharge power config flow selector: Replaced the dropdown with only two options (800W / 2500W) with a slider ranging from 800W to 2500W in 50W increments, both in initial setup and options flow.
Fixed¶
write_register()refresh never executed: Theasync_request_refresh()call after a successful register write was dead code — it sat afterreturn Trueinside theasync with self.lockblock and was never reached. Restructured the method so the refresh executes outside the lock after a successful write.- First execution ignores time slot restrictions: After integration reload/reconfiguration, the first control cycle sent power to batteries without checking time slot restrictions. This caused a brief discharge pulse (~2.5s) even when the current day/time was outside any configured slot, which was then corrected to 0W on the next cycle. The first execution now checks
_is_operation_allowed()before sending any power commands. - Charge/discharge power limits swapped: The PD controller clamped charging power using
max_discharge_powerand vice versa. This caused charging to be limited to the discharge limit (e.g., 800W instead of 2500W) and discharge to use the charge limit. Both clamp conditions now use the correct limit for their direction.
[1.1.1] - 2026-02-27¶
Fixed¶
- Incorrect time slot translations: Fixed descriptions in English, German, French, and Dutch that incorrectly stated batteries "will NOT discharge" during slots. The correct behavior is the opposite — batteries are ALLOWED to discharge during configured slots and blocked outside them. Spanish translations were already correct.
[1.1.0] - 2026-02-27¶
Added¶
- Configurable target grid power per time slot: The PD controller can now regulate toward a user-defined grid power target instead of the fixed 0W. Each time slot includes a
target_grid_powerfield (range: -500W to +500W, default: 0W). Negative values target slight export (e.g. -150W), positive values allow slight import. Outside of active time slots, the controller defaults to 0W. This enables economic optimization for tariff setups where feed-in is more valuable than self-consumption. - Minimum charge/discharge power per time slot: Each time slot can now define
min_charge_powerandmin_discharge_powerthresholds (range: 0-500W, default: 0W = disabled). When the PD controller output is below the configured minimum, the controller stays idle instead of operating at inefficient low power levels. This reduces micro-cycling, unnecessary battery wear, and improves roundtrip efficiency. - Time slot overlap validation: The config flow now rejects time slots that overlap with existing ones on shared days. Also prevents midnight-crossing slots (start >= end) to avoid day-ambiguity — users must create two separate slots for overnight periods instead.
- Midnight-crossing slot runtime logic removed: Simplified
_is_operation_allowed()and_get_active_slot()to remove dead midnight-crossing code, since midnight-crossing slots are now rejected at configuration time.
[1.0.4] - 2026-02-26¶
Added¶
- V3 battery support: Version-specific Modbus register maps, entity definitions, and timing for V3 firmware.
- V3 packet correction: Automatically fixes malformed MBAP length bytes in V3 exception responses that caused pymodbus timeouts.
- Automatic reconnection in Modbus retry loops: Both read and write operations now reconnect if the TCP connection is lost mid-retry (skipped during shutdown to avoid occupying the single TCP slot).
Changed¶
- Platform files (
button.py,number.py,select.py,switch.py) now use coordinator's version-specific entity definitions instead of importing hardcoded V2 lists. ManualModeSwitchusescoordinator.get_register()instead of hardcoded register addresses, making it version-aware.- Bumped
pymodbusrequirement from>=3.0.0to>=3.5.0. - Version-specific Modbus timing: V2 uses 50ms, V3 uses 150ms between messages.
- RS485 control mode disable now writes the correct
command_offvalue (0x55BB) instead of0, which V3 firmware rejects with Modbus Exception 3.
Fixed¶
- Race condition during reload: Control loop and coordinator refresh continued running during
async_unload_entry, causing "Not connected" write errors. Fixed by cancelling timers at the start of unload, adding a shutdown flag to suppress expected errors and skip operations, and reordering the unload sequence to: cancel timers → set shutdown flag → wait for in-flight ops → unload platforms → write shutdown registers → disconnect. - Reconfiguration fails randomly with connection error: In-flight coordinator polls could survive the shutdown
disconnect()and automatically reconnect via Modbus retry logic, occupying the battery's single TCP connection slot. The newasync_setup_entrywould then fail with[Errno 111] Connect call failed. Fixed by exiting Modbus read/write retries immediately during shutdown and adding an early exit check in the coordinator poll cycle. - Options flow connection validation: Reconfiguration now temporarily closes the coordinator's active connection under lock, tests with a fresh connection, and reconnects the coordinator, instead of opening a second Modbus TCP connection (which the firmware rejects since it only supports one simultaneous connection).
- V3 Modbus serialization: Polling reads now acquire the coordinator lock, preventing interleaving with control loop writes on the same TCP connection. V3 firmware mishandled concurrent requests, causing transaction ID mismatches ("extra data") and written values not being applied. New
write_power_atomic()method writes all power registers and reads feedback under a single lock acquisition.
[1.0.3] - 2026-02-22¶
Fixed¶
- Fix
KeyErrorforforce_modewhendata_typeis missing (PR #3 by @openschwall).
[1.0.2] - 2026-02-20¶
Fixed¶
- Remove redundant
_write_config_to_batteries()call during options flow. The function opened a second Modbus TCP connection while the coordinator was still holding the first one, causing "Not connected" errors on V3 batteries. The reload already applies all configuration values viaasync_setup_entry(). - Fix
async_close()in Modbus client attempting toawaitthe synchronousclose()method, which caused "object NoneType can't be used in 'await' expression" errors on every reload. - Fix "Unable to remove unknown job listener" error on reload by switching
homeassistant_startedlistener fromasync_listen_oncetoasync_listen. The one-time listener auto-removed itself after firing, causingasync_on_unloadto fail when trying to cancel it during reload. - Run startup consumption backfill immediately on reload instead of waiting for
homeassistant_started(which never fires again after boot).
[1.0.1] - 2026-02-18¶
Changed¶
- Remove V3-exclusive entity definitions to match V2 register footprint.
- Deleted 20 entity definitions from the V3 definition lists (sensors, binary sensors, selects, buttons) that had no equivalent in V2.
- This reduces V3 Modbus-polled registers from ~38 to ~22, which should significantly cut options flow reload time for V3 users.