Home Assistant integration that automates electricity buying and selling for homes with solar + battery storage. It runs a SoC-bucketed dynamic-programming scheduler over Nordpool spot prices, your tariff, the solar forecast, a learned household baseload, battery state, and degradation cost — then drives the inverter to the most profitable storage mode each slot.
Status: alpha (v0.1.0). Only the Solis platform (
solis_modbus) is verified on real hardware. The Huawei / SolaX / Sungrow / GoodWe / Deye drivers are implemented and selectable but untested against hardware — verify on first connect. Fronius / SMA / Kostal are telemetry-only (no write). Run with the Automation switch off first to observe before trusting it.
Everything flows through other HA integrations — sunSale calls no external API directly.
- Home Assistant 2024.x+ (Python 3.12+)
- A price source — Nordpool (default), or ENTSO-e / Octopus Agile / Amber, selectable in setup — or none when both your buy and sell prices are fixed. Currency follows the source (EUR/SEK/NOK/DKK/GBP/AUD/…); the optimisation math is currency-neutral.
- An inverter/battery integration — for Solis: Pho3niX90/solis_modbus
- (optional) a solar forecast integration (Forecast.Solar, Solcast, Open-Meteo, …)
HACS (custom repository): HACS → Integrations → ⋮ → Custom repositories → add
https://github.com/LaurisK/sunSale as category Integration → Download → restart HA.
Manual: copy custom_components/sun_sale/ into config/custom_components/, restart HA.
Then Settings → Devices & Services → Add Integration → sunSale.
Setup opens on one menu listing the sections — Electricity prices, Inverter and battery
and Solar forecast (once an inverter is set up). Open a section to add it to the installation;
each section is a menu of pages (price source and sensor, buy price, sell price, price level,
price-forecast weather; platform and power ratings, battery, entity mapping, dedicated BMS, power
sensors, energy counters). Every page is saved with its Confirm button, whose last field is a
dropdown offering ✓ save and go back or ✕ discard and go back. ✓ Finish configuration
saves once every section you added is complete. For Solis, the solis_modbus config entry is
auto-detected and all entity IDs are resolved from the HA registry — manual mapping is only a
fallback. Configure on the integration card opens the same menus later. Effective prices used
by the scheduler:
buy = (energy + markup + grid_fee) × (1 + VAT)
sell = (energy − deduction − grid_fee) × (1 − tax)
Buy and sell are set up independently. energy is the market price from the price source (a market-based sell price uses a separate live export feed where the source has one) or a fixed price you enter. The grid fee has 1, 2 or 4 tariffs: with one it applies at all times; with 2 or 4 you enter a fee per tariff and, per day type, which tariff is active from what time (up to six "from HH:MM → tariff" rows, 24 h, wrapping past midnight). Workdays always have a schedule; weekends and public holidays (the national calendar of Home Assistant's country) can get their own, and summer and winter can have separate schedules with configurable start dates.
A trade is taken only when sell × efficiency − buy − 2 × degradation > 0, where degradation =
purchase_price / (rated_cycle_life × estimated_capacity × 2). Capacity is learned over days
from the inverter's energy counters.
Created under one sunSale device. Highlights (full set in docs/):
- Actions/prices:
sensor.sunsale_current_action,..._next_action[_time],..._current_buy_price/..._current_sell_price,..._expected_profit_today,..._schedule. - Battery/diagnostics:
..._estimated_battery_capacity,..._degradation_cost,..._current_baseload,..._battery_runtime,..._monthly_bill,..._observed_inverter_mode(commanded vs. observed + per-register verify state). - Energy counters:
..._today_{generation,imported,exported}and the baked..._yesterday_*. - Controls:
select.sunsale_mode_override(operator intent;sunSalesentinel hands back to the scheduler),switch.sunsale_automation(master kill-switch), theswitch.sunsale_allow_*/..._use_standbypolicy switches, and thenumber.sunsale_*planner knobs. - Services:
sun_sale.force_recalculate,sun_sale.force_verify_inverter_mode. The coordinator otherwise refreshes every 5 minutes.
Mode dispatch is write-once-on-change → verify → reconcile (no per-cycle re-assert); RC-backed
modes refresh the RAM-only RC registers each cycle. Contract in
docs/control_loop.md and docs/solis_control.md.
- Install/configure with Automation off; watch
sensor.sunsale_schedulefor a day or two. - Confirm a manual
select.sunsale_mode_overridereaches the inverter (..._observed_inverter_mode). - Turn Automation on; tune tariff/battery/policy values as real-world cost becomes clear.
python -m venv venv && source venv/bin/activate
pip install -r requirements_dev.txt
pytest # full suite (mostly HA-free)
ruff check custom_components/sun_sale/ && mypy custom_components/sun_sale/contract/, pipeline/, and the pure helpers run without Home Assistant imports.
Architecture (tiered observer DAG → control module) is in
docs/ARCHITECTURE.md and docs/MODULES.md.
Contributing (incl. DCO sign-off): CONTRIBUTING.md.
Live tools (tools/) target a running HA via REST. Credentials resolve from --url/--token,
the HA_URL/HA_TOKEN env vars, or a local tools/secrets.json (gitignored — copy
tools/secrets.example.json):
tools/integration_check.py— deep-checks a live/api/sun_sale/debugsnapshot against raw entity state; exits non-zero on failure (CI / post-deploy gating).tools/deploy.py— push branch → force HACS redownload → restart HA → wait for reload.
- Only
solis_modbusis hardware-verified; the other vendor drivers are implemented but untested against hardware (verify on first connect), and Fronius / SMA / Kostal are telemetry-only. - The SoC-bucketed DP introduces small discretisation error vs. a continuous optimum.
GNU AGPL-3.0-or-later © 2026 Laurynas Klimavicius.
Free to use, modify, and redistribute — as long as your version stays open under the same license. Because sunSale can control hardware over a network, the AGPL also requires anyone who runs a modified version as a network-accessible service to make their source available.