Skip to main content
Version: Next 🚧

Plan Charging Action

The plan_charging action turns battery parameters into a complete cost-minimized charging schedule. Instead of manually computing energy, duration, and power, you describe the battery (capacity, current SoC, target SoC, max power) and the action returns a per-interval plan with SoC progression, cost totals, and segment grouping.

Experimental

The plan_charging action is experimental and still undergoing testing. Its parameters, response format, and behavior may change in future releases. Use it in automations with care, and please report any issues.

When to use this

If you already know the duration in minutes and just need the cheapest time window, use find_cheapest_hours or find_cheapest_block. Use plan_charging when you know your battery/EV parameters and want the integration to compute the duration, account for charging losses, and produce a SoC progression.

At a Glance

SituationExample
Home battery: "Charge from 20% to 80%, efficiency 0.92"current_soc_percent: 20, target_soc_percent: 80, battery_capacity_kwh: 10
EV with 3-phase charger: "Use 1/2/3 phases as needed"charge_power_steps_w: [1380, 4140, 11000]
Battery with modulation: "30 W – 1200 W continuous"min_charge_power_w: 30, max_charge_power_w: 1200
Deadline-aware: "At least 50% before next peak"must_reach_soc_percent: 50, must_reach_by_event: next_peak_period
Arbitrage: "Only charge if later discharge is profitable"expected_discharge_price: 0.28, reserve_for_discharge: true

Required Inputs

FieldDescription
max_charge_power_wMaximum charging power in watts (upper bound for every interval).
current_soc_percent or current_soc_kwhCurrent battery state of charge.
target_soc_percent or target_soc_kwhDesired battery state of charge.
battery_capacity_kwhRequired when you use percent values.

All other inputs (deadline, power steps, grid limit, economics, search range) are optional.

Choosing Between Fixed / Continuous / Stepped Power

ModeTriggerBehavior
FixedOnly max_charge_power_w setEvery selected interval charges at full power. Last interval may over-shoot the target slightly (rounding up).
ContinuousAdd min_charge_power_wPlanner can reduce the final partial interval down to the minimum power — no over-shoot.
SteppedAdd charge_power_steps_w: [a, b, c]Planner picks the smallest allowed step that covers the remaining energy. Mutually exclusive with min_charge_power_w.

Deadlines

Combine a minimum SoC with a deadline:

  • must_reach_soc_percent / must_reach_soc_kwh — the minimum you need by the deadline.
  • Then pick one of:
    • must_reach_by — absolute datetime.
    • must_reach_by_event — one of midnight, next_peak_period, next_best_period_end.

The planner runs a two-pass schedule: first guarantee the minimum SoC before the deadline using the cheapest pre-deadline intervals, then fill the remaining target with the cheapest intervals from the full search range.

Economics (Arbitrage)

For "charge cheap now, discharge expensive later" use cases:

  • discharging_efficiency — fraction still usable when discharged (default 1.0).
  • expected_discharge_price — expected price per kWh at discharge time (in your configured display unit).
  • reserve_for_discharge — when true, discards intervals that are unprofitable given the round-trip efficiency.
  • max_cost_per_kwh — a hard ceiling; any interval above this price is discarded before scheduling.

The response's economics.break_even_price tells you the maximum charging price at which the round-trip still breaks even.

Examples

Home battery — charge from 20% to 80% overnight

Show YAML
service: tibber_prices.plan_charging
data:
battery_capacity_kwh: 10
current_soc_percent: 20
target_soc_percent: 80
charging_efficiency: 0.92
max_charge_power_w: 2500
search_scope: remaining_today
response_variable: plan

EV — 3-phase, at least 50% before next peak period

Show YAML
service: tibber_prices.plan_charging
data:
battery_capacity_kwh: 60
current_soc_percent: 30
target_soc_percent: 80
must_reach_soc_percent: 50
must_reach_by_event: next_peak_period
max_charge_power_w: 11000
charge_power_steps_w: [1380, 4140, 11000]
grid_import_limit_w: 16000
response_variable: plan

Battery arbitrage — only if profitable

Show YAML
service: tibber_prices.plan_charging
data:
battery_capacity_kwh: 10
current_soc_percent: 10
target_soc_percent: 100
charging_efficiency: 0.92
discharging_efficiency: 0.92
expected_discharge_price: 0.28 # ct/kWh value expected when discharging
reserve_for_discharge: true
max_charge_power_w: 3000
search_scope: next_48h
response_variable: plan

Response Structure

The response contains the following top-level keys:

KeyDescription
successtrue when the request itself worked (even if no schedule was produced); false only on a Tibber API outage (reason: "price_data_unavailable").
intervals_foundtrue when a schedule was produced.
batteryNormalized SoC / capacity / efficiency / achieved_soc_kwh (what you actually reach with the returned schedule).
chargingMode, total duration, total energy, total cost, and the schedule block.
charging.schedulesegments[], intervals[], segment_count, seconds_until_start, seconds_until_end, and price statistics.
deadlinePresent when a deadline was set — includes must_reach_by, must_reach_soc_kwh, achieved_soc_kwh, deadline_met.
economicsPresent when any economic parameter was set — includes break_even_price, expected_net_savings, round_trip_efficiency.
price_comparisonDifference between the selected schedule and the most expensive equivalent window.
relaxation_applied / relaxation_stepsWhether the schedule was relaxed to fit available data.
reasonStable reason code when no schedule was found (see below).

Per-Interval Fields

Each entry in charging.schedule.intervals[] includes:

  • starts_at, ends_at, price, level, rating_level
  • power_w — power assigned to this interval (watts)
  • grid_energy_kwh — energy drawn from the grid
  • stored_energy_kwh — energy actually stored after losses
  • soc_after_kwh, soc_after_percent — cumulative SoC after this interval

Reason Codes

When no schedule is found, reason contains one of:

CodeMeaning
price_data_unavailableThe Tibber API was temporarily unavailable for an uncached range. This is the only case where success is false — treat it as a transient error and retry later. Your sensors keep running from cache.
already_at_targetCurrent SoC is already at or above target — no charging needed.
no_data_in_rangeThe search range has no price data. Often not an error: tomorrow's prices are usually published by Tibber around 13:00 local time, but the day-ahead auction can be delayed — sometimes the data only arrives later in the afternoon or evening. A range covering tomorrow returns this until then (with success: true). Retry later.
no_intervals_matching_level_filtermin_price_level / max_price_level filtered everything out.
no_intervals_after_economic_filtermax_cost_per_kwh or reserve_for_discharge filtered everything out.
energy_unreachableThe energy needed cannot be charged within the available intervals + power limits.
energy_unreachable_by_deadlineThe minimum SoC cannot be reached before the deadline with the available intervals.
selection_above_distance_thresholdmin_distance_from_avg is not satisfied by the cheapest selection.

💬 Comments are page-specific. For a new question or idea, open a dedicated Discussion on GitHub so it gets its own thread and proper visibility.