How much money did your home battery actually make today?
A Home Assistant integration that multiplies every kWh your battery charges and discharges by the price at that exact moment, and shows the result as a live profit ticker.
Setup in 5 steps · Troubleshooting · How profit is calculated
Standard energy dashboards show kWh. With dynamic prices (EPEX, Nordpool, Tibber, Frank Energie, Zonneplan …) the question that matters is money: did charging at 3 AM and discharging at 7 PM pay off? Battery ROI answers that per day, per month, and since the day you installed it.
- 🧮 Real profit, not an estimate: every 30 seconds, energy × the price that applied at that moment
- 🖱️ No YAML: pick your own sensors in a setup screen, change them later without losing your totals
- 🔌 Works with any battery that has a power sensor in Home Assistant (EcoFlow, Zendure, Marstek, Anker SOLIX, Victron, Huawei, SolarEdge, Sessy, Tesla Powerwall and so on), any grid meter, any dynamic price sensor, W/kW and EUR/kWh, ct/kWh or EUR/MWh
- ☀️ Solar aware: with a feed-in price and a grid meter, it knows whether a kWh replaced buying or selling
- 📈 Dashboard card included: today, this month, yesterday, last month, live €/h, monthly pace, payback and efficiency
- 🎨 Matches your theme: the card uses only Home Assistant theme variables, light and dark
- 🇳🇱 English and Dutch: setup screen and card follow your Home Assistant language
- ✏️ Visual card editor: no YAML needed on the dashboard either
- 📴 Works offline: the card and its UI library ship inside the integration, no CDN
| What | Why |
|---|---|
| Home Assistant 2024.11 or newer | Uses the current config and options flow APIs |
| HACS | To install and update (manual install also works) |
| A battery power sensor | Power into and out of the battery, as two sensors or one signed sensor |
| A price sensor, or your fixed tariff | A dynamic tariff sensor works best. On a fixed or peak/off-peak contract you type in the prices instead |
| Optional: feed-in price, grid power, solar production, state of charge | Better accuracy and more on the card |
No extra Python packages are installed. The card's only library, Lit, is bundled.
Five steps, about 10 minutes. Every step says exactly where to click in Home Assistant.
Before you start: you need HACS installed, and your battery and price sensors must already exist in Home Assistant (for example from your battery's own integration, a smart meter integration like DSMR or HomeWizard, and a price integration like Nordpool, ENTSO-e or Tibber).
Write down the names of the sensors you'll pick in step 4.
- Go to Settings → Devices & services → Entities (tab at the top).
- Type in the search box to find each one:
| You need | Search for | What it looks like |
|---|---|---|
| Battery charge power (required) | battery + power, or your battery brand |
Watts that go into the battery. Some batteries have one sensor that is positive while charging and negative while discharging; that's fine |
| Battery discharge power | battery + power, or your battery brand |
Watts that come out of the battery. Skip if the sensor above is signed |
| Electricity price (required, or a peak price) | price, tibber, nordpool, epex |
Your current price per kWh |
| Grid power (recommended) | grid, p1, net, power_consumption |
Your smart meter or energy monitor: watts from (+) or to (−) the grid |
| Feed-in price (recommended) | price |
What you get per exported kWh. With a dynamic contract this is often the same price sensor |
| Solar production | solar, pv, inverter |
Watts your panels produce right now |
| Battery level | battery + level, soc, state_of_charge |
Battery % |
Examples by brand. Entity names differ per integration and per firmware, so treat these as hints, not exact names:
| Kind | Brand or integration | Sensor to look for |
|---|---|---|
| Battery | EcoFlow, Zendure, Anker SOLIX | separate input / output power sensors → fill both charge and discharge |
| Battery | Marstek, Victron, Huawei, SolarEdge, Sessy, Tesla Powerwall | one battery power sensor, positive one way and negative the other → fill charge only and set Positive power means |
| Grid | DSMR / Slimme meter, HomeWizard P1, P1 Monitor, Shelly EM, Tibber Pulse | power consumption minus power production, or one signed active power sensor |
| Solar | Enphase, SolarEdge, SMA, Growatt, Fronius, APsystems | current power or AC power of the inverter |
| Price | Nordpool, ENTSO-e, EnergyZero, Tibber, Frank Energie, Zonneplan, Octopus | current price |
Your grid meter (or battery) only has two separate sensors, one per direction, and you want one signed sensor? Make one with a template helper (Settings → Devices & services → Helpers → Create helper → Template → Template sensor, state {{ states('sensor.power_consumption')|float(0) - states('sensor.power_production')|float(0) }}, unit W, device class Power).
Not sure which way a signed sensor goes? Open it (click the name) while the battery is charging. Positive number = "positive means charging".
Click the button above and choose your Home Assistant, or do it by hand:
- Open HACS in the sidebar.
- Click ⋮ (top right) → Custom repositories.
- Paste
https://github.com/PeterSlijkhuis/Battery-ROI, pick type Integration, click Add. - Search HACS for Battery ROI, open it, click Download (bottom right).
- HACS asks which version: keep the newest (for example
v0.2.1) and click Download.
No HACS? Install by hand
Copy the folder custom_components/battery_roi from this repository into /config/custom_components/ on your Home Assistant (for example with the File editor or Samba add-on), so you end up with /config/custom_components/battery_roi/manifest.json.
- Go to Settings → System.
- Click the power icon (top right) → Restart Home Assistant → Restart.
- Wait until Home Assistant is back (about a minute), then refresh your browser (Ctrl+F5). In the phone app: close the app completely and open it again. Without this the card won't show up in step 5.
Click the button above, or:
- Go to Settings → Devices & services.
- Click + Add integration (bottom right).
- Search for Battery ROI and click it.
- Fill in the form with the sensors from step 1. Only the two fields marked required are needed; open the folded sections for more accuracy.
- Click Submit.
All fields explained:
| Section | Field | Required | What to enter |
|---|---|---|---|
| Battery | Charge power | ✅ | Power into the battery, or one signed battery power sensor |
| Discharge power | Power out of the battery. Leave empty when the field above is signed | ||
| Positive power means | Only for a signed sensor: does positive mean charging or discharging? | ||
| State of charge | Battery %, shown on the card | ||
| Prices | Electricity price sensor (dynamic) | ✅ or a peak price | EPEX, Nordpool, ENTSO-e, Tibber, Frank Energie, Zonneplan… |
| Extra cost per kWh, excl. VAT | Only for raw market prices (EPEX, Nordpool): energy tax + supplier fee in EUR/kWh | ||
| VAT % | Only for raw market prices: 21 in the Netherlands | ||
| Peak price | ✅ or a sensor | All-in price per kWh. On a single-rate contract enter your price here and leave off-peak empty. With a sensor too, it fills in whenever the sensor is unavailable | |
| Off-peak price | Peak/off-peak contracts only: the all-in off-peak price | ||
| Off-peak from / until | When the off-peak price applies (default 23:00 to 07:00) | ||
| Off-peak all weekend | On by default: Saturday and Sunday are off-peak all day, as on most peak/off-peak contracts. Switch off if yours isn't | ||
| Feed-in price sensor | What you get per exported kWh, from a sensor | ||
| Fixed feed-in price | Same, as a fixed amount (0 if you get nothing). Use one of the two | ||
| Net metering until | The day net metering ends for you. Before it the feed-in price is ignored, after it it counts. Optional; leave empty if you have no net metering | ||
| Grid and solar | Grid power | Smart meter or energy monitor, + import / − export | |
| Solar production | Shown on the card | ||
| Battery costs | Standby power | Watts the battery uses itself that its power sensors miss | |
| Wear cost per kWh discharged | Purchase price ÷ (capacity in kWh × rated cycles) | ||
| Purchase cost | What the battery cost you; adds a payback sensor and an expected payback date | ||
| Battery installed on | Fills in the totals from Home Assistant's history from this day on, see Start from your install date |
Tibber, Frank Energie, Zonneplan sensors already include tax and VAT: leave Extra cost and VAT empty.
- Open the dashboard where you want the card.
- Click the pencil (top right) to edit. On the default Overview dashboard Home Assistant first asks you to Take control; confirm it.
- Click + Add card (bottom right), type Battery ROI in the search box, and click the card.
- The card already points at the right sensors, so there is nothing to fill in. Click Save, then Done (top right).
Prefer YAML? Add a Manual card with:
type: custom:battery-roi-cardThe numbers start at €0 from the moment you finish step 4, unless you filled in Battery installed on. Read the next section before you judge them.
Battery ROI books money when it happens. Charging costs money right away; the payoff only arrives when the battery discharges later at a higher price. So a normal first day looks like this:
| When | What you see | Why |
|---|---|---|
| Battery charging (night, cheap hours, or solar) | Profit slightly negative, red arrow, Spending €0.03/h | You paid for energy that is now sitting in the battery |
| Battery full, waiting | Profit stays put, Idle | Nothing flows, nothing is booked (except standby power, if you entered it) |
| Battery discharging (evening peak) | Profit climbs, turns positive, green arrow, Earning | Every kWh out replaces a kWh you would have bought at the high price |
| End of the day | Today is the day's real result; tomorrow it moves to Yesterday |
Some numbers stay Unknown at first, on purpose:
| Sensor | Shows up when | Why wait |
|---|---|---|
| Efficiency | 1 kWh has been charged | Smaller amounts give meaningless percentages |
| Payback | You entered a purchase cost, 7 days have passed and profit is positive | One day says nothing about a year |
| Pace | From the 2nd of the month | A projection from a few hours is noise |
Judge the battery on a full day, and better on a full week. A single charge without a discharge will always be red.
| Contract | Where the money comes from | What the card usually shows |
|---|---|---|
| Dynamic (hourly prices) | Charging in cheap hours, discharging in dear ones, plus stored solar | Clearly positive on most days |
| Peak/off-peak | The gap between the two prices, often only a few cents, plus stored solar | Small; can be negative when the gap is smaller than the charging losses |
| Single rate | Only stored solar that you would otherwise have exported for less | Only charging losses, unless you enter a feed-in price and grid power |
A small or negative number on a fixed contract is not a bug: it is what the battery earns on price differences alone. The solar you keep is where the money is, so on a fixed contract a feed-in price and grid power are not optional.
| Part of the card | Meaning |
|---|---|
| Today / This month tiles | Profit so far, with Yesterday / Last month underneath for comparison |
| Lifetime tile | Everything the battery has earned since you set up Battery ROI (or last pressed Reset totals), with the start date underneath |
| Arrow and color | Green arrow up = earned money, red arrow down = cost money, grey = (about) zero |
| Earning / Spending / Idle €…/h | What the battery earns or costs right now, per hour |
| Price, ☀ kW, 🔋 % | Current price per kWh, solar production and battery level (only if you set those sensors) |
| Pace | This month's profit projected to a full month |
| Paid back by | Month the battery has paid for itself at the rate so far, with the years left |
| Efficiency | kWh out ÷ kWh in; 80 to 90% is normal for home batteries |
Tap a tile or the ticker to see its history graph.
| I want to… | Where |
|---|---|
| Change which sensors are used | Settings → Devices & services → Battery ROI → Configure (your totals are kept) |
| Fill in history from the day you got the battery | Configure → Battery costs → Battery installed on, see below |
| Start the totals from zero | Settings → Devices & services → Battery ROI → device → Reset totals → Press |
| See all Battery ROI numbers | Settings → Devices & services → Battery ROI → device |
| See a number's history | Tap it on the card |
| Change what the card shows | Edit the dashboard → click the card → Edit |
| Update to a new version | Settings lists an update for Battery ROI at the top → click it → Install, then restart (step 3). The card then shows an orange Battery ROI was updated bar: tap it once to load the new card |
Had the battery before you installed Battery ROI? Fill in Battery installed on (in setup, or later via Configure → Battery costs). Battery ROI then replays Home Assistant's own history from that day and fills in Today, This month, Lifetime, energy and payback. A notification tells you when it's done and from which day it found data.
How far back it can go depends on what Home Assistant kept:
| Period | What Battery ROI uses | Accuracy |
|---|---|---|
| Last ~10 days (the recorder's default) | Every recorded sensor change | Same as live tracking |
| Older | Hourly averages from long-term statistics | Close, but prices and power inside an hour are averaged |
| Before your sensors existed in Home Assistant | Nothing | Not counted |
- Long-term statistics only exist for sensors with a state class. Most battery power sensors have one; some price sensors don't, and hours without a price are skipped.
- Hours when Home Assistant was off are skipped rather than guessed.
- The replay replaces the current totals. It runs once per date; change the date to run it again.
Numbers look wrong?
- Profit is negative on day one → normal while the battery is charging, see Your first day.
- Profit goes down while the battery is discharging → the sign is flipped. Change Positive power means (step 4 via Configure), or check your grid power sensor, then Reset totals.
- Profit is far too small or large → check the price sensor's unit (EUR/kWh, ct/kWh and EUR/MWh are handled) and that Extra cost is only filled in for raw market prices.
- Card says Battery ROI isn't set up → finish step 4, then refresh the browser.
- Card doesn't appear in the card list, or "Custom element doesn't exist: battery-roi-card" → the page was loaded before Battery ROI was installed, and Home Assistant never adds a new card to a page that is already open. In a browser press Ctrl+F5. In the phone app close the app completely (swipe it away) and open it again. Still missing? Settings → Companion app → Troubleshooting → Reset frontend cache, then close and reopen the app once more.
- "Configuration error" after Home Assistant restarted → fixed in 0.5.1. Battery ROI now also puts a copy of the card in
/config/www/battery_roiand adds it as a dashboard resource, because that folder is available the moment Home Assistant starts, before integrations are loaded. If yourwwwfolder didn't exist yet, this starts working after one more restart. If you manage dashboard resources in YAML, add/local/battery_roi/battery-roi-card.jsas amoduleresource yourself. Removing the integration removes the copy and the resource again.
The visual editor covers everything. For YAML, all keys are optional:
| Option | Default |
|---|---|
title |
Battery ROI |
daily |
sensor.battery_roi_profit_today |
monthly |
sensor.battery_roi_profit_this_month |
total |
sensor.battery_roi_profit_total (the Lifetime tile) |
rate |
sensor.battery_roi_rate |
payback |
sensor.battery_roi_payback |
efficiency |
sensor.battery_roi_efficiency |
price, soc |
read from the rate sensor |
currency |
your Home Assistant currency |
The pace line projects this month's profit to a full month, using the exact time elapsed; it stays hidden on the 1st.
| Sensor | Meaning |
|---|---|
sensor.battery_roi_rate |
€/h right now. Positive = earning. Attributes: price, feed-in price, grid kW, solar kW, state of charge |
sensor.battery_roi_profit_today |
Profit today, last_period = yesterday |
sensor.battery_roi_profit_this_month |
Profit this month, last_period = last month |
sensor.battery_roi_profit_total |
Profit since setup, tracking_since = start date |
sensor.battery_roi_energy_charged |
kWh into the battery |
sensor.battery_roi_energy_discharged |
kWh out of the battery |
sensor.battery_roi_efficiency |
Discharged ÷ charged, shown after 1 kWh |
sensor.battery_roi_payback |
Years until the battery has paid for itself at the average rate so far. Attribute paid_back_on holds the expected date. Only exists if you entered a purchase cost; shown after 7 days with positive profit |
button.battery_roi_reset_totals |
Start all totals from zero |
Totals survive restarts. Gaps longer than 15 minutes (Home Assistant down, sensor offline) are skipped rather than guessed.
Profit is your grid bill without the battery minus your grid bill with it.
flowchart LR
B[Battery power<br/>charge / discharge] --> S((every 30 s))
P[Price now<br/>+ extra cost] --> S
F[Feed-in price] -.-> S
G[Grid power] -.-> S
S --> R[€/h right now]
R --> T[Today · Month · Total]
- Price sensor only.
profit per hour = (discharge kW − charge kW) × price. The same price is used in both directions, which is right under net metering (salderingsregeling). - Plus feed-in price and grid power. Battery ROI can tell solar charging from grid charging. Each kWh is valued at the price it actually displaced:
| Battery is… | While the house is… | Valued at |
|---|---|---|
| discharging | importing from the grid | import price (avoided buying) |
| discharging | exporting | feed-in price (extra sold) |
| charging | exporting solar surplus | feed-in price (gave up selling) |
| charging | importing | import price (bought) |
Is solar charging free? Only if you'd get nothing for exporting it. Storing a solar kWh means not selling it, so it costs whatever you'd have been paid:
- Net metering (saldering): an exported kWh cancels a bought one, so a stored solar kWh is worth the full price. Leave the feed-in fields empty, or fill them in now together with the Net metering until date so the switch happens by itself.
- No net metering: set a feed-in price (sensor or fixed) plus grid power. Solar charging then costs that feed-in price, often a few cents, and grid charging costs the full import price.
- You get nothing for export: set the fixed feed-in price to 0 and solar charging is free.
Included: round-trip losses, because you charge more kWh than you get back. Optional: the battery's own standby draw, and wear per kWh discharged (counted once, not on both charge and discharge).
Tip for the Netherlands: net metering ends on 1 January 2027. From then on a stored solar kWh is worth the feed-in price, not the import price, Enter your feed-in price, grid meter and Net metering until = 1 January 2027 once, and the numbers switch over on their own.
Tip for raw EPEX prices: they exclude energy tax, supplier fee and VAT. Fill in Extra cost per kWh and VAT: the import price becomes (market price + extra cost) × (1 + VAT).
Why do I see "icon not available" instead of the Battery ROI icon?
The icon ships inside the integration, which Home Assistant supports from version 2026.3. On older versions the icon stays empty; everything else works.
Why not just use the battery's state of charge?
State of charge moves in whole percents and hides charging losses, so a 5 kWh battery can hide 50 Wh per step. Power sensors give the real flow.
The numbers start at zero. Can I import history?
Yes, see Start from your install date.
Can I use my own card?
Yes. All numbers are ordinary sensors, so Mushroom, Tile or any other card works.
pip install -r requirements_test.txt
pytestCI runs Home Assistant's hassfest validation and the test suite on every pull request. The screenshots above come from a real Home Assistant 2026.2 with demo sensors.
Issues and pull requests are welcome at github.com/PeterSlijkhuis/Battery-ROI.