{"id":50908851,"url":"https://github.com/slippyex/sunsteer","last_synced_at":"2026-06-16T08:00:52.075Z","repository":{"id":364745282,"uuid":"1267378076","full_name":"slippyex/sunsteer","owner":"slippyex","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-14T09:09:30.000Z","size":1508,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-14T11:09:27.572Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/slippyex.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-12T13:39:00.000Z","updated_at":"2026-06-14T09:09:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/slippyex/sunsteer","commit_stats":null,"previous_names":["slippyex/sunsteer"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/slippyex/sunsteer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slippyex%2Fsunsteer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slippyex%2Fsunsteer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slippyex%2Fsunsteer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slippyex%2Fsunsteer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/slippyex","download_url":"https://codeload.github.com/slippyex/sunsteer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/slippyex%2Fsunsteer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34396429,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-16T08:00:28.056Z","updated_at":"2026-06-16T08:00:52.059Z","avatar_url":"https://github.com/slippyex.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Sunsteer\n\n[![ci](https://github.com/slippyex/sunsteer/actions/workflows/ci.yml/badge.svg)](https://github.com/slippyex/sunsteer/actions/workflows/ci.yml)\n[![release](https://img.shields.io/github/v/release/slippyex/sunsteer?include_prereleases\u0026sort=semver)](https://github.com/slippyex/sunsteer/releases)\n[![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\n**Local SG-Ready heat-pump control from PV surplus.**\n\nSunsteer reads your grid meter, decides when genuine PV surplus is available, and\nswitches your heat pump's SG-Ready input through a local relay — no cloud, fully\nobservable, with a web UI that explains every single decision.\n\nIt exists because the vendor cloud couldn't switch a relay. The full story:\n[*The Relay the Cloud Couldn't Switch*](https://medium.com/@mvelten773/the-relay-the-cloud-couldnt-switch-c6eac9dab196).\n\n![Sunsteer web UI — local control room for PV-surplus heat-pump control](docs/img/ui-desktop.png)\n\n\u003csub\u003eThe control room: live surplus and the decision being made right now (left), today's\nsurplus/threshold/heat-pump-run timeline, heat-pump · inverter · weather telemetry, and the\neffectiveness of self-consumed PV. History and the full decision log expand below.\u003c/sub\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eMore views — history charts \u0026amp; mobile\u003c/summary\u003e\n\nHistory expanded (savings, temperatures, runtime vs. surplus, compressor, efficiency, PV strings):\n\n![Sunsteer history charts](docs/img/ui-desktop-history.png)\n\nThe same control room on a phone:\n\n![Sunsteer on mobile](docs/img/ui-mobile.png)\n\n\u003c/details\u003e\n\n## Try it in two minutes — no hardware needed\n\n```bash\ngit clone https://github.com/slippyex/sunsteer.git\ncd sunsteer/deploy/compose\ndocker compose -f docker-compose.demo.yml up -d\n```\n\nOpen **http://localhost:8080** (login `admin` / `sunsteer`). A synthetic PV \"day\"\npasses every 10 minutes; switch the mode to **auto** in the settings to watch the\ncontroller decide in real time. The heat-pump telemetry card is populated too — the demo\nruns the `heatpump-exporter` with its `mock` driver (`HEATPUMP_DRIVER=mock`), so no vendor\naccount is needed. Tear down with `docker compose -f docker-compose.demo.yml down -v`.\n\n## Features\n\n- **Adaptive threshold** — the ON threshold scales with the remaining PV forecast for\n  the day (Open-Meteo GTI per roof plane, forecast.solar fallback), so cloudy days\n  still harvest surplus instead of waiting for a peak that never comes.\n- **Self-calibrating** — the forecast's performance ratio is recalibrated daily from\n  your actual production. No manual tuning drift.\n- **Hysteresis done right** — ON/OFF streak requirements, minimum runtimes and\n  off-times protect the compressor; no relay flapping on passing clouds.\n- **Fail-safe by design** — stale meter data switches the heat pump OFF; a hardware\n  auto-off watchdog on the relay catches a dead controller; the web UI is\n  fail-closed behind HTTP Basic auth. See [docs/architecture.md](docs/architecture.md).\n- **Explainable** — every decision lands in a decision log with its reason; the UI's\n  \"why\" card explains in plain language (English/German) what the controller is\n  waiting for right now.\n- **Runtime tuning in the UI** — thresholds, delays, runtimes and prices live in the\n  database and hot-reload every control cycle. Static hardware config stays in `.env`.\n- **Observable** — Prometheus metrics from every service, optional Grafana add-on,\n  English alert rules included.\n\n## Run it for real\n\nYou need: a grid meter the exporter can read (currently **SMA Sunny Home Manager 2.0**),\na **Shelly Gen2 relay** (e.g. Pro 1PM) wired to the heat pump's SG-Ready input, and a\nLinux host with Docker on the same network. Wiring belongs in the hands of a licensed\nelectrician — read [DISCLAIMER.md](DISCLAIMER.md) first.\n\n```bash\ncd deploy/compose\ncp .env.example .env     # fill in the marked REQUIRED values\ndocker compose up -d     # pulls released images from GHCR\n```\n\nStep-by-step instructions: [docs/setup.md](docs/setup.md) ·\nSupported devices and wiring notes: [docs/hardware.md](docs/hardware.md)\n\nKubernetes: see [deploy/k8s/](deploy/k8s/) for example manifests (a generic kustomize\nbase — the reference install runs on K3s).\n\nReleased images (multi-arch, `linux/amd64` + `linux/arm64` — Raspberry Pi works):\n\n| Image | Purpose |\n|---|---|\n| `ghcr.io/slippyex/sunsteer/energy-exporter` | Meter readings (Speedwire multicast or mock), relay state, optional inverter telemetry; serves `/state` + `/metrics`; writes TimescaleDB |\n| `ghcr.io/slippyex/sunsteer/surplus-controller` | The control loop: adaptive threshold → hysteresis → switch the relay; fail-safe OFF on stale data |\n| `ghcr.io/slippyex/sunsteer/control-ui` | Web UI (EN/DE): live status, decision log with explanations, history charts, settings |\n| `ghcr.io/slippyex/sunsteer/heatpump-exporter` | Optional: generic heat-pump telemetry (temperatures, compressor, energy counters); `HEATPUMP_DRIVER=vicare` for Viessmann ViCare, `mock` for the demo — see [docs/heatpump-interface.md](docs/heatpump-interface.md) |\n\n## Architecture\n\n```mermaid\nflowchart LR\n  subgraph MEASURE\n    SHM[SMA Home Manager 2.0\u003cbr/\u003eSpeedwire multicast] --\u003e EXP[energy-exporter]\n    SHELLY[Shelly Gen2 relay] --\u003e|state poll| EXP\n    INV[SMA inverter\u003cbr/\u003eModbus, optional] --\u003e EXP\n  end\n  subgraph DECIDE\n    EXP --\u003e|/state JSON| CTRL[surplus-controller]\n    OM[Open-Meteo GTI\u003cbr/\u003eforecast.solar fallback] --\u003e CTRL\n  end\n  subgraph ACT\n    CTRL --\u003e|Switch.Set + auto-off watchdog| SHELLY\n    SHELLY --\u003e|SG-Ready contact| HP[Heat pump]\n  end\n  EXP --\u003e TSDB[(TimescaleDB)]\n  CTRL --\u003e TSDB\n  TSDB --\u003e UI[control-ui]\n  CTRL --\u003e|/status| UI\n  HPEXP[heatpump-exporter\u003cbr/\u003eoptional, HEATPUMP_DRIVER] --\u003e TSDB\n```\n\nDetails, data flow and the fail-safe chain: [docs/architecture.md](docs/architecture.md)\n\n## Different hardware?\n\nThe controller only consumes a small, versioned `/state` JSON — it does not care where\nthe numbers come from. Two ways to bring your own meter:\n\n1. **In-tree driver:** implement the `GridMeter` protocol in\n   `services/energy-exporter/src/drivers/` (the built-in `mock` driver is the template).\n2. **Your own exporter:** serve the documented `/state` contract from any process —\n   see [docs/state-interface.md](docs/state-interface.md).\n\nContributions welcome: [CONTRIBUTING.md](CONTRIBUTING.md)\n\n## Roadmap\n\n- More meter drivers (Shelly 3EM at the feed-in point, Tibber Pulse, P1/DSMR)\n- Grafana dashboard provisioning\n- More SG-Ready states than ON/OFF (e.g. recommendation vs. command)\n- MQTT / Home Assistant integration\n\n## License\n\n[MIT](LICENSE) — see [DISCLAIMER.md](DISCLAIMER.md) before wiring anything to a\nheating system. Sunsteer is a private project and is not affiliated with SMA,\nShelly/Allterco, or Viessmann.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslippyex%2Fsunsteer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fslippyex%2Fsunsteer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fslippyex%2Fsunsteer/lists"}