-
-
Notifications
You must be signed in to change notification settings - Fork 6
dashboard setup
Step-by-step guide for setting up the HSEM ApexCharts dashboard in Home Assistant.
- ApexCharts Card installed via HACS
-
HSEM integration configured and running (the
sensor.hsem_workingmode_sensorentity must be available) - The
hourly_recommendationsattribute must be populated (wait for the first planner cycle to complete)
- In Home Assistant, go to Settings → Dashboards.
- Click the three-dot menu in the top-right corner and select Raw configuration editor.
- Paste the YAML below into your dashboard.
- Replace
sensor.batteries_state_of_capacityandsensor.power_importwith your own battery SoC and grid import power entities. - Save and refresh your dashboard.
Tip: Start in Read-Only mode (
switch.hsem_read_onlyon) to safely review planner recommendations before enabling hardware writes.
views:
- title: HSEM
badges: []
sections:
- type: grid
cards:
- type: heading
heading: HSEM Working Mode Recommendation
heading_style: title
grid_options:
columns: full
rows: 2
- type: custom:apexcharts-card
update_interval: 5m
experimental:
disable_config_validation: true
grid_options:
columns: full
layout_options:
grid_columns: 3
grid_rows: 1
header:
show: false
graph_span: 48h
span:
start: day
now:
show: true
color: red
label: Now
apex_config:
chart:
height: 120
stroke:
curve: stepline
xaxis:
labels:
format: HH
rotate: -45
rotateAlways: true
hideOverlappingLabels: true
style:
fontSize: 10
fontWeight: 500
yaxis:
show: false
min: 0
max: 1
tickAmount: 1
series:
- name: Batteries Charge From Grid
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: area
opacity: 1
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end, recommendation })
=> {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
const on = recommendation === 'batteries_charge_grid' ? 1 : null;
out.push([s, on], [e, on]);
}); return out;
color: "#ef4444"
- name: Batteries Charge From Solar
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: area
opacity: 1
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end, recommendation })
=> {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
const on = recommendation === 'batteries_charge_solar' ? 1 : null;
out.push([s, on], [e, on]);
}); return out;
color: "#22c55e"
- name: Batteries Discharge Mode
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: area
opacity: 1
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end, recommendation })
=> {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
const on = recommendation === 'batteries_discharge_mode' ? 1 : null;
out.push([s, on], [e, on]);
}); return out;
color: "#f97316"
- name: Batteries Wait Mode
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: area
opacity: 1
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end, recommendation })
=> {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
const on = recommendation === 'batteries_wait_mode' ? 1 : null;
out.push([s, on], [e, on]);
}); return out;
color: "#8b5cf6"
- name: EV Smart Charging
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: area
opacity: 1
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end, recommendation })
=> {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
const on = recommendation === 'ev_smart_charging' ? 1 : null;
out.push([s, on], [e, on]);
}); return out;
color: "#06b6d4"
- name: Time Passed
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: area
opacity: 1
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end, recommendation })
=> {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
const on = recommendation === 'time_passed' ? 1 : null;
out.push([s, on], [e, on]);
}); return out;
color: "#64748b"
- name: Force Batteries Discharge
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: area
opacity: 1
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end, recommendation })
=> {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
const on = recommendation === 'force_batteries_discharge' ? 1 : null;
out.push([s, on], [e, on]);
}); return out;
color: "#ec4899"
- type: tile
entity: sensor.hsem_workingmode_sensor
features_position: bottom
vertical: false
grid_options:
columns: 24
rows: 1
- type: heading
heading: Battery
heading_style: title
- type: custom:apexcharts-card
update_interval: 10m
apex_config:
chart:
height: 150px
legend:
show: false
xaxis:
labels:
show: true
format: HH
rotate: -45
rotateAlways: true
hideOverlappingLabels: true
style:
fontSize: 10
fontWeight: 10
header:
show: true
show_states: true
colorize_states: true
all_series_config:
type: area
opacity: 0.3
stroke_width: 1
series:
- entity: sensor.batteries_state_of_capacity
type: line
color: "#eab308"
yaxis_id: pct
opacity: 1
stroke_width: 2
- entity: sensor.power_import
color: "#ef4444"
yaxis_id: watt
group_by:
func: avg
duration: 5min
yaxis:
- id: pct
show: true
opposite: false
decimals: 0
max: 100
min: 0
- id: watt
show: true
opposite: true
decimals: 0
min: 0
- type: custom:apexcharts-card
update_interval: 5m
header:
show: true
title: batteries_charged_kwh
graph_span: 48h
span:
start: day
now:
show: true
color: red
label: Now
apex_config:
chart:
height: 180
stroke:
curve: stepline
width: 1
markers:
size: 0
xaxis:
labels:
format: HH
rotate: -45
rotateAlways: true
hideOverlappingLabels: true
style:
fontSize: 10
fontWeight: 500
yaxis:
decimalsInFloat: 3
opposite: true
series:
- name: batteries_charged_kwh
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: line
color: "#22c55e"
float_precision: 3
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end,
batteries_charged_kwh }) => {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, batteries_charged_kwh], [e, batteries_charged_kwh]);
}); return out;
- type: custom:apexcharts-card
update_interval: 1m
experimental:
disable_config_validation: true
grid_options:
columns: full
layout_options:
grid_columns: 3
grid_rows: 1
graph_span: 24h
span:
start: day
now:
show: true
color: red
label: Now
apex_config:
chart:
height: 500
stroke:
width: 2
xaxis:
labels:
format: HH
rotate: -45
rotateAlways: true
hideOverlappingLabels: true
style:
fontSize: 10
fontWeight: 500
yaxis:
decimalsInFloat: 3
series:
- name: avg_house_consumption_kwh
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: area
color: "#f97316"
float_precision: 3
opacity: 0.3
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end,
avg_house_consumption_kwh }) => {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, avg_house_consumption_kwh], [e, avg_house_consumption_kwh]);
}); return out;
- name: avg_house_consumption_1d_kwh
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: line
color: "#3b82f6"
float_precision: 3
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end,
avg_house_consumption_1d_kwh }) => {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, avg_house_consumption_1d_kwh], [e, avg_house_consumption_1d_kwh]);
}); return out;
- name: avg_house_consumption_3d
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: line
color: "#eab308"
float_precision: 3
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end,
avg_house_consumption_3d_kwh }) => {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, avg_house_consumption_3d_kwh], [e, avg_house_consumption_3d_kwh]);
}); return out;
- name: avg_house_consumption_7d_kwh
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: line
color: "#8b5cf6"
float_precision: 3
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end,
avg_house_consumption_7d_kwh }) => {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, avg_house_consumption_7d_kwh], [e, avg_house_consumption_7d_kwh]);
}); return out;
- name: avg_house_consumption_14d_kwh
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: line
color: "#22c55e"
float_precision: 3
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end,
avg_house_consumption_14d_kwh }) => {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, avg_house_consumption_14d_kwh], [e, avg_house_consumption_14d_kwh]);
}); return out;
column_span: 2
- type: grid
cards:
- type: custom:apexcharts-card
update_interval: 5m
header:
show: true
title: estimated_net_consumption_kwh
graph_span: 48h
span:
start: day
now:
show: true
color: red
label: Now
apex_config:
chart:
height: 180
stroke:
curve: stepline
width: 1
markers:
size: 0
xaxis:
labels:
format: HH
rotate: -45
rotateAlways: true
hideOverlappingLabels: true
style:
fontSize: 10
fontWeight: 500
yaxis:
decimalsInFloat: 3
opposite: true
series:
- name: estimated_net_consumption_kwh
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: line
color: "#06b6d4"
float_precision: 3
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end,
estimated_net_consumption_kwh }) => {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, estimated_net_consumption_kwh], [e, estimated_net_consumption_kwh]);
}); return out;
- type: custom:apexcharts-card
update_interval: 5m
header:
show: true
title: estimated_cost
graph_span: 48h
span:
start: day
now:
show: true
color: red
label: Now
apex_config:
chart:
height: 180
stroke:
curve: stepline
width: 1
markers:
size: 0
xaxis:
labels:
format: HH
rotate: -45
rotateAlways: true
hideOverlappingLabels: true
style:
fontSize: 10
fontWeight: 500
yaxis:
decimalsInFloat: 2
opposite: true
series:
- name: estimated_cost_currency
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: line
color: "#22c55e"
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end,
estimated_cost_currency }) => {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, estimated_cost_currency], [e, estimated_cost_currency]);
}); return out;
- type: custom:apexcharts-card
update_interval: 5m
header:
show: true
title: avg_house_consumption_kwh
graph_span: 48h
span:
start: day
now:
show: true
color: red
label: Now
apex_config:
chart:
height: 180
stroke:
curve: stepline
width: 1
markers:
size: 0
xaxis:
labels:
format: HH
rotate: -45
rotateAlways: true
hideOverlappingLabels: true
style:
fontSize: 10
fontWeight: 500
yaxis:
decimalsInFloat: 3
opposite: true
series:
- name: Estimated consumption
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: line
float_precision: 3
color: "#ef4444"
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end,
avg_house_consumption_kwh }) => {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, avg_house_consumption_kwh], [e, avg_house_consumption_kwh]);
}); return out;
- type: custom:apexcharts-card
update_interval: 5m
header:
show: true
title: export_price
graph_span: 48h
span:
start: day
now:
show: true
color: red
label: Now
apex_config:
chart:
height: 180
stroke:
curve: stepline
width: 1
markers:
size: 0
xaxis:
labels:
format: HH
rotate: -45
rotateAlways: true
hideOverlappingLabels: true
style:
fontSize: 10
fontWeight: 500
yaxis:
decimalsInFloat: 3
opposite: true
series:
- name: export_price
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: line
color: "#22c55e"
float_precision: 3
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end, export_price }) =>
{
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, export_price], [e, export_price]);
}); return out;
- type: grid
cards:
- type: custom:apexcharts-card
update_interval: 5m
header:
show: true
title: estimated_battery_capacity_kwh
graph_span: 48h
span:
start: day
now:
show: true
color: red
label: Now
apex_config:
chart:
height: 180
stroke:
curve: stepline
width: 1
markers:
size: 0
xaxis:
labels:
format: HH
rotate: -45
rotateAlways: true
hideOverlappingLabels: true
style:
fontSize: 10
fontWeight: 500
yaxis:
decimalsInFloat: 0
opposite: true
series:
- name: estimated_battery_capacity_kwh
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: line
color: "#06b6d4"
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end,
estimated_battery_capacity_kwh }) => {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, estimated_battery_capacity_kwh], [e, estimated_battery_capacity_kwh]);
}); return out;
- type: custom:apexcharts-card
update_interval: 5m
header:
show: true
title: estimated_battery_soc_pct
graph_span: 48h
span:
start: day
now:
show: true
color: red
label: Now
apex_config:
chart:
height: 180
stroke:
curve: stepline
width: 1
markers:
size: 0
xaxis:
labels:
format: HH
rotate: -45
rotateAlways: true
hideOverlappingLabels: true
style:
fontSize: 10
fontWeight: 500
yaxis:
decimalsInFloat: 0
opposite: true
series:
- name: estimated_battery_soc_pct
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: line
color: "#eab308"
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end,
estimated_battery_soc_pct }) => {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, estimated_battery_soc_pct], [e, estimated_battery_soc_pct]);
}); return out;
- type: custom:apexcharts-card
update_interval: 5m
header:
show: true
title: solcast_pv_estimate_kwh
graph_span: 48h
span:
start: day
now:
show: true
color: red
label: Now
apex_config:
chart:
height: 180
stroke:
curve: stepline
width: 1
markers:
size: 0
xaxis:
labels:
format: HH
rotate: -45
rotateAlways: true
hideOverlappingLabels: true
style:
fontSize: 10
fontWeight: 500
yaxis:
decimalsInFloat: 3
opposite: true
series:
- name: solcast_pv_estimate_kwh
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: line
color: "#f59e0b"
float_precision: 3
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end,
solcast_pv_estimate_kwh }) => {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, solcast_pv_estimate_kwh], [e, solcast_pv_estimate_kwh]);
}); return out;
- type: custom:apexcharts-card
update_interval: 5m
header:
show: true
title: import_price
graph_span: 48h
span:
start: day
now:
show: true
color: red
label: Now
apex_config:
chart:
height: 180
stroke:
curve: stepline
width: 1
markers:
size: 0
xaxis:
labels:
format: HH
rotate: -45
rotateAlways: true
hideOverlappingLabels: true
style:
fontSize: 10
fontWeight: 500
yaxis:
decimalsInFloat: 3
opposite: true
series:
- name: import_price
entity: sensor.hsem_workingmode_sensor
attribute: hourly_recommendations
type: line
color: "#ef4444"
float_precision: 3
show:
legend_value: false
data_generator: >
const rows =
(Array.isArray(entity.attributes.hourly_recommendations) ?
entity.attributes.hourly_recommendations : [])
.slice()
.sort((a,b) => new Date(a.start) - new Date(b.start));
const out = []; rows.forEach(({ start, end, import_price }) =>
{
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, import_price], [e, import_price]);
}); return out;
type: sections
max_columns: 2
cards: []The dashboard uses a three-section layout within one view:
| Section | Column span | Cards | Description |
|---|---|---|---|
| Main | 2 columns | 7 | Recommendation timeline, battery status, charged kWh, consumption breakdown |
| Middle | 1 column | 4 | Net consumption, estimated cost, consumption, export price |
| Right | 1 column | 4 | Battery capacity, simulated SoC, PV forecast, import price |
The top stepline area chart renders 48 hours of planner recommendations. Each recommendation type is a color-coded horizontal band.
| Series | Color | Recommendation string |
|---|---|---|
| Batteries Charge From Grid |
#ef4444 (red) |
batteries_charge_grid |
| Batteries Charge From Solar |
#22c55e (green) |
batteries_charge_solar |
| Batteries Discharge Mode |
#f97316 (orange) |
batteries_discharge_mode |
| Batteries Wait Mode |
#8b5cf6 (purple) |
batteries_wait_mode |
| EV Smart Charging |
#06b6d4 (cyan) |
ev_smart_charging |
| Time Passed |
#64748b (slate) |
time_passed |
| Force Batteries Discharge |
#ec4899 (pink) |
force_batteries_discharge |
Dual-axis chart combining live battery SoC (sensor.batteries_state_of_capacity)
with 5-minute averaged grid import power (sensor.power_import).
Customize: Replace
sensor.batteries_state_of_capacityandsensor.power_importwith your own entity IDs. These come from your inverter/sensor integrations.
The remaining 11 charts each plot a single field from hourly_recommendations
as a stepline across the 48-hour horizon:
| Chart title | Field | Unit |
|---|---|---|
batteries_charged_kwh |
batteries_charged_kwh |
kWh |
| Consumption breakdown (5 series) |
avg_house_consumption_kwh, ..._1d/3d/7d/14d_kwh
|
kWh |
estimated_net_consumption_kwh |
estimated_net_consumption_kwh |
kWh |
estimated_cost |
estimated_cost_currency |
Currency |
avg_house_consumption_kwh |
avg_house_consumption_kwh |
kWh |
export_price |
export_price |
Currency/kWh |
estimated_battery_capacity_kwh |
estimated_battery_capacity_kwh |
kWh |
estimated_battery_soc_pct |
estimated_battery_soc_pct |
% |
solcast_pv_estimate_kwh |
solcast_pv_estimate_kwh |
kWh |
import_price |
import_price |
Currency/kWh |
All HSEM charts use the same JavaScript pattern to extract data from
hourly_recommendations:
- Guard against missing/non-array data with
Array.isArray(). - Sort slots chronologically by
starttimestamp. - Map each slot to
[timestamp_ms, value]pairs for ApexCharts. - For stepline charts, push both
[start, value]and[end, value]to create horizontal segments with sharp vertical transitions.
Example for a single-field stepline:
const rows = (Array.isArray(entity.attributes.hourly_recommendations)
? entity.attributes.hourly_recommendations : [])
.slice()
.sort((a, b) => new Date(a.start) - new Date(b.start));
const out = [];
rows.forEach(({ start, end, field_name }) => {
const s = new Date(start).getTime();
const e = new Date(end).getTime();
out.push([s, field_name], [e, field_name]);
});
return out;| Problem | Solution |
|---|---|
| Charts show no data | Verify sensor.hsem_workingmode_sensor exists and hourly_recommendations attribute is populated |
All bands are gray (time_passed) |
The planner hasn't run a new cycle yet — wait for the next update interval |
| Charts are offset by one hour | Check your Home Assistant timezone matches your local time |
| Battery SoC chart is empty | Replace sensor.batteries_state_of_capacity with your actual battery SoC entity |
| Grid import chart is empty | Replace sensor.power_import with your actual grid import power entity |
- Home — User-facing overview: features, FAQ, working modes, battery schedules, excess export, consumption sensors
- Battery Charging Economics — How to calculate the minimum charging price for a battery schedule
- Architecture Overview — System context, layered architecture, module map, planning pipeline
- Planner Specification — Normative — all planner invariants, rules, and constraints
- Planner Technical Guide — How the planner works with worked examples
- Cost Function Math — Complete mathematical formulation of the 8-term cost function
- Energy Accounting — Physical energy flow model, SoC simulation, efficiency math
- Candidate Generation — How candidates are generated, assumptions, partial-SoC
- MILP Optimization — Full LP formulation, variable layout, constraints, and solver pipeline
- Consumption Prediction — Weighted-average model, IQR outlier detection, spike suppression
- Safety Modes — Degraded mode, read-only gate, write-verify applier, runtime resolver
- Price Scaling — EDS price scaling, eds_share conversion factor
- Services Reference — All 4 HSEM services with examples
- Sensors Reference — Complete entity reference: all sensor, select, switch, number, and time entities
- Dashboard Setup — Step-by-step ApexCharts dashboard with full YAML, layout reference, and troubleshooting
- Config Flow Reference — Every config/options flow step and field
- EV Charge Plan Setup — EV planned load configuration guide
- EV Surplus Charging Automation — Wire your physical EV charger (go-e, Easee, Zaptec) to follow HSEM surplus recommendations
- EV Optimal Charging Template — Legacy Home Assistant template sensor for cost-optimal EV charging
- Forecast Accuracy Tracking — Forecast vs actual tracking system
- Huawei Entities — Canonical HA entity ID reference
- Troubleshooting Guide — Diagnose and fix common problems: missing data, wrong prices, write failures, battery behaviour
- Quality Checks — Static quality tools and CI configuration