A Lovelace card for Home Assistant that displays the current PV surplus and lists loads by priority. Answers one question at a glance: is there enough surplus for this device?
It is the display half of the Energy Manager Integration: the integration calculates the surplus, keeps the loads and switches them automatically by priority — the card finds it on its own and displays it. Loads are maintained exclusively in the integration.
The card is not meant to be used without the integration. It still runs — it then shows the surplus from sensors you configure yourself — but without loads there is little point to it. Configurations from earlier versions keep working; see Legacy configurations.
Card and integration are versioned independently — the numbers are deliberately not kept in lockstep, because the two sides share an attribute contract, not a release cadence. Minimum integration versions per feature, and the reasoning behind that choice, are in the interface contract.
Shown: 2.4 kW export plus 600 W battery charging make 3.0 kW of available surplus. The wallbox needs at least 4.2 kW and stays grey, the immersion heater is running and covered, the washing machine would be ready — but is still under its minimum off time.
Note: the image was produced in a test environment (tools/preview.html). The card itself is
the shipped bundle; the surrounding Home Assistant elements (card frame, toggles, state icons) are
recreated for the screenshot and look slightly different in a real installation.
- HACS → Dashboard → ⋮ menu → Custom repositories
- Add the repository URL, category Dashboard
- Install "Energy Manager Card"
- Clear the browser cache (Ctrl+F5)
- Copy
energy-manager-card.jsfrom the latest release to/config/www/ - Settings → Dashboards → ⋮ → Resources → Add:
URL
/local/energy-manager-card.js?v=0.5.2, type JavaScript module
Everything that concerns the card itself can be set in the graphical editor. Loads are not part of that — they come from the integration.
With the Energy Manager integration installed, this is enough:
type: custom:energy-manager-cardMeter sensors, battery, smoothing and the list of loads all come from the integration; the editor hides those fields and points there instead. Loads are maintained under Settings → Devices & Services → Energy Manager → Add load. That way there is exactly one place for every setting — keeping the same list in two places would inevitably let them drift apart.
The card header additionally gets the main switch of the automation. With it off, nothing is switched.
If you have the integration installed but do not want to use it for one particular card, set
use_integration: false in YAML. Deliberately without a toggle in the editor: this is a fallback,
not a second mode of operation.
See docs/examples.yaml and the option table in
docs/configuration.md.
With the integration both work without any preparation: it creates a number.…_priority and a
switch.…_automation per load, and the card operates them.
- An icon in the card header enables reorder mode. Only then do handles and arrow buttons appear — permanently visible, they would move priorities by accident while scrolling on a tablet. When reordering, the card writes the ranks as a gapless 1…n.
- The toggle on the right switches the automation, not the device. A coloured dot on the icon
shows whether the device is running; you can still switch it from the detail dialog (click the
name or icon). Set
switch_action: deviceif you prefer otherwise. - The main switch in the header stops the entire automation.
The reason entities are needed for this is technical: a Lovelace card cannot write its own configuration at runtime. An order changed from the dashboard would otherwise be gone after a reload.
They are the most common stumbling block because they sound alike. They act at different points and do not replace one another:
| Field | Acts | Protects against |
|---|---|---|
turn_on_delay |
before switching on | starting during a brief drop in production |
turn_off_delay |
before switching off | stopping because of a passing cloud |
min_runtime |
after switching on | runtimes that are too short (wash cycle, heat pump) |
min_off_time |
after switching off | restarting too early (compressor pressure equalising) |
A compressor typically needs min_off_time: 600, a wallbox rather turn_on_delay: 120 together
with min_runtime: 900.
These fields are set on the load in the integration, and they are enforced there. The card shows the resulting countdown from the integration's exact timestamp.
Optional. Charging power always counts as divertible power towards the surplus: what is currently flowing into the battery could go to a load instead.
How discharging is treated is controlled by battery_mode:
| Mode | Formula | Meaning |
|---|---|---|
charge_only (default) |
−grid + max(battery, 0) |
"How much can I switch on without drawing from the grid?" The battery may contribute. |
full |
−grid + battery |
"How much does the PV deliver beyond the house load?" Stored energy counts as unavailable. |
The difference is substantial. Example: 463 W PV, 842 W house consumption, battery discharging at 386 W, 7 W coming from the grid.
charge_only→ 7 W deficit — the house runs practically self-sufficient, the battery covers the gapfull→ 393 W deficit — that much the PV falls short of the house load
charge_only is the default because full reports a deficit while the battery discharges that
plainly contradicts the meter reading. Choose full if you want to reserve the battery for the
evening.
Further options:
battery_min_soc— below this, charging takes precedence and no surplus is reportedbattery_reserve_w— power always reserved for the battery
Below the large figure the card also shows the actual meter readings ("Grid 7 W import · Battery 386 W discharging"), so that the calculated surplus and the real grid flow are not confused.
If the battery sensor fails, the card carries on without it but marks the value as uncertain rather than presenting a wrong result as reliable.
smoothing_window (default 60 s) averages the surplus time-weighted: every reading applies
until the next one arrives. A sensor at 3000 W for 55 s and 0 W for 5 s yields 2750 W — not 1500 W
as a plain average would. 0 disables smoothing.
The surplus is distributed as a budget in priority order. With 2000 W of surplus and five devices of 1500 W each, exactly one turns green — not all five.
| Indicator | Meaning |
|---|---|
| green, solid | running, covered by the surplus |
| orange, solid | running, but drawing from the grid |
| green, faded | off, the surplus would be sufficient |
| orange, faded | off, surplus marginal (from 80 % of the demand) |
| grey | off, surplus insufficient |
"A sensor does not measure power" — a kWh meter is configured instead of a W sensor. An energy meter reports an amount, not an instantaneous value. In that case the card deliberately shows no value rather than silently assuming 0 W.
The surplus has the wrong sign — toggle invert_grid. Verify in sunshine: while exporting, the
figure must be positive.
The card does not load after an update — HA caches the resource aggressively. Clear the cache;
with a manual installation, increment ?v=<version> in the resource URL.
Before the integration existed, the card calculated the surplus itself and kept its own list of loads. Those configurations keep working, but nothing here is maintained in the editor any more — two places for the same list was precisely the problem the integration solves.
Meter source. Either a single bidirectional grid sensor:
type: custom:energy-manager-card
grid_entity: sensor.grid_power # >0 import, <0 exportIf your sensor uses the opposite sign (positive while exporting), set invert_grid: true.
Or separate sensors for production and consumption, both always positive:
meter_mode: split
production_entity: sensor.pv_production
consumption_entity: sensor.house_consumptionBoth yield the same surplus — the formula is verified against both paths.
Loads and their priority. Priority and automation participation each need one helper per load. Those helpers were the very reason the integration was built: two real HA helpers per load, accumulating in the instance.
devices:
- switch_entity: switch.wallbox
power_entity: sensor.wallbox_power
priority_entity: input_number.prio_wallbox # or a number entity
auto_entity: input_boolean.auto_wallbox # or a switch
max_power: 11000Either all loads have a priority helper or none do. A mix produces an order derived partly from helper values and partly from list positions, which is hard to predict.
The four timing fields may be set here too, but nobody enforces them without the integration — the
card only estimates the countdown from last_changed of the switch entity. That is a hint, not a
lock.
npm install
npm run check # format + lint + typecheck + test
npm run build # -> dist/energy-manager-card.js (single file)
npm run dev # watch build
npm run serve # dev server on :4000, usable as an HA resourceThe calculation core (src/lib/) is covered by Vitest — units, signs, battery correction, time
weighting, budget cascade and lockout periods. That is where the errors sit that are hard to
reproduce in a running installation.
tools/preview.html produces the screenshot above from the built bundle.
MIT
