Predictive charging — Dynamic Pricing mode¶
Automatically selects the cheapest hours of the day to cover the calculated energy deficit.
Compatible price integrations¶
- Nord Pool — both the official Home Assistant integration and the HACS integration
- PVPC (ESIOS REE, Spain)
- CKW (Switzerland)
- EPEX Spot (e.g. aWATTar)
- ENTSO-e (Transparency Platform)
- Tibber — no price sensor needed; the engine polls the
tibber.get_pricesservice directly (see below)
Tibber needs no sensor
Selecting Tibber as the price integration leaves the Electricity price sensor field unused — the engine calls the tibber.get_prices service (today's prices, plus tomorrow's after ~13:00), caches the slots and refreshes hourly. The official Tibber integration must be configured in HA.
Official Nord Pool and HACS are selected the same way
Select Nordpool and choose a price entity from the provider. A HACS sensor continues to be read from its raw_today / raw_tomorrow attributes. If that sensor has price_in_cents: true, Omnibattery automatically converts its slots and current price to major currency/kWh, so thresholds must still be entered in €/kWh (or the corresponding major currency), not cents. For an entity from Home Assistant's official Nord Pool integration, Omnibattery automatically resolves its market area, calls nordpool.get_prices_for_date for today, converts the returned currency/MWh values to currency/kWh, and refreshes the cache hourly. No separate provider option or template sensor is needed.
Configuration¶
| Field | Description |
|---|---|
| Price integration type | Nordpool / PVPC / CKW / EPEX Spot / ENTSO-e / Tibber |
| Electricity price sensor | HA price entity. For Nord Pool, select either an official-integration entity or the existing HACS sensor; unused for Tibber |
| Max price threshold (€) | (Optional) Price ceiling; does not charge even during "cheap" hours if the price exceeds this value. Also used as the discharge threshold when price-based discharge control is enabled |
| Only discharge when price is above threshold | (Optional) Price-gated discharge — see below |
| Discharge price floor (€) | (Optional) Separate floor for price-gated discharge — opens an idle band between the charge ceiling and this floor. Empty = reuse the max price threshold for both. See Separate discharge price floor |
| Solar forecast safety margin (kWh) | (Optional) Extra energy buffer added to consumption forecast before deciding whether to charge (default 0 kWh) |
| Predictive grid charge margin (%) | (Optional) Tops up the grid-charge amount to hedge optimistic solar forecasts — e.g. a 2 kWh grid need at 50 % charges 3 kWh. Capped at the gap to max SOC (default 0 %) |
| Negative-price opportunistic charging | (Optional, default off) Charge in qualifying negative import-price slots even when the normal forecast has no deficit |

Daily evaluation (00:05)¶
At 00:05 the controller:
- Calculates the energy deficit (battery + solar vs. expected consumption).
- Fetches today's hourly prices from the configured integration.
- Selects the cheapest hours needed to cover the deficit.
- Calculates and stores the daily average price from the hourly price profile.
- Schedules the charging slots for the day.
Retry logic¶
If price data is unavailable at 00:05, the system retries every 15 minutes for the first hour.
HA restart mid-day¶
If HA restarts after the 00:05 window without a prior evaluation, the controller runs an automatic evaluation at startup (after 15 seconds). It considers the remaining slots of the current day and, when the provider has already published them, the next 12 hours so a restart does not leave the next overnight window without a plan.
Automatic re-evaluation during the day¶
The 00:05 plan is not immutable. Dynamic Pricing adapts it as the real day develops:
- One hour before each selected future slot, the energy balance is checked again. A slot is silently skipped when the battery and expected solar now cover the need. If a deficit remains, a persistent notification confirms that the slot will be used. Back-to-back slots are not re-evaluated while the previous slot is still charging.
- Late afternoon / evening, the controller performs one additional recharge assessment. When solar start was detected, it runs approximately 1.5 hours before the estimated end of production; if no start was detected, it uses a safe fallback at 16:00. It projects the remaining household consumption until midnight, subtracts usable battery energy and remaining solar, and adds only the cheap future slots needed to cover a material deficit (at least 0.3 kWh). This is a safety top-up, so it is not blocked by the optional arbitrage-margin gate.
- After a 30-point SOC drop, it performs the same late-day deficit assessment immediately instead of waiting for the evening trigger. The comparison is against the average battery SOC recorded at the last Dynamic Pricing evaluation; only drops of at least 30 percentage points trigger it, the reference is reset after reevaluation, and an SOC rise never triggers it.
These reevaluations keep existing charge limits, SOC floors, time-slot ownership, manual mode, backup and availability protections authoritative. The daily reference and once-per-day evening guard reset at midnight.
Re-evaluate Dynamic Pricing button¶
When Dynamic Pricing is enabled, the system device exposes Re-evaluate Dynamic Pricing (button.*_reevaluate_dynamic_pricing) in the dashboard and in Home Assistant. Pressing it immediately rebuilds the schedule with the latest price and solar data, using the same extended horizon as the startup catch-up path (end of today or now + 12 hours, whichever is later).
This button is useful after changing a price threshold, forecast or runtime option. It is deliberately not a full multi-day planner: pressing it in the afternoon does not reserve tomorrow afternoon's energy against today's deficit. Tomorrow's normal plan is built at 00:05 once that day's balance is known.
Negative-price opportunistic charging¶
This opt-in Dynamic Pricing feature is intended for installations with or without solar. When enabled, Omnibattery independently finds hourly or 15-minute slots whose normalized import price is negative. It calculates the battery energy needed to reach each battery's configured maximum SOC and selects the most-negative individual slots first. A solar forecast sensor is not required.
The calendar records why each interval was selected: deficit, negative_price, or combined. A positive-price deficit slot therefore keeps the normal deficit-based SOC target; it cannot consume energy that is pending only for an opportunity. In a qualifying combined slot, the higher of the deficit and opportunistic targets applies. Each battery uses its own configured maximum SOC as the opportunistic ceiling.
Charging stops as soon as the battery's configured maximum SOC is reached, and remaining opportunity-only slots are removed. A pure opportunity also stops if the live price becomes unavailable or is no longer negative. Contracted power, per-battery and system charge limits, user blockers, manual ownership, backup, availability and all existing safety controls remain authoritative.
The negative import-price condition is deliberately separate from the Negative injection threshold below. The former detects when importing energy is attractive; the latter identifies solar anti-curtailment risk. Outside a solar-risk window, a negative-price slot can charge toward the configured maximum SOC as before. Inside a risk window it is not rejected automatically: it can use only the headroom left after the solar reserve:
The opportunity never consumes the solar reserve. If actual solar is lower than forecast, the remaining reserve falls progressively and more grid charging becomes available; if actual solar is higher, the opportunity is reduced or stopped. Contracted power, SOC limits, minimum reserves, manual ownership and all other safety blockers still apply. A charge required to guarantee minimum SOC remains the safety exception. Missing solar data puts the anti-curtailment planner in fail-safe mode but does not cancel an otherwise valid import-price opportunity.
The runtime switch is available in the Omnibattery System controls, so automations can enable the feature without reopening the options flow.
Smart Pre-discharge / Anti-curtailment¶
This is an opt-in subfunction of Dynamic Pricing. It does not control a PV inverter. When enabled, Omnibattery reuses the normalized 15-minute or hourly price slots and the existing solar model to find future slots where:
- the price is at or below Negative injection threshold (default
0 €/kWh), and - forecast solar surplus would exceed household consumption.
The planner first calculates the headroom needed to absorb the forecast solar surplus. Before the first risk window it selects the most valuable (highest-price) eligible blocks for pre-discharge, stopping at the configured SOC floors, reserves, power limits and existing blockers. The same Solar forecast safety margin is used by predictive charging when deciding whether the solar forecast is sufficient. Slots are grouped into approximately one-hour blocks to avoid chatter. Consumption is distributed uniformly from the existing daily-history estimate when no more detailed model is available.
The live controls are available only when Predictive Grid Charging uses Dynamic Pricing:
| Control | Meaning |
|---|---|
| Smart Pre-discharge | Runtime opt-in switch; default off |
| Negative injection threshold | Inclusive price threshold for a risk slot |
| Pre-discharge reserve SOC | Additional SOC floor; 0 uses existing floors |
| Pre-discharge export mode | Self-consumption only, Automatic, or Custom limit |
| Custom deliberate-export limit (W) | Shown for Custom limit; caps deliberate export to the grid during pre-discharge. This is an export limit, not total battery discharge power |
| Solar forecast safety margin | Extra buffer in kWh used by predictive charging and anti-curtailment |
The three export modes are:
- Self-consumption only: no deliberate grid export; equivalent to
0 W. - Automatic: calculates only the export power needed to create the required headroom; it does not always use the maximum available discharge power.
- Custom limit: deliberately exports up to the configured W limit. The value describes deliberate grid export, not total battery discharge power.
Existing configurations remain compatible: legacy 0 maps to Self-consumption only, while a positive legacy value maps to Custom limit. During a risk window, the controller clamps the net grid target to zero: the battery may cover domestic consumption, but it will not deliberately export to the grid. The feature never bypasses minimum or guaranteed-minimum SOC, user time-slot ownership, manual control, backup mode, unavailable/non-responsive batteries, or capacity protection. Missing prices, forecast, SOC, capacity or a valid grid meter are fail-safe conditions: any smart override and blocker are cleared. The plan is rebuilt after restart, at the normal daily evaluations, when the feature is enabled, after a material change in available battery headroom, and by the existing Re-evaluate Dynamic Pricing button. Parameter changes invalidate the old plan; use that button to apply them immediately instead of waiting for the next evaluation. Plans are not persisted.
The single binary sensor for this feature, curtailment_status, reports the current state, reason, next risk window, risk slots, required/current headroom, planned discharge, shortfall, per-battery targets, selected discharge slots and active export target. It also exposes automation-oriented attributes:
protected_window_active: the negative-injection window is active.headroom_deficit_kwh: headroom still missing to absorb the forecast.inverter_curtailment_required:trueonly when the protected window is active and headroom is missing;falsewhen a valid plan needs no inverter limit;nullwhile the plan is fail-safe or unavailable.- The downloaded diagnostics include
solar_reserve_remaining_kwh,current_free_space_kwh, andopportunistic_space_available_kwh. The latter is never negative and followscurrent free space − remaining solar reserve. charge_limit_reasonandcharge_limit_reasonsidentify why opportunistic grid charging is limited, including active charge blockers and exhaustion of the solar reserve. Theexportdiagnostic reports the selected mode and, when present, the deliberate-export limit in W.
active_export_target_w is the battery's pre-discharge export target, not a universal PV-inverter command. An automation should apply an inverter-specific limit and restore normal operation only after the status no longer requires curtailment.
Price-based discharge control¶
The "Only discharge when price is above threshold" option adds an extra condition to discharge behaviour.
When active, every controller cycle (event-driven) checks whether the current price allows discharge:
If current_price > threshold:
→ Discharge allowed (PD controller operates normally)
If current_price <= threshold:
→ Discharge BLOCKED (battery holds)
The threshold is resolved as follows:
- If Max price threshold is configured, that value is used.
- If Max price threshold is empty, the daily average price is used.
The daily average price is calculated automatically during the 00:05 evaluation from the hourly price profile. The goal is to preserve battery energy for the most expensive hours of the day. If no fixed threshold is configured and the daily average is not available yet, discharge control does not act.
Separate discharge price floor¶
By default a single threshold gates both ends: the battery grid-charges only below the max price threshold and discharges only above it. The optional Discharge price floor decouples the two by setting a lower discharge floor, opening an idle band between them:
price ≥ max price threshold → discharge allowed
discharge floor < price < ceiling → idle (no grid charge, no discharge)
price ≤ discharge price floor → discharge BLOCKED
In the idle band the battery neither grid-charges nor discharges — but solar-surplus charging still works. This avoids cycling the battery for the marginal price difference around the average. The floor must be at or above the charge ceiling (it is validated on save); leave it empty to reuse the max price threshold for both (the single-threshold behaviour above).
Both thresholds are also exposed as live number entities (Max Price Threshold and Discharge Price Floor) so automations can rewrite them without entering the options flow.
Minimum arbitrage margin¶
A fixed charge ceiling answers "is this price low?" but not "is it low enough". Those come apart in winter, when a flat price curve can sit entirely below the ceiling while offering no spread to trade against. Charging then runs the battery through a cycle that the round-trip losses eat.
The optional Minimum Arbitrage Margin makes the ceiling move with the day instead. At each evaluation the engine takes the most expensive hours still ahead, as many as it plans to charge for, and requires:
Slots that fail are dropped. If none survive, the day is skipped entirely.
The margin is empty by default, which leaves slot selection exactly as it was. Setting it back to 0 disables it again. When set, it applies on top of the max price threshold, and whichever ceiling is stricter wins.
The gate runs on the 00:05 evaluation only. The evening recharge after a poor solar day is a deficit-driven safety top-up rather than an arbitrage trade, and by then the remaining horizon holds no expensive hours to price against, so applying the gate there would block every recharge it exists to perform.
Round-Trip Efficiency (default 0.85) is the AC-to-AC ratio used to value a stored kWh. Lower values tighten the gate. Note this is the marginal ratio (extra kWh out per extra kWh in), not the gross figure you get by dividing lifetime discharge by lifetime charge, which also carries standby drain. Standby is paid whether or not you cycle, so folding it in here would refuse profitable charges.
Both are exposed as live number entities, and the evaluation notification reports the resulting ceiling so a skipped night is traceable.
Interaction with time slots¶
If time slots are configured to restrict discharge, both conditions must be met for the battery to discharge:
Outside a slot that allows discharge, the battery never discharges. Inside one, it only discharges when the price is high enough.
Effect on the PD controller¶
When discharge is blocked by price, the controller completely freezes its state (power to 0, no derivative term update), the same as during a time slot restriction. The battery resumes smoothly as soon as the price exceeds the active threshold again.
Diagnostic attributes¶
The predictive_charging_active binary sensor exposes:
| Attribute | Description |
|---|---|
charging_needed |
Whether charging is needed according to the balance |
selected_hours |
Selected hours with individual prices |
average_price |
Average price of the selected hours |
estimated_cost |
Estimated charging cost |
evaluation_timestamp |
When the last evaluation was performed |
price_data_status |
Price sensor status (ok (N slots), sensor_unavailable, no_slots, not_evaluated) |
