After years of just paying the electricity bill, I built a system where Home Assistant does the shopping: it buys energy when it is cheap, stores it, and spends it when the grid is expensive. Sharing the architecture here because most of the pieces are standard HA — the value is in how they are wired together.
Measured result: a typical month went from ~€180 to €113. Averaged over the year we save €50–75/month; battery arbitrage alone contributes €0.60–1.00/day. (I live in Germany — hourly dynamic tariffs are widely available across Europe. The principles apply anywhere day-ahead prices exist in machine-readable form.)
The four layers
- Hardware (on site): solar array, home battery (Anker Solix), smart meter, and the household loads. The battery is zero-export: surplus goes to storage, nothing is gifted to the grid — self-consumed kWh are worth 3–4× the feed-in rate here.
- Data: hourly exchange prices (day-ahead), weather forecast, a learned consumption profile per weekday, and live system status (SOC, solar W, grid W).
- Brain: Home Assistant + a few small helper scripts in Docker on a small VPS (€4.49/month — works for me only because the battery is cloud-connected anyway; purely local hardware belongs on a local machine).
- Action: steer the battery mode, shift loads (washing machine gets slot suggestions via Telegram, family taps one), buy from the grid only in planned cheap hours.
The 7-state machine (every 5 minutes, one decision)
| State | When |
|---|---|
| EMERGENCY | battery critically empty → charge now |
| FLOOR | below reserve (25%) → hold, grid covers house |
| CHARGE | current hour is on the cheap-hours plan |
| FULL | charge target (~95%) reached |
| DISCHARGE | expensive peak hour (>~38 ct) |
| HOLD | peak coming soon → don't waste the charge |
| IDLE/SLEEP | nothing to do → especially at night |
Hysteresis everywhere (in at 25% / out at 55%), otherwise it flaps. "Sleep" only discharges at night if price, remaining charge AND a cheap recharge window all line up.
The price pipeline
Fetch prices hourly (one JSON file as single source of truth) → compute tomorrow's plan in the afternoon (cheapest hours covering expected net demand = learned consumption minus expected solar) → replan every 30 minutes with a 48h view → the state machine executes on the 5-minute beat.
What cost me the most time (honest lessons)
- Cloud latency & hangs: the battery ignores a mode change now and then, sensors freeze with identical timestamps. Fix: a properly rated smart plug in front of the battery — 90s off/on forces a real reboot. My self-healing triggers it automatically when needed.
- Freshness checks before every action. A frozen sensor plus an eager automation is how you buy expensive power at 7 pm.
- Quality gates over features: every 15 min a checklist (prices loaded? plan computed? charged in the cheap window? SOC ready before peak?) rolls up into one health percentage on the dashboard. Most "failures" were bugs in my own checks — verify first, then repair.
- Only use officially supported battery modes. Undocumented ones hung the system for hours.
Happy to answer questions about any layer — especially the state machine and the charging-plan logic. I also wrote the whole build up as a beginner-friendly field report (ebook) since friends kept asking; happy to point to it if that's within the forum rules.