Skip to content

Latest commit

 

History

History
1523 lines (1199 loc) · 139 KB

sensors-reference.md

File metadata and controls

1523 lines (1199 loc) · 139 KB

HSEM Sensors Reference

Comprehensive reference for all entities exposed by the HSEM integration, including attributes, states, and dashboard examples.


Entity overview

HSEM exposes these entity types:

Type Count Description
Sensor ~40 Read-only state, plan, diagnostic, financial, and EV entities
Select 2 Force working mode override and Solcast likelihood selector
Switch ~12 Toggle entities for EV settings, features, and ML options
Time 2 EV charge deadlines
Number 4 Charge/discharge efficiency and EV target SoC controls

Recorder footprint

All attributes below are always available on the live state (state_attr(), dashboards, automations). To keep the Home Assistant database small (issue #1099), HSEM records only small scalar attributes. Lists, dicts, per-slot timestamps and restore payloads are listed in each sensor's _unrecorded_attributes and never reach the recorder:

Sensor Not recorded
sensor.hsem_workingmode_sensor all attributes
sensor.hsem_plan_explanation_sensor rejected_plans, constraints, current_slot_start, current_slot_end
sensor.hsem_ev_*optimal_charging_plan charging_slots, planned_load_by_slot, data_quality
sensor.hsem_ev_*soc_economics points, configured_deadlines, baseline_plan_cost
sensor.hsem_ev_*charger_current_limit schedule
sensor.hsem_solar_confidence_sensor hour_factors, processed_through, _solar_corrector_data
sensor.hsem_forecast_accuracy_sensor _forecast_tracker_data
sensor.hsem_prediction_accuracy_sensor action_mix
sensor.hsem_savings_tracker_sensor daily
sensor.daily_plan_vs_actual today, yesterday, history
Export income / import cost / net grid balance today, last_7_days, last_30_days, this_month, this_year, daily
sensor.hsem_performance_* yesterday, last_7_days, last_30_days, daily, notes
sensor.hsem_applier_status_sensor last_apply_details, failed_entities, unverified_entities
sensor.hsem_ocpp_*charger_status all attributes
sensor.hsem_ocpp_*charger_sessions sessions
sensor.hsem_next_update_sensor last_updated (recorded as the state of sensor.hsem_last_updated_sensor)
sensor.hsem_house_consumption_energy_avg_* all except the state (measurements, last_updated, …)
sensor.hsem_house_consumption_power_* all except the state

State values are always recorded, so history graphs and long-term statistics of the sensor states are unaffected. Restart restore reads RestoreEntity storage (.storage/core.restore_state), which keeps every attribute regardless of this list.

last_updated attributes. On the per-hour consumption sensors, last_updated is the time the published value last changed. On the working mode sensor and sensor.hsem_next_update_sensor it is the coordinator heartbeat (time of the last completed update cycle), the same value as the sensor.hsem_last_updated_sensor state. For "when did this value last change" on any entity, use Home Assistant's built-in last_changed ({{ states.sensor.x.last_changed }}).


Devices (issue #875)

Entities are split across 7 Home Assistant devices instead of one, so each subsystem gets its own device page and can be scoped independently in dashboards, automations, and Areas.

Device Identifier suffix Entities
Controller (none — legacy) Working mode, degraded mode, read-only, hardware-writes, missing-entities, force-mode, last/next-updated, update-interval, applier-status, plan-explanation, daily-plan-vs-actual, recommendation-interval, force-working-mode selector, extended-attributes/verbose-logging/ML switches
Battery & Energy _battery_energy Battery SoC, effective discharge floor, net consumption, PV curtailment, charge/discharge efficiency numbers, dynamic discharge floor switch
Hourly Consumption Profile _hourly_consumption All 168 per-hour-block entities (HSEMHouseConsumptionPowerSensor + integral + utility-meter + 1/3/7/14-day averages × 24 hour blocks)
Financial _financial Export income, import cost, net grid balance, savings, performance (savings capture, actual benefit, missed opportunity, battery and solar utilisation, EV charging efficiency)
Forecast _forecast Forecast accuracy, prediction accuracy, solar confidence, Solcast likelihood selector
EV Primary _ev_primary EV charging/plan sensors, calculated power, current limit, target SoC number, deadline time, smart-charging/force-charge-now/force-discharge/auto-full switches, OCPP charger sensors for charger_index=1
EV Secondary _ev_secondary The same set as EV Primary for charger_index=2 / the second EV (only present when the second EV is configured)

The Controller device keeps the original (DOMAIN, entry_id) identifier that predates the split — no migration is needed for its entities. Every other device is identified as (DOMAIN, f"{entry_id}_<suffix>").

Naming: EV Secondary and OCPP entity names no longer carry a redundant "Second"/"2" marker (e.g. "Charger Status", not "OCPP Charger Second Status") — the device name (e.g. "HSEM EV Secondary") already disambiguates via _attr_has_entity_name = True. unique_id and entity_id are unchanged.

Migration: on first startup after upgrading, a one-time migration (custom_components/hsem/device_migration.py) reassigns device_id for every pre-existing entity to its new device, gated by a migration-version flag stored on the config entry so it runs exactly once. unique_id is never touched. This is a breaking change for dashboards/automations that reference the old single device_id.


Working mode sensor

The primary HSEM sensor. Exposes the active battery recommendation and carries all planner output as attributes.

Entity: sensor.hsem_workingmode_sensor

State Meaning
batteries_charge_grid Battery charging from grid (forced by schedule or price)
batteries_charge_solar Battery charging from PV surplus
batteries_discharge_mode Battery discharging to cover house load
batteries_discharge_window_mode Inside a seasonal discharge window, but the plan dispatches no discharge; self-consumption still allowed
force_batteries_discharge Forced discharge to grid (excess export)
force_export Negative import price — all energy exported
ev_smart_charging EV charging load allocated
batteries_wait_mode Battery idle; may allow self-consumption above the planner reserve depending on Wait mode behaviour
time_passed Slot is in the past
missing_input_entities Required HA entities unavailable

Standard attributes

Attribute Type Description
batteries_current_capacity float (kWh) Current usable battery capacity above discharge floor
batteries_usable_capacity float (kWh) Usable battery capacity (rated − floor)
batteries_recommended_min_price_threshold float Discharge threshold from calculate_recommended_threshold()
batteries_capacity_loss_pct float Configured capacity loss percentage
import_electricity_price_state float Live import spot price (currency/kWh)
export_electricity_price_state float Live export spot price (currency/kWh)
export_electricity_min_price float Minimum export price for export gating
electricity_price_update_interval int Price refresh interval (minutes)
house_consumption_power_state float (W) Instantaneous house load
house_power_includes_ev_charger_power bool Whether EV load is baked into house power
net_consumption float (W) Net load (house − solar)
net_consumption_with_ev float (W) Net load including EV
solar_production_power_state float (W) Instantaneous solar production
months_winter / months_summer list[int] Configured winter/summer month ranges
batteries_enable_excess_export bool Excess export gating enabled
batteries_excess_export_discharge_buffer float Discharge buffer for excess export
batteries_forecast_reserve_pct float Extra SoC points above the hardware floor reserved immediately after intentional battery export (issue #807, 0 = disabled)
phase_aware_charging_enabled bool Live phase-aware grid-charge safety limiter enabled (issue #831, default False)
grid_phase_readings tuple[[float, str] | None, …] Live per-phase grid readings (a, b, c) as [value, unit], unit W (signed) or A (issue #1119); None per phase when unavailable or in an unsupported unit
grid_phase_voltage_v tuple[float | None, float | None, float | None] Live per-phase voltage (a, b, c) in volts; None when unconfigured or unavailable (230 V is then assumed)
huawei_batteries_grid_charge_max_power_w float | None Live Huawei grid-charge maximum-power ceiling in Watts, or None if unconfigured/unavailable
primary_grid_charge_owned bool Whether HSEM currently believes it owns an armed Huawei grid-charge cap (issue #840). Drives the Error-mode emergency stop; never true for a charge armed outside of HSEM
battery_target dict | None House-battery target SoC diagnostics for the next occurrence (issue #1109): target_time, target_kwh, stage1_projected_kwh, projected_kwh, shortfall_kwh, penalty_per_kwh, stage2_ran, stage2_status, max_import_delta_kwh, and the preference cost (issue #1185): stage1_cost, stage2_cost, preference_cost, preference_cost_per_kwh, terminal_soc_value_delta (null unless the target changed the plan). None when the target is disabled
batteries_wait_mode_behavior string Wait mode behaviour: strict or self_consumption_with_reserve
wait_mode_self_consumption dict What the live batteries_wait_mode slot does with the battery (issue #1255): active (the slot runs as self-consumption, the house may use the battery), reserve_kwh (energy held back, above the hardware floor), surplus_kwh (energy above the reserve). active is false and both figures are null when the behaviour does not apply: another state, strict, an EV charging or planned, a planned PV export, or no reserve. The planned SoC of a wait slot never includes this discharge
house_consumption_energy_weight_1d float 1-day consumption prediction weight
house_consumption_energy_weight_3d float 3-day weight
house_consumption_energy_weight_7d float 7-day weight
house_consumption_energy_weight_14d float 14-day weight
last_updated string (ISO-8601) Last coordinator cycle timestamp
status string ok, read_only, wait, or error
degraded_mode string ok, degraded, or error
hardware_writes_blocked bool Safety gate preventing hardware writes
apply_status string Last apply result: ok, unverified, failed, skipped
apply_failed_entities list[string] Entities that failed the last hardware write
data_quality dict Structured price/PV and load-forecast completeness report
force_working_mode_state string Active override mode or auto

Plan output attributes

Attribute Type Description
hourly_recommendation dict | null The recommendation slot active right now
hourly_recommendations list[dict] Full list of planner slots for the horizon

hourly_recommendations slot structure

Each entry in the hourly_recommendations list is a dictionary with these keys:

Key Type Description
start string (ISO-8601) Slot start timestamp
end string (ISO-8601) Slot end timestamp
recommendation string | null Working-mode value (see state table above)
import_price float Spot import price (local currency/kWh)
export_price float Spot export price (local currency/kWh)
avg_house_consumption_kwh float Weighted spike-aware consumption estimate (kWh)
avg_house_consumption_1d_kwh float 1-day window contribution (kWh)
avg_house_consumption_3d_kwh float 3-day window contribution (kWh)
avg_house_consumption_7d_kwh float 7-day window contribution (kWh)
avg_house_consumption_14d_kwh float 14-day window contribution (kWh)
solcast_pv_estimate_kwh float Forecast PV production for the slot (kWh)
estimated_net_consumption_kwh float avg_consumption + ev_planned_load − pv_estimate (kWh)
ev_planned_load_kwh float Extra EV AC load added to net consumption (kWh, ≥ 0)
ev_accounted_load_kwh float EV AC load already in house consumption (kWh, ≥ 0)
ev_total_planned_load_kwh float Total EV AC load (planned + accounted, kWh, ≥ 0)
ev_charger_calculated_power float Primary EV charger target AC power (W)
ev_second_charger_calculated_power float Second EV charger target AC power (W)
estimated_cost_currency float Estimated grid cost for the slot (local currency)
batteries_charged_kwh float Energy scheduled to charge into battery (kWh)
batteries_discharged_kwh float Energy drawn from battery by SoC simulation (kWh)
estimated_battery_capacity_kwh float kWh above the hardware discharge floor at slot end
estimated_battery_soc_pct float Simulated absolute SoC at slot end (0–100 %)
grid_import_kwh float Energy imported from grid (kWh)
grid_export_kwh float Energy exported to grid (kWh)
is_ev_surplus_only_slot bool Slot restricted to EV surplus-only charging

Extended attributes (when enabled)

When the switch.hsem_extended_attributes switch is on, additional entity-ID attributes are exposed. These reference the raw HA entity IDs for troubleshooting:

Attribute Description
import_electricity_price_sensor_entity Import price sensor entity ID
export_electricity_price_sensor_entity Export price sensor entity ID
ev_charger_power_entity Primary EV charger power entity ID
ev_charger_status_entity Primary EV charger status entity ID
ev_soc_entity Primary EV SoC entity ID
ev_connected_entity Primary EV connected entity ID
ev_second_charger_power_entity Second EV charger power entity ID
ev_second_charger_status_entity Second EV charger status entity ID
ev_second_soc_entity Second EV SoC entity ID
ev_second_connected_entity Second EV connected entity ID
force_working_mode_entity Force working mode entity ID
house_consumption_power_entity House consumption power entity ID
solar_production_power_entity Solar production power entity ID
solcast_pv_forecast_forecast_today_entity Solcast today forecast entity ID
solcast_pv_forecast_forecast_tomorrow_entity Solcast tomorrow forecast entity ID
(plus all huawei_solar_* entity IDs) Huawei battery and inverter entity IDs
recommendation_interval_minutes Slot width in minutes
recommendation_interval_length Number of slots in the horizon
unique_id Integration unique ID
update_interval Polling interval in minutes
read_only Whether read-only mode is active

EV attributes

Attribute Type Description
ev_charger_power_state float (W) Primary EV charger instantaneous power
ev_charger_status_state bool Primary EV currently charging
ev_soc_state float (%) Primary EV current SoC
ev_soc_target_state float (%) Primary EV target SoC
ev_connected_state bool Primary EV plugged in
ev_allow_charge_past_target_soc bool Allow charging past target
ev_past_target_confidence_factor float Confidence factor (0.0–1.0) applied to the avoided-future-import valuation of past-target charging
ev_charger_max_discharge_power_state float (W) Battery discharge ceiling while this EV charges (once permission below is granted)
ev_charger_force_max_discharge_power bool Permission for the Huawei battery to discharge while this EV charges; without it, discharge is 0 W whenever the EV is active
ev_second_enabled bool Second EV integration enabled
ev_second_charger_power_state float (W) Second EV charger power
ev_second_charger_status_state bool Second EV charging
ev_second_soc_state float (%) Second EV SoC
ev_second_soc_target_state float (%) Second EV target SoC
ev_second_connected_state bool Second EV plugged in
ev_second_allow_charge_past_target_soc bool Second EV past-target flag
ev_second_past_target_confidence_factor float Second EV past-target confidence factor (0.0–1.0)
ev_second_charger_max_discharge_power_state float (W) Second EV: same discharge ceiling semantics as the primary EV
ev_second_charger_force_max_discharge_power bool Second EV: same discharge permission semantics as the primary EV

Huawei battery attributes

Attribute Type Description
huawei_solar_batteries_charging_cutoff_capacity_state float (%) Inverter charging cutoff SoC
huawei_solar_batteries_grid_charge_cutoff_soc_state float (%) Grid charge cutoff SoC
huawei_solar_batteries_maximum_charging_power_state float (W) Maximum charge power
huawei_solar_batteries_maximum_discharging_power_state float (W) Maximum discharge power
huawei_solar_batteries_rated_capacity_max_state float (Wh) Rated battery capacity
huawei_solar_batteries_rated_capacity_min_state float (kWh) Discharge floor capacity
huawei_solar_batteries_state_of_capacity_state float (%) Battery SoC
huawei_solar_batteries_tou_charging_and_discharging_periods_periods list Parsed TOU periods
huawei_solar_batteries_tou_charging_and_discharging_periods_state string Raw TOU entity state
huawei_solar_batteries_working_mode_state string Inverter working mode
huawei_solar_inverter_active_power_control_state_state string APC mode
huawei_solar_batteries_excess_pv_energy_use_in_tou_state string Excess PV in TOU setting

Plan explanation sensor

Displays the planner's strategy rationale and per-candidate cost breakdown.

Entity: sensor.hsem_plan_explanation_sensor

Key attribute Description
State Winning candidate name: "milp", "passive", "no_action"
selected_strategy Human-readable description (e.g. "charge_grid_discharge_peak")
winner_name Winning candidate name (same as state)
summary One-sentence human-readable reason
score Estimated savings vs doing nothing (currency)
estimated_total_cost Net grid cost for the horizon
price_spread Max minus min import price (arbitrage potential)
peak_import_price / off_peak_import_price Price extremes
forecast_pv_kwh Total PV forecast for the horizon
forecast_net_consumption_kwh Total load minus PV
battery_soc_pct / battery_soc_at_end_pct Starting and ending SoC
constraints Active flags (winter_month, excess_export_enabled, etc.)
rejected_plans Alternatives with name, reason, and full cost breakdown
hysteresis_active Whether plan-level hysteresis was applied
hysteresis_reason Explanation of hysteresis decision
solver_status How the MILP solve ended (issue #1309): optimal, time_limit (the solver stopped at its time limit and its best plan so far is used; selected_strategy is then solver_time_limit), or failed (no MILP plan, a fallback is used; selected_strategy is solver_failed). null without scipy
solver_time_s Wall time of that solve in seconds
solver_gap_pct Relative MIP gap HiGHS reported, in percent (0 when proven optimal)
solver_time_limit_s The solver time limit (hsem_planner_solver_time_limit_s)
ml_forecast_wind_configured Whether wind chill is enabled and a weather entity is configured
ml_forecast_wind_slots_used Future slots using per-slot forecast wind speed
ml_forecast_wind_fallback_slots Future slots that fell back to the nearest measured wind reading

Financial sensors

Cumulative monetary sensors that track grid import cost and export revenue. All three use total because signed prices may make their values decrease.

They count on every update cycle, whether or not HSEM is planning: a forced working mode, a missing entity or a load forecast that is not ready yet does not pause them. Only a stretch in which no cycle ran at all (Home Assistant down, or longer than two update intervals between cycles) is left out, because a meter delta that old cannot be priced.

Entities:

  • sensor.hsem_export_income — Cumulative export revenue
  • sensor.hsem_import_cost — Cumulative import cost
  • sensor.hsem_net_grid_balance — Export income minus import cost

Attributes (all three): today, last_7_days, last_30_days, this_month, this_year (each {import_cost, export_income, net_balance}) and daily — the newest 90 daily records, oldest first. The tracker keeps 366 completed days in hsem_financial_history.json so the this_year rollup is always complete; older days are pruned at midnight and on load.

sensor.hsem_export_income

Property Value
Type sensor
State class total
State Cumulative export revenue (local currency)
Device class monetary

sensor.hsem_import_cost

Property Value
Type sensor
State class total
State Cumulative import cost (local currency)
Device class monetary

sensor.hsem_net_grid_balance

Property Value
Type sensor
State class total
State Net grid balance (export_income − import_cost, local currency)
Device class monetary

Template example:

{{ states('sensor.hsem_net_grid_balance') | float | round(2) }}

Prediction accuracy sensor

Tracks prediction accuracy across multiple horizons — solar, load, and battery SoC — up to 30 days.

Entity: sensor.hsem_prediction_accuracy_sensor

State Meaning
soc_mae_7d 7-day battery SoC MAE (percentage points)
Attribute Unit Description
soc_mae_7d pp 7-day SoC Mean Absolute Error
soc_mae_30d pp 30-day SoC Mean Absolute Error
solar_mape % Solar forecast MAPE
load_mae_kwh kWh Load Mean Absolute Error
action_mix dict Distribution of planner actions over the window

Solar confidence sensor

Per-hour PV forecast accuracy factors and confidence percentile.

Entity: sensor.hsem_solar_confidence_sensor

Attribute Description
State Mean accuracy factor across the horizon (ratio)
factors dict — per-hour correction factors for each slot
confidence_pct float — confidence percentile (e.g. 50 = median)

Template example:

{{ states('sensor.hsem_solar_confidence_sensor') | float | round(3) }}
Confidence: {{ state_attr('sensor.hsem_solar_confidence_sensor', 'confidence_pct') }}%

EV charging plan sensors

Diagnostic sensors displaying the EV charging plan details.

Entities:

  • sensor.hsem_ev_optimal_charging_plan — Primary EV, created only when hsem_ev_planned_load_enabled is set
  • sensor.hsem_ev_second_optimal_charging_plan — Second EV, created only when hsem_ev_second_planned_load_enabled is set (issue #859)
State Meaning
not_connected EV is not plugged in
smart_charging_disabled Smart charging turned off
fully_charged Already at or above target SoC
charging EV scheduled to charge in current slot
waiting Connected but no active charging slot
unavailable Not configured or capacity/power is zero

Key attributes:

Attribute Description
battery_capacity_kwh EV battery nameplate capacity
charge_power_kw Charger AC output power
current_soc / target_soc EV SoC values
ev_connected Whether vehicle is plugged in
total_kwh_needed Energy needed to reach target
deadline ISO-8601 charging deadline
charging_slots List of allocated charging slots with details
planned_load_by_slot Dict of slot → kWh load
data_quality Diagnostic warnings

EV SoC economics sensors

Diagnostic sensors that answer one question for each EV: what would it cost to charge from the current SoC to X % by time Y? They show a table of charging costs, in real money, for the target SoCs 50, 60, 70, 80 and 100 %, against a few deadlines:

  • the configured times of day (default 08:00 and 17:00, up to 3 per EV, set in Configure → EV planned load as hsem_ev_planned_load_economics_deadlines / hsem_ev_second_planned_load_economics_deadlines), each at its next occurrence; and
  • the EV's own target deadline (time.hsem_ev_deadline_time / time.hsem_ev_second_deadline_time), which is always added as an extra column while it is ahead of now (issue #1344). When it falls on a configured time of day, the two share one column.

No target is recommended: the sensor shows the numbers and you decide.

Entities:

  • sensor.hsem_ev_soc_economics — Primary EV, created only when hsem_ev_planned_load_enabled is set
  • sensor.hsem_ev_second_soc_economics — Second EV, created only when hsem_ev_second_planned_load_enabled is set

How each cost is computed

Each row is one extra run of the planner (run_planner()) on a copy of the input of the last plan. Only that EV's target SoC and deadline are changed; prices, the PV forecast, the house battery, the other EV and every setting are reused unchanged. Its result is the money cost of the whole plan over the planning horizon (PlannerOutput.plan_cost.total_cost): grid import, minus export income, plus house-battery wear, for the house and the car together. That is plan_total_cost.

To isolate the car, one more run per EV sets only its target to its current SoC, so the plan does no target-driven charging. That is the baseline (baseline_plan_cost): what the house costs without charging the car any further. Each row's total_cost is the difference (issue #1348):

total_cost = plan_total_cost − baseline_plan_cost

So total_cost is the charging cost from the current SoC to that row's target by that deadline, including any change it causes in how the house battery and export are used. The baseline does not depend on the deadline: an EV already at its target gets no deadline in the plan, so one baseline serves every column.

total_cost is not clamped at zero. It can come out slightly negative: the planner minimises money plus the value of the energy left in the house battery at the end, not money alone, so a plan with more car charging can spend a little less money and leave the house battery a little emptier.

States

State Meaning
ready Table computed. points may still be empty when no column or no target at or above the SoC remains
not_connected EV is not plugged in. No table
smart_charging_disabled Smart charging is off, or the EV planned load is disabled. No table
unavailable No plan has run yet, the EV's SoC is unavailable, or capacity or charger power is 0

Attributes

Attribute Description
current_soc_pct EV SoC when the table was computed (null when unavailable — the table is then empty, never computed from a made-up 0 %)
target_deadline The EV's target deadline (ISO 8601) when it is ahead of the computation time, else null. Shown in every state, also when the EV is not connected
configured_deadlines The configured times of day the table was computed for, e.g. ["08:00", "17:00"]. Duplicates are removed and the list is sorted
baseline_plan_cost Whole-plan cost with this EV's target at its current SoC (no target-driven charging), which every row's total_cost is measured against. null when no row was computed. Not recorded
points Flat list of rows, one per (target SoC, deadline column). Columns are in time order, earliest deadline first; within a column, targets ascend. Not recorded (see below)

Each entry in points has:

Field Description
target_soc_pct Target SoC of this row
deadline_label Local HH:MM of the column, e.g. "08:00". Unique per column
deadline ISO 8601 deadline actually used. A configured time resolves to its next occurrence (today if still ahead, else tomorrow); the target-deadline column uses the exact deadline
is_target_deadline true in the column of the EV's own target deadline
total_cost Charging cost from the current SoC to this target by this deadline: plan_total_cost − baseline_plan_cost, in the price sensor's currency. 0.0 when the target equals the current SoC
plan_total_cost Money cost of the whole plan (house and car, whole horizon) with this target and deadline. Equals baseline_plan_cost when the target equals the current SoC
feasible Whether the charger can deliver the energy in time at its rated power: capacity × (target − SoC) ≤ charger kW × hours left × charger efficiency
delta_from_previous total_cost minus that of the previous (lower) row in the same column: what the extra SoC costs. null for the first row of a column
delta_per_10pct delta_from_previous scaled to 10 percentage points of SoC, to compare uneven steps (e.g. 80 → 100 % is a 20-point step). null for the first row

Reading the table

Rows below the current SoC are left out. At 63 % the 50 and 60 % rows are missing and every column starts at 70 %. The first row's total_cost is already the full cost from the current SoC, so its delta_from_previous is null. A target exactly equal to the SoC stays in with total_cost 0.0; it reuses the baseline run instead of a run of its own.

feasible only checks power and time. It ignores price, the grid and phase fuse limits, the charger's minimum power, the deadline safety margin and the other EV sharing the connection, so a true row can still fall short in the real plan. When a row is false, the planner charges as much as it can before the deadline and stops: its total_cost is the cost of that shortfall plan, not of reaching the target. That is why infeasible rows often show a delta of 0 — higher targets cannot be reached either, so they cost the same.

total_cost before issue #1348 was the whole-plan cost. Automations or templates that read total_cost as the plan cost should read plan_total_cost instead.

Worked example. An EV with a 77 kWh battery and an 11 kW charger at 35 % SoC, computed at 14:00, target deadline 07:00, default configured deadlines, a house using 3 kWh an hour, prices 0.05 at night, 0.10 by day and 0.30 from 16:00 to 21:00. The baseline (no further charging) costs 3.44:

Column (deadline_label) Target total_cost plan_total_cost delta_from_previous delta_per_10pct feasible
17:00 (today) 50 % 1.17 4.61 ✓
60 % 1.93 5.37 +0.76 0.76 ✓
70 % 3.73 7.17 +1.79 1.79 ✓
80 % 5.52 8.96 +1.79 1.79 ⚠
100 % 5.52 8.96 +0.00 0.00 ⚠
07:00 (is_target_deadline: true) 50 % 1.17 4.61 ✓
60 % 1.93 5.37 +0.76 0.76 ✓
70 % 2.71 6.16 +0.78 0.78 ✓
80 % 3.47 6.91 +0.76 0.76 ✓
100 % 5.01 8.46 +1.54 0.77 ✓
08:00 (tomorrow) ...

Read it like this:

  • Charging from 35 to 80 % by 07:00 costs 3.47 (34.7 kWh, about 0.10 per kWh). The plan as a whole costs 6.91, of which 3.44 is the house.
  • By 07:00, each extra 10 % (7.7 kWh) costs between 0.76 and 0.78.
  • By 17:00, 70 % costs 1.79 more than 60 %: with only three hours left, the charging reaches into the 0.30 peak that starts at 16:00. Reaching 70 % by 07:00 instead costs 1.01 less (2.71 against 3.73).
  • 80 % and 100 % cannot be reached by 17:00 (34.7 kWh in 3 hours at 11 kW): both rows are false and show the same cost, the cost of charging flat out.
  • Here the 08:00 column repeats the 07:00 one, because the extra hour adds no cheaper price. The delta_per_10pct of 100 % (0.77) is half its delta_from_previous (1.54), because 80 → 100 % is a 20-point step.

When the table is recomputed

Each row needs one planner run for every target above the current SoC in every column, plus one baseline run per EV, so up to targets × columns + 1 runs per EV:

  • default (08:00, 17:00 and the target deadline): up to 5 × 3 + 1 = 16 per EV;
  • most (3 configured times plus the target deadline): up to 5 × 4 + 1 = 21 per EV.

Both EVs are computed in one background job with a 60 s limit. If that job is still running, or runs out of time, the previous table stays.

The table is recomputed on every fresh replan, the same events that update sensor.hsem_ev_optimal_charging_plan (EV plug/unplug, charging start/stop, target-SoC threshold crossings, slot boundaries, price changes). It is also recomputed at once when its columns no longer match: a changed list of configured times, or a target deadline that changed or just passed. Otherwise, while the previous plan is being reused, it is recomputed at most every 30 minutes.

Recorder and dashboard

points and configured_deadlines are lists and baseline_plan_cost changes on every recompute, so they are never written to the recorder (issue #1099): read them from the live state. target_deadline, current_soc_pct and the state are recorded.

points is flat and already in time order. To show one line per column, walk the distinct deadline values in order, as the bundled dashboard's EV Charging Economics card does (see dashboard-setup.md); it marks the target-deadline column with ★. Jinja's groupby('deadline_label') works too, but sorts the columns by label instead of by time.


EV charger calculated power sensors

Diagnostic sensors exposing the planner's calculated EV charger power for the current slot as their state, so it can be graphed and used in automations without parsing the working-mode sensor attributes.

Entities:

  • sensor.hsem_ev_charger_calculated_power — Primary EV, created only when hsem_ev_planned_load_enabled is set
  • sensor.hsem_ev_second_charger_calculated_power — Second EV, created only when hsem_ev_second_planned_load_enabled is set (issue #859)
State Unit Description
≥ 0 W Target AC power for the charger in the active slot (0 when no charging is planned)

Key attributes:

Attribute Description
slot_start / slot_end ISO-8601 window of the active planning slot
ev_total_planned_load_kwh Total EV AC load planned for the slot (kWh)

These mirror ev_charger_calculated_power and ev_second_charger_calculated_power from the hourly_recommendation attribute of the working-mode sensor.


EV charger current limit sensors

Diagnostic sensors publishing the planner's per-slot EV charging ceiling in whole amps, for an external current controller to consume. HSEM owns the economics (how many amps are worth drawing this slot); the external controller keeps final authority for fuse safety and may only ramp within the published ceiling. Conversion always rounds down and is phase-aware (see planner-spec.md Published charging ceiling and stranded-residue re-portioning).

Entities:

  • sensor.hsem_ev_charger_current_limit — Primary EV, created only when hsem_ev_planned_load_enabled is set
  • sensor.hsem_ev_second_charger_current_limit — Second EV, created only when hsem_ev_second_planned_load_enabled is set (issue #859)
State Unit Description
≥ 0 A Ceiling for the active slot, floored to whole amps (0 when no charging is planned)

Key attributes:

Attribute Description
phase_topology The charger's configured phase topology used for the conversion
schedule Up to 24 future slots, each {start, current_a, power_w}

Startup and a failed coordinator cycle are fail-closed: the entity is unavailable and its native value is 0. A previously restored positive state is never accepted as a live actuator command (issue #789) — only a successful, live coordinator recommendation can publish a positive ceiling.


Daily plan-vs-actual sensor

Diagnostic sensor tracking daily cumulative plan-vs-actual energy deviations.

Entity: sensor.daily_plan_vs_actual

Tracks planned kWh vs actual kWh for import, export, PV, consumption, and battery throughput on a per-calendar-day basis using cumulative energy meter readings.


EV charging active sensor

Boolean sensor indicating whether any EV is actively drawing power.

Entity: sensor.hsem_ev_charging_sensor — created only when hsem_ev_planned_load_enabled is set (issue #859)

State Meaning
on At least one EV is charging
off No EV is charging

Key attributes:

Attribute Meaning
ev_soc_pct / ev_second_soc_pct Vehicle integration's reported SoC
ev_effective_soc_pct / ev_second_effective_soc_pct Reported SoC plus bounded delivered-energy credit when session identity is valid, else None
ev_delivered_energy_credit_kwh / ev_second_delivered_energy_credit_kwh Battery-side measured energy not yet reflected by vehicle telemetry
ev_power_w / ev_second_power_w Live measured charger power

Delivered-energy credit is in memory only (issue #789). It clears on disconnect, restart, unsafe session/SoC identity changes, and battery capacity changes. Invalid power readings and excessive sample gaps add no energy and break the integration interval without erasing credit already validated in the same identified session. See planner-spec.md Session EV invariant and utils/ev_delivered_energy.py.


Battery SoC sensor

Snapshot of the battery state of charge with optional learned capacity tracking.

Entity: sensor.hsem_battery_soc_sensor

State Unit Description
0–100 % Battery SoC percentage

Key attributes:

Attribute Unit Description
learned_capacity_kwh kWh Learned usable battery capacity from charge/discharge cycles
capacity_samples int Number of charge/discharge samples contributing to the learned capacity

House load power sensor

The house's own power draw derived from the Huawei inverters and power meter (issue #1310), for installations without an EMMA or SDongle load sensor. Created only when Inverter Active Power and Power Meter Active Power are set in the Power step.

Entity: sensor.hsem_house_load_power

State Unit Description
a float W Σ inverter active power − power meter active power (export positive)

It is unavailable, never 0, while any input is unavailable or non-numeric. Inputs in kW are converted to W. HSEM reads it as the house consumption power when hsem_house_consumption_power is empty or points to an entity that does not exist. See home.md.

Attribute Description
inverter_active_power_entities The inverters' active power entities
power_meter_active_power_entity The power meter's active power entity

Dynamic discharge floor

Controls and reports the effective discharge floor SoC: the reserve the battery needs now to carry the house to the next refill, when the dynamic floor feature is enabled.

The state is the configured minimum SoC plus the reserve: reserve_kwh × safety_margin ÷ rated capacity × 100, capped at the configured maximum SoC (issue #1221). A reserve_kwh of 1.7 on a 10 kWh battery with a 5 % minimum and a margin of 1.15 reads 24.6 %, and the battery then holds 1.96 kWh above its minimum. Before that fix the state was the reserve's share of the usable capacity read as an absolute SoC (20.6 % in the same case), which held less than the reserve and could exceed 100 %.

The sensor reports the reserve at the start of the current slot. The planner does not hold that value for the whole horizon: the reserve declines slot by slot and is gone after the refill (issue #1188). Within the current slot the plan may take the battery down to the next slot's reserve, so the live SoC can read slightly below this sensor until the next replan. When the live SoC is already below the reserve, the battery serves the most expensive hours until the refill and waits in the cheapest ones (issue #1222); estimated_battery_soc_pct in the plan always starts at the inverter's reading (issue #1094).

The refill scan reads planned grid charges from a floor-free reference solve in the same replan (issue #1140). When that plan charges from the grid before the next solar surplus, the refill_type attribute is grid_charge and reserve_kwh is the house load until the charge, whatever the size of the charge (issues #1214, #1220); the load behind the charge is not reserved. A charge that only buys back the excess-export buffer after the plan sold the battery is not a refill (issue #1239): the night behind it is still reserved. When the plan charges in the current slot, reserve_kwh is 0 and the floor equals the configured minimum SoC. A planned charge counts from the first slot priced like the one it is planned in (within one battery cycle cost), so next_refill_slot can be earlier than the slot the plan charges in (issue #1198). When it does not, but a slot before the next solar surplus is priced within one battery cycle cost of the cheapest import price in the next 48 h and the battery could charge enough there, the floor is released the same way with refill_type: grid_available (issue #1156). When it does not either, but a slot before the next solar surplus is priced within one battery cycle cost of the cheapest price between it and that surplus, with dearer slots behind it, the bridge ends at the first such slot with refill_type: cheap_window (issue #1247): the plan can buy there for the dearer slots whether or not it does, so a 0.15 night before a 0.12 day reserves the evening before it and nothing behind it, and inside the window the floor is the configured minimum. Otherwise the floor bridges to the next solar surplus (refill_type: solar_surplus). Between replans the sensor keeps the floor the current plan was solved with.

While the floor reserves energy, the constraints attribute of sensor.hsem_plan_explanation_sensor lists dynamic_discharge_floor, and battery_below_dynamic_floor when the live SoC is at or below it (issue #1227). With battery_below_dynamic_floor listed, batteries_wait_mode with grid import in the cheapest hours before the refill is the floor keeping the battery for the more expensive ones.

Entities:

  • sensor.hsem_effective_discharge_floor_sensor — Current effective floor SoC (%)
  • switch.hsem_dynamic_discharge_floor — Enable/disable the dynamic floor feature

sensor.hsem_effective_discharge_floor_sensor

Property Value
Type sensor
State Current effective discharge floor SoC percentage
Unit %

switch.hsem_dynamic_discharge_floor

Property Value
Type switch
State on (dynamic floor active) or off (static floor)

Template example:

{% if is_state('switch.hsem_dynamic_discharge_floor', 'on') %}
  Dynamic floor: {{ states('sensor.hsem_effective_discharge_floor_sensor') }}%
{% endif %}

OCPP charger sensors

Sensors providing live status and diagnostics for an OCPP-compliant EV charger connected via the integrated OCPP server. HSEM runs one OCPP server per EV (primary EV on port 9000 by default, second EV on port 9001). The primary sensors below are only created when hsem_ocpp_enabled is set; the _second variants exist only when the second EV is configured and the second OCPP server is enabled (issue #859).

Configuration keys (set in config flow):

Key Description
hsem_ocpp_enabled Enable the OCPP server
hsem_ocpp_port TCP port for the primary EV's OCPP WebSocket connections
hsem_ocpp_cpid OCPP charge point identifier
hsem_ocpp_start_window_s Seconds before charge deadline to start charging
hsem_ocpp_stop_window_s Seconds after charge deadline to stop charging
hsem_ocpp_second_enabled Enable the dedicated second-EV OCPP server (requires second EV)
hsem_ocpp_second_port TCP port for the second EV's OCPP WebSocket connections
hsem_ocpp_second_cpid Second EV charger's OCPP charge point identifier

sensor.hsem_ocpp_charger_status_sensor

Property Value
Type sensor
State not_configured (this EV's OCPP server isn't enabled), disconnected (enabled, no charger connected), or the live connection/charging state (Available, Preparing, Charging, Finishing)
Attributes listening (bool — server socket bound), port, requested_current_a (whole amps in the last SetChargingProfile sent to the charger, None until the first profile has been sent — issue #886), anti_flap_state ("idle"/"starting"/"charging"/"stopping" — HSEM's own anti-flap state machine, issue #892), stalled (bool — True when the active session has been stuck reporting "SuspendedEVSE"/"Faulted"/"Unavailable" for over 5 minutes despite an open transaction; "SuspendedEV", an EV-decided pause, is never flagged — issue #894), url (best-effort ws://<host>:<port>/<cpid>, or wss:// with OCPP TLS on (issue #1341), for the EVSE's OCPP config, including the configured charge-point ID, when HA can resolve a reachable host) — present only when this EV's OCPP server is enabled; plus one entry per connected charger CPID with status, power_w, transaction_id, connected_at, status_changed_at (issue #894)

sensor.hsem_ocpp_charger_power_sensor

Property Value
Type sensor
State Live charging power (kW)
Device class power
Unit kW

Reports 0 whenever the connector's status is anything other than "Charging" (issue #969) — previously it kept showing the last MeterValues reading verbatim, so e.g. a connector that had just transitioned to SuspendedEVSE could still display several kW with no fresh meter data to explain why.

sensor.hsem_ocpp_charger_info_sensor

Property Value
Type sensor
State Charger summary string
Attributes vendor, model, firmware_version, serial_number

sensor.hsem_ocpp_charger_sessions_sensor

Property Value
Type sensor
State Number of completed sessions
Attributes sessions — list of completed session logs (start time, energy, duration)

Second-EV variants

When the second EV is configured with its own OCPP server, four additional diagnostic sensors are created with _second suffixed entity IDs:

  • sensor.hsem_ocpp_charger_status_sensor_second
  • sensor.hsem_ocpp_charger_power_sensor_second
  • sensor.hsem_ocpp_charger_info_sensor_second
  • sensor.hsem_ocpp_charger_sessions_sensor_second

These mirror the primary sensors but read from the second EV's dedicated OCPP server.

Template example:

{{ states('sensor.hsem_ocpp_charger_status_sensor') }}
{{ states('sensor.hsem_ocpp_charger_power_sensor') | float | round(2) }} kW

Savings tracker sensor

Tracks actual vs missed savings over a rolling 90-day window. Savings include export revenue, avoided grid-import cost from battery discharge, and the price difference when battery energy is charged below the mean positive import price for the current cycle's calendar day. Missing, non-finite, and non-positive planner prices are excluded from that daily mean.

The discharge part needs the battery's Charge/discharge power sensor (Huawei Solar step of the configuration). It is optional: without it, or while it is unavailable, savings from battery discharge are not counted and the figure covers export revenue and cheap charging only.

Entity: sensor.hsem_savings_tracker_sensor

Property Value
Type sensor (diagnostic)
State Today's actual savings (local currency): today_actual
Device class monetary
State class total

Three figures are tracked, each for today, the last 7 and 30 days, and in total since the integration was installed:

  • actual — savings that accrued while HSEM was in control (force mode auto);
  • missed — savings that accrued while a working mode was forced: export revenue and discharge savings of that time are booked here instead of under actual. Charge savings need a plan and are not counted while a mode is forced;
  • baseline — what a passive installation without a battery would have paid for the same house load and PV.
Attribute Description
today_actual, today_missed, today_baseline Today's figures
last_7_days_actual, last_7_days_missed Sums over the last 7 days, today included
last_30_days_actual, last_30_days_missed Sums over the last 30 days, today included
total_actual, total_missed, total_baseline Cumulative since the integration was installed
daily Daily records, newest 90: date, actual_savings, missed_savings, baseline_cost
max_history_days, history_total_days Days kept, and days currently held

daily is not written to the recorder (see Recorder footprint); the history is kept in .storage/hsem_savings_history.json.

These are per-cycle estimates. For how the day compares with the best it could have been, see the Performance sensors.

Template example:

{% set s = 'sensor.hsem_savings_tracker_sensor' %}
Today: {{ state_attr(s, 'today_actual') }} (missed {{ state_attr(s, 'today_missed') }})
Last 30 days: {{ state_attr(s, 'last_30_days_actual') }}

Performance sensors

How good the last day's control turned out to be (issue #1182), where what was missed was lost: in battery dispatch or in the handling of PV surplus (issue #1270), and how well the timing of EV charging was chosen (issue #1271).

Entities (on the HSEM Financial device, all with the performance prefix):

Entity State Unit
sensor.hsem_performance_savings_capture Share of the achievable savings that HSEM actually achieved %
sensor.hsem_performance_actual_benefit What HSEM's control was worth against plain self-consumption local currency
sensor.hsem_performance_missed_opportunity What perfect hindsight would have saved on top of that local currency
sensor.hsem_performance_battery_utilisation Share of the potential not lost through battery dispatch %
sensor.hsem_performance_solar_utilisation Share of the potential not lost through PV surplus handling %
sensor.hsem_performance_ev_charging_efficiency Share of what the best charging times save over plug-and-charge that was realized (only with EV planning enabled) %

All of them cover the trailing 24 hours, are updated once per completed slot, and are diagnostic sensors with the measurement state class. They are rolling values, not totals, so they carry no monetary device class (Home Assistant allows that one only on totals); the amounts use Home Assistant's currency as their unit.

The bundled dashboard has a Performance view that shows all of this, including the 7- and 30-day figures and a day-by-day breakdown: see Performance view.

What is compared

Three ways the same 24 hours could have gone, over the same slots, the same realized prices and the same load:

Run What it is
realized What was paid: grid import × import price − grid export × export price
baseline Plain inverter self-consumption — the hardware with nobody controlling it
oracle The cheapest the 24 hours could have been, with perfect foresight

From them:

Figure Definition Meaning
potential baseline − oracle What the best possible control could have saved
missed opportunity realized − oracle What was left on the table (never negative)
actual benefit potential − missed What HSEM's control was worth against self-consumption
savings capture benefit ÷ potential The share of the potential that was realized

Worked example. Self-consumption would have cost 5.00, HSEM's day cost 3.40, and the best possible day costs 3.00, all three ending with the same energy in the battery. Potential 2.00, missed opportunity 0.40, actual benefit 1.60, savings capture 80 %.

Three rules keep the comparison fair:

  1. The battery must end as full as it really did. A run that ends with a fuller battery has paid for energy it has not used yet. Left alone, the oracle would win every day by emptying the battery, and every reserve would look like a mistake. So each oracle starts with the energy the battery really had and must end with at least the energy of the run it is compared with. Energy HSEM still holds at the end of the window is therefore not missed; holding it during the window while importing at a high price is.
  2. Your own settings are not a missed opportunity. The oracle respects the hard limits: usable capacity, charge and discharge power, efficiencies, the main fuse, the export limit, the export fee, and whether (and from which export price) you let the battery export at all. It does not get HSEM's own caution — dynamic floor, reserves, hysteresis, wear pricing, terminal value — because that is what is being measured.
  3. The EV is fixed load. The load is everything that is not the battery, read off the meters as grid import − grid export − battery charged + battery discharged. The EV is inside it in all three runs, so when the car was charged is not scored yet (that is stage 3).

Where it was lost: battery and solar

The missed opportunity is split in two by one more hindsight run that sits between the realized day and the oracle:

Run What it may decide
realized Nothing: what happened
PV-as-realized oracle Grid charging and discharging, with perfect foresight; PV surplus is stored and exported as it really was
oracle Everything

Each run can do everything the one above it can, so their costs are ordered, and the two differences add up to the missed opportunity exactly:

Figure Definition Meaning
battery loss realized − PV-as-realized What better grid charging and better-timed discharging would have saved
solar loss PV-as-realized − oracle What storing or exporting the PV surplus at better times would have saved
battery utilisation 1 − battery loss ÷ potential Share of the potential not lost through battery dispatch
solar utilisation 1 − solar loss ÷ potential Share of the potential not lost through PV surplus handling

Both percentages use the same potential as the savings capture, so the shortfalls add up: a capture of 65 % with battery utilisation 72 % and solar utilisation 93 % reads "35 points were lost, 28 on battery dispatch and 7 on PV surplus".

A reserve held through an expensive evening is battery loss. Surplus sold cheaply at noon and bought back in the evening is solar loss. A slot's PV surplus is what its PV left over after the load; "as it really was" means the oracle stores exactly as much of it as was really stored (and may add grid charge on top where all of it was stored).

Storing surplus and using it later are two decisions, and the value of the pair is booked under solar. The split says where a loss became visible, not which setting caused it: surplus that could not be stored because the battery had been filled from the grid overnight shows up as solar loss.

On the real days committed in the repository (a 15 kWh installation) most of what was missed is battery dispatch, with solar utilisation around 90 % on the ordinary days:

Day Missed Battery loss Solar loss Capture Battery Solar
2026-09-15 3.26 2.57 0.70 65.0 % 72.4 % 92.5 %
2026-09-26 19.51 13.03 6.48 −26.7 % 15.3 % 57.9 %
2026-09-29 2.29 1.77 0.53 54.5 % 65.0 % 89.5 %

(from the backtest's battery energy meters; the sensors use SoC-derived flows and differ by a few points, see The same numbers as the backtest below).

EV charging efficiency

sensor.hsem_performance_ev_charging_efficiency exists when EV planning is enabled for at least one charger. It scores the timing of EV charging: the energy the car got is taken as given, and only when it was delivered is compared.

A session is, per charger, a run of consecutive slots in which the car was plugged in and HSEM was managing the charging: EV planning enabled, smart charging on, "force charge now" off. Inside a session the energy that was really delivered may be moved, but not across the charge deadline. Everything else the charger delivered stays fixed load, as in the figures above.

Three hindsight runs, all with the battery dispatched optimally, differ only in where the session energy sits:

Run EV energy
plug-and-charge Every session delivered at full charger power from the moment the car was plugged in
oracle Delivered when it really was (the oracle of the figures above)
joint oracle Delivered at the cheapest times within each session
Figure Definition Meaning
EV missed opportunity oracle − joint oracle What better timing would have saved (never negative)
EV potential plug-and-charge − joint oracle What the best timing saves over none
EV charging efficiency 1 − EV missed ÷ EV potential The share of that potential that was realized

100 % is the best possible timing, 0 % is no better than plugging in and charging, and the figure is negative when the timing was worse than that. It is unknown when the EV potential is below 0.05 or no managed session delivered energy.

The EV figure is on top of the missed opportunity, not part of it. The missed opportunity, battery and solar figures treat the EV as fixed load and do not change. The joint oracle can do everything the oracle can, so

realized − joint oracle = battery loss + solar loss + EV missed opportunity

exactly. The EV is scored against an optimally run battery, not against the battery as it ran: that is what makes the parts add up, and it keeps a badly run battery from making the EV timing look good or bad.

What it needs and what it leaves out:

  • A charger power sensor and a connected sensor for the charger. Without a connected sensor there is no session to move energy within.
  • The charging power is capped at the configured charger power, or at the highest power the session itself reached when none is configured.
  • The oracle may charge at any power up to that cap. It does not know the charger's minimum current or its phase switching, so it is a little more optimistic than the hardware allows.
  • The EV energy comes from the charger's power reading, sampled once per update cycle. At the default 5-minute cycle that is coarse.
  • A session that reaches past the edge of the 24-hour window is scored for the part inside it.
  • The definition has not yet been checked against a real installation with an EV; the repository's recorded days carry no EV data.

Perfect hindsight is an upper bound, not a target

The oracle knows the day's PV, load and prices exactly. HSEM planned from forecasts. 100 % is not reachable, and a capture of 60–80 % on a day with an ordinary forecast error is a good result. What the figure is for is the trend, and days that stand out. Telling forecast error from planner error is what the offline backtest does (scripts/backtest_attribute.py, see backtest-harness.md).

Which number to watch. On a day with flat prices there is almost nothing to gain, and a share of almost nothing is noise: below a potential of 0.05 (in your currency) the savings capture is unknown. Missed opportunity stays meaningful on such days and is the more robust headline. A capture can be negative — the day cost more than plain self-consumption would have — and is reported as such, never clamped to 0.

Not the savings tracker

sensor.hsem_savings_tracker_sensor answers a different question, and its missed figures mean something else:

Savings tracker Performance sensors
"Actual" Sum of per-cycle estimates (export revenue, cheap charging…) baseline − realized, with the battery's end energy equalised
"Missed" Savings that accrued while force mode was not auto What perfect hindsight would have saved on top
Compared with A live estimate of a house without a battery The same slots run as self-consumption and as the best possible day

Attributes

All performance sensors publish the same attributes:

Attribute Description
reason Why a value is unknown (see below); null when all three are known
actual_benefit, missed_opportunity, savings_capture The three figures of the window (capture in percent)
potential baseline − oracle
battery_loss, solar_loss The two parts of the missed opportunity (they add up to it)
battery_utilisation, solar_utilisation The same as shares of the potential, in percent
breakdown_reason Why the battery and solar figures are unknown; null when they are known
ev_missed_opportunity, ev_potential What better EV charging times would have saved, and what the best times save over plug-and-charge
ev_charging_efficiency 1 − ev_missed_opportunity ÷ ev_potential, in percent
ev_shiftable_kwh EV energy delivered in managed sessions
ev_reason Why the EV figures are unknown; null when they are known
realized_cost, baseline_cost, oracle_cost The cost of each run
window_start, window_end, slot_count The 24 physical hours that were scored
coverage Share of the window that was observed (0–1)
control_coverage Share of the observed time in which HSEM was in control (0–1)
start_stored_kwh Energy in the battery at the start of the window
realized_end_stored_kwh, baseline_end_stored_kwh Energy the real run and the self-consumption run end with
residual_kwh Energy the meters cannot explain (see below)
solver_status optimal, unsolved, unavailable or not_run
notes Anything about the data that qualifies the score
yesterday The same figures for the last completed local day
last_7_days, last_30_days Benefit, missed opportunity, potential and capture over completed days
daily The newest 30 completed days one by one, oldest first: date, reason, the three savings figures, potential, the battery and solar losses and utilisations, ev_missed_opportunity and ev_charging_efficiency. For the dashboard charts

control_coverage tells a planner miss from a day HSEM was not driving: it is below 1 when force mode was not auto, HSEM was read-only, or the cycle was degraded. A poor score with a low control coverage is not the planner's.

Periods are ratios of sums. last_7_days and last_30_days add up the costs of the completed local days and divide once; they are never a mean of daily percentages, which would let a day with nothing to gain weigh as much as a day with a lot. scored_days says how many of the period's days carry a score; a day that could not be scored is left out, never counted as zero.

When a value is unknown

No number is published that cannot be stood behind. reason is one of:

reason Meaning
not_evaluated Nothing has been scored yet
insufficient_coverage Part of the window was not observed: a slot is missing or was observed for less than 90 %
no_site_limits HSEM has not planned yet since it was installed, so the battery's limits are not known
potential_below_threshold Almost nothing could have been saved: only the capture is unknown, the two amounts are valid
stored_energy_outside_limits The battery's SoC ran more than 10 % of the usable capacity outside the configured SoC limits
energy_balance_residual The meters contradict each other (see residual_kwh)
scipy_unavailable The solver library is not installed
oracle_unsolved The best possible day could not be found, or not proven best, within 10 seconds
evaluation_failed The scoring raised an error (see the HSEM log)

breakdown_reason takes the same values, and one more: breakdown_unsolved when the score itself is valid but the extra hindsight run behind the split could not be solved in time. The savings capture, actual benefit and missed opportunity are then published as usual and only the two utilisation sensors are unknown.

In the last_7_days and last_30_days attributes the battery and solar figures are taken over the days that carry the split (breakdown_days), with those days' own potential. Days scored before this split existed count towards scored_days but not towards breakdown_days.

ev_reason takes the same values as reason, and two more: no_ev_session when no managed session delivered energy that could have been moved, and ev_unsolved when the two EV hindsight runs could not be solved in time. The EV period figures are taken over the days that carry an EV score (ev_days).

A new installation shows insufficient_coverage for its first 24 hours. So does a restart that kept Home Assistant down for more than about eight minutes: the energy meters keep counting, so a short gap loses nothing, but a long one cannot be placed in time.

Requirements

  • Grid import and export energy meters configured in HSEM (the same ones the financial sensors use). Without them there is no realized cost.
  • Import and export price sensors with a current value.
  • The battery's flows are taken from the change in its state of charge, through the configured rated capacity and efficiencies. No battery power sensor is needed.
  • PV production (the PV energy meter, or the PV power sensor as a fallback) is used for residual_kwh and to let the oracle curtail PV when export prices are negative.

residual_kwh is a consistency check: a house cannot have a negative load, so the running total of grid import − grid export − battery flows + PV must never fall. The largest fall is energy the meters cannot explain. It stays near zero on a consistent installation and grows when a meter, a unit or the configured battery capacity is wrong; above the largest of 1 kWh, 15 % of the usable capacity and 10 % of the PV produced, the window is not scored.

The same numbers as the backtest

The definitions and the code are shared with the offline backtest (custom_components/hsem/planner/hindsight_scoring.py). A local day scored by the sensors (yesterday) equals

python3 scripts/backtest_score.py <actuals> --battery-flows soc

on the same slots. Without --battery-flows soc the backtest reads the battery's energy meters instead of its SoC, and the two differ by a few points of capture on real days; see backtest-harness.md.

The record is kept in .storage/hsem_performance_history.json: the slots of the last 50 hours and the scores of the last 90 completed days. It survives a restart.

Template example:

{% set s = 'sensor.hsem_performance_savings_capture' %}
Last 24 h: {{ states(s) }} % ({{ state_attr(s, 'missed_opportunity') }} missed)
Last 30 d: {{ state_attr(s, 'last_30_days').savings_capture }} %

PV curtailment sensor

Detects when the inverter is actively curtailing PV production.

Entity: sensor.hsem_pv_curtailment_sensor

Property Value
Type sensor
State curtailed (PV being limited) or normal (no curtailment)
Entity category diagnostic

Template example:

{% if is_state('sensor.hsem_pv_curtailment_sensor', 'curtailed') %}
  PV is being curtailed
{% endif %}

Diagnostic sensors

Entity Display Name Purpose State / Value
sensor.hsem_applier_status_sensor Inverter Apply Status Hardware write success/failure ok, unverified, failed, skipped
sensor.hsem_battery_soc_sensor Battery State of Charge Battery SoC snapshot Percentage (0–100)
sensor.daily_plan_vs_actual Daily Plan vs Actual Daily energy plan-vs-actual tracking Dict with cumulative metrics
sensor.hsem_degraded_mode_sensor System Health Overall system health ok, degraded, error
sensor.hsem_ev_charger_calculated_power EV Charger Calculated Power Planner target power for primary EV charger Watts (W)
sensor.hsem_ev_second_charger_calculated_power EV 2 Charger Calculated Power Planner target power for second EV charger Watts (W)
sensor.hsem_ev_charger_current_limit EV Charger Current Limit Planner charging ceiling for primary EV charger Amps (A)
sensor.hsem_ev_second_charger_current_limit EV 2 Charger Current Limit Planner charging ceiling for second EV charger Amps (A)
sensor.hsem_ev_charging_sensor EV Charging Active Any EV actively charging on, off
sensor.hsem_ev_optimal_charging_plan EV Optimal Charging Plan Primary EV plan state charging, waiting, etc.
sensor.hsem_ev_second_optimal_charging_plan EV Second Optimal Charging Plan Second EV plan state charging, waiting, etc.
sensor.hsem_force_mode_sensor Force Working Mode Override active indicator auto or override mode name
sensor.hsem_solar_confidence_sensor Solar Forecast Confidence Per-hour PV forecast accuracy factors Mean factor (ratio)
sensor.hsem_hardware_writes_sensor Hardware Writes Writes allowed/blocked by safety gate allowed, blocked
sensor.hsem_read_only_sensor Read-Only Mode Read-only mode indicator on, off
sensor.hsem_net_consumption_sensor Net Consumption Net load (house minus solar) Watts (W)
sensor.hsem_last_updated_sensor Last Updated Last coordinator cycle timestamp ISO-8601 timestamp
sensor.hsem_next_update_sensor Next Update Next scheduled coordinator cycle ISO-8601 timestamp
sensor.hsem_missing_entities_sensor Missing Input Entities Count of missing input entities Integer
sensor.hsem_plan_explanation_sensor Plan Explanation Planner strategy and cost breakdown Winning candidate name
sensor.hsem_prediction_accuracy_sensor Prediction Accuracy Multi-horizon forecast accuracy soc_mae_7d
sensor.hsem_forecast_accuracy_sensor Forecast Accuracy PV and load forecast MAE kWh
sensor.hsem_recommendation_interval_sensor Recommendation Interval Slot width and horizon info Minutes
sensor.hsem_update_interval_sensor Update Interval Current polling interval Minutes
sensor.hsem_workingmode_sensor Working Mode Active battery recommendation Working mode state
sensor.hsem_export_income Export Income Cumulative export revenue Monetary (total)
sensor.hsem_import_cost Import Cost Cumulative import cost Monetary (total)
sensor.hsem_net_grid_balance Net Grid Balance Export income minus import cost Monetary (total)
sensor.hsem_effective_discharge_floor_sensor Effective Discharge Floor Current effective floor SoC Percentage
sensor.hsem_ocpp_charger_status_sensor OCPP Charger Status Charger connection/charging state String
sensor.hsem_ocpp_charger_power_sensor OCPP Charger Power Live charging power kW
sensor.hsem_ocpp_charger_info_sensor OCPP Charger Info Vendor, model, firmware, serial String
sensor.hsem_ocpp_charger_sessions_sensor OCPP Charger Sessions Completed session log Integer
sensor.hsem_savings_tracker_sensor Savings Tracker Actual vs missed savings (90-day) Monetary
sensor.hsem_performance_savings_capture Performance: Savings Capture Share of the achievable savings achieved (24 h) Percentage
sensor.hsem_performance_actual_benefit Performance: Actual Benefit Benefit over plain self-consumption (24 h) Local currency
sensor.hsem_performance_missed_opportunity Performance: Missed Opportunity What perfect hindsight would have added (24 h) Local currency
sensor.hsem_performance_battery_utilisation Performance: Battery Utilisation Potential not lost through battery dispatch Percentage
sensor.hsem_performance_solar_utilisation Performance: Solar Utilisation Potential not lost through PV surplus handling Percentage
sensor.hsem_performance_ev_charging_efficiency Performance: EV Charging Efficiency Realized share of the best EV charging timing (EV planning only) Percentage
sensor.hsem_pv_curtailment_sensor PV Curtailment PV curtailment detection curtailed / normal

Select entities

Force working mode

Entity: select.hsem_force_working_mode

Option Description
auto Normal operation — planner controls battery
batteries_charge_grid Force grid charge
batteries_charge_solar Force solar charge
batteries_discharge_mode Force discharge to house
batteries_discharge_window_mode Force discharge to house (window label)
batteries_wait_mode Force idle
ev_smart_charging Force EV charging
force_batteries_discharge Force discharge to grid
force_export Force all energy to export

Solcast PV forecast likelihood

Entity: select.hsem_solcast_likelihood

Selects which Solcast likelihood scenario to use for PV forecasts (e.g. p10, p50, p90). This setting is also configurable in the options flow.


Switch entities

EV switches are only created when the corresponding EV's planned load (managed charging) is enabled — hsem_ev_planned_load_enabled for the primary EV, hsem_ev_second_planned_load_enabled for the second EV (issue #859). All other switches are always created.

Entity Purpose
switch.hsem_read_only Block all hardware writes
switch.hsem_extended_attributes Enable extended diagnostic attributes
switch.hsem_verbose_logging Enable verbose logging
switch.hsem_ev_charger_force_max_discharge_power Force EV maximum discharge power
switch.hsem_ev_smart_charging Enable smart EV charging scheduling
switch.hsem_ev_force_charge_now Force immediate EV charging
switch.hsem_ev_second_smart_charging Enable smart charging for second EV
switch.hsem_ev_second_force_charge_now Force immediate second EV charging
switch.hsem_ml_consumption_enabled Enable ML-based consumption prediction
switch.hsem_ml_consumption_sequential Enable sequential (intra-day momentum) ML mode
switch.hsem_dynamic_discharge_floor Enable dynamic discharge floor
switch.hsem_ev_auto_full_negative_price Auto-Full EV on negative price
switch.hsem_ev_min_soc_charge_enabled Charge EV immediately below its minimum SoC
switch.hsem_ev_second_min_soc_charge_enabled Charge second EV immediately below its minimum SoC
switch.hsem_ev_cheap_price_charge_enabled Charge EV immediately at or below a price
switch.hsem_ev_second_cheap_price_charge_enabled Charge second EV immediately at or below a price

Number entities

number.hsem_ev_target_soc, number.hsem_ev_min_soc_charge_soc and number.hsem_ev_cheap_price_charge_max_price are only created when hsem_ev_planned_load_enabled is set; the number.hsem_ev_second_* versions only when hsem_ev_second_planned_load_enabled is set (issue #859). The battery efficiency numbers are always created.

Entity Purpose Range
number.hsem_battery_charge_efficiency Battery charge efficiency 1–100 %
number.hsem_battery_discharge_efficiency Battery discharge efficiency 1–100 %
number.hsem_ev_target_soc Primary EV target SoC 0–100 %
number.hsem_ev_second_target_soc Second EV target SoC 0–100 %
number.hsem_ev_min_soc_charge_soc Primary EV minimum SoC (charge immediately below it) 1–100 %
number.hsem_ev_second_min_soc_charge_soc Second EV minimum SoC (charge immediately below it) 1–100 %
number.hsem_ev_cheap_price_charge_max_price Primary EV cheap-price threshold (import sensor's unit) −10 to 10
number.hsem_ev_second_cheap_price_charge_max_price Second EV cheap-price threshold (import sensor's unit) −10 to 10

Time entities

time.hsem_ev_deadline_time is only created when hsem_ev_planned_load_enabled is set; time.hsem_ev_second_deadline_time only when hsem_ev_second_planned_load_enabled is set (issue #859).

Entity Purpose
time.hsem_ev_deadline_time Primary EV charge deadline
time.hsem_ev_second_deadline_time Second EV charge deadline

Config flow additions

Quick setup (#610)

The config flow includes a quick_setup step that auto-detects HA entities (Huawei inverter, EV charger, Solcast forecasts, price sensors) to reduce manual configuration.

OCPP server

OCPP configuration is exposed through the config flow with these keys: hsem_ocpp_enabled, hsem_ocpp_port, hsem_ocpp_cpid, hsem_ocpp_start_window_s, hsem_ocpp_stop_window_s. See OCPP charger sensors above.


Services

hsem.create_dashboard

Logs the path to the bundled HSEM dashboard YAML and provides import instructions. The dashboard includes cards for working mode, battery SoC, financial sensors, EV status, and plan explanation. Import the YAML manually via Developer Tools → Services or Settings → Dashboards.

The service does not create or modify Home Assistant dashboards automatically; it only surfaces the bundled YAML path for manual import.


Internal additions (no new sensors)

These changes are internal to the planner and do not expose new entities:

  • Weekday/weekend profiling (#612): WeekdayProfile module-level singleton distinguishes weekday vs weekend consumption patterns for more accurate load forecasting.
  • Session EV charging (#615): EVConfig.session_charge_kw field allows per-session charge power configuration for EV co-optimisation.

Data quality attribute

The data_quality dict on the working mode sensor is the serialized planner input-completeness report. Example structure:

{
  "is_complete": false,
  "load_forecast_ready": false,
  "load_forecast_reason": "zero_forecast_with_live_demand",
  "load_forecast_missing_hours": [],
  "load_forecast_estimated_hours": [],
  "horizon_has_tomorrow": true,
  "horizon_days": 2,
  "tomorrow_price_missing_hours": [],
  "tomorrow_pv_missing_hours": [],
  "day2_price_missing_hours": [],
  "day2_pv_missing_hours": [],
  "today_price_missing_hours": [],
  "today_pv_missing_hours": []
}

The *_price_missing_hours and *_pv_missing_hours lists name the hours no source covered (issue #1196). A missing price is planned with the same-hour price of the nearest earlier day (the same quarter of the hour when prices are quarter-hourly), a missing PV forecast with zero. With a 36 or 48 h horizon tomorrow_price_missing_hours is therefore filled, and is_complete false, every day until the day-ahead prices are published; a price forecast sensor that covers tomorrow closes the gap. PV hours are only listed when a PV forecast sensor is configured. A published price or forecast of exactly 0 is data and is not listed.

load_forecast_ready is false when average-sensor provenance or a populated future value is missing, non-finite, or negative. A complete zero profile is valid while live house demand is at most 50 W; above that threshold the reason is zero_forecast_with_live_demand. is_complete requires both load readiness and complete price/PV inputs.

load_forecast_missing_hours lists the hour blocks whose rolling-average sensors have no stored sample yet. Up to 4 such hours are planned with a conservative estimate (the larger of the nearest measured hours on each side) and listed in load_forecast_estimated_hours. is_complete stays false while any hour is estimated. With more missing hours the forecast is not ready (source_unavailable) and the missing hours are still listed (issue #1110).