The app showed the controller's heater signal as a device, a detail and a statistic. Drop it from the API (heaterOn, heaterHours) and the UI; the gateway still records output_heater as raw data. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7.4 KiB
Specification
Stable description of what we build. The plan lives in roadmap.md, the reasons in decisions/.
Goal
An app that helps my father run and understand the house in Krosno, from his phone, also away from home. The heat pump comes first: see what it does and change its few setpoints, replacing the outdated Frisko app. Right after the first heat pump view, the energy: PV, battery, grid and consumption, which my father wants a lot. Simpler, readable and better tested than Frisko. Grafana stays for long-term analysis; the app shows up to 7 days of history.
Scope
In:
- Heat pump: live state, history, four setpoints (boost, away), alarms.
- Energy, read-only: PV production (Deye, Fronius), battery, grid import/export, house consumption.
- Access from outside through my server; Polish UI for my parents.
Out, until decided otherwise:
- Writing to the inverters beyond the battery program and reserve of F8, and controlling PV.
- Editing the weekly program of the heat pump.
- Cloud services (Solarman, Frisko Online) and the logger's push to a second server.
- Replacing Grafana.
Principles
- The heating must keep working. Safety beats features.
- Understand a device first, then add features one at a time, bottom-up.
- Frisko is a source of knowledge about the controller, never of code.
- Code a person reads quickly: small classes, clear names, few dependencies.
Context
| Component | Where | Notes |
|---|---|---|
| Heat pump controller Frisko RX910-SE21, fw v24062015 | behind the gateway | Ground source (boreholes); slow to respond; see frisko/ |
| Gateway Frisko MK01 (DS1206/EM500) | 192.168.0.150:502 |
Modbus TCP → RTU 9600; one TCP client at a time, a new one drops the old |
| Solar thermal collector | roof | Also heats the CWU tank; not connected to anything we read |
| Hybrid inverter Deye SUN-10K-SG04LP3-EU | logger 192.168.0.232:8899 |
10 kW, 3 phases, 48 V battery (backup only); most of the PV |
| PV inverter Fronius Symo 3.7-3-M | 192.168.0.143 |
4.2 kWp; local Solar API (JSON) |
| House meter SDM630 | RS485 gateway 192.168.0.10:502 |
Whole house; read today by rpi-measurements into Influx electricity/PRAD2 (unchanged), moving into our gateway; per-phase energy counters go to electricity/house_meter_phases |
| Solar-Log | 192.168.0.210 |
Old monitoring, no data since April 2026 |
Raspberry Pi (raspberrypi) |
192.168.0.158 |
Debian 11 (kept as is); runs the gateway (and rpi-measurements until the move); BME280 sensor; WireGuard 10.0.0.2 |
| My server | Kraków | Proxmox and Docker: backend, Traefik, InfluxDB 1.x, Grafana, Gitea. Same subnet 192.168.0.0/24 as Krosno; WireGuard on OPNsense |
InfluxDB has no authentication; Traefik allows only Krosno LAN, home LAN and WireGuard.
Architecture
phone (web UI) ─HTTPS─▶ Traefik + auth ─▶ backend (Docker, Kraków) ─WireGuard─▶ gateway (Pi) ─▶ devices
history in InfluxDB owns every device link
- Gateway on the Pi (ADR-006): the only client of the heat pump (MK01), Deye, Fronius and the house meter. Polls, writes readings to InfluxDB with a buffer, serves the snapshot, executes checked commands and timed restores, even without the tunnel.
- Backend in Kraków: phone API, presets, notifications. Reads history from InfluxDB and the live snapshot from the gateway; sends commands only to the gateway.
Layers in both apps, dependencies point inwards only:
| Layer | Contains | Depends on |
|---|---|---|
domain |
readings, settings, commands, rules | nothing |
application |
use cases, ports | domain |
adapters |
gateway: Modbus, Solarman, Fronius, InfluxDB writer, HTTP; backend: gateway client, InfluxDB reader, HTTP | application, domain |
Requirements
Functional:
-
F1 Live state of the heat pump: idle / heating CWU / heating CO, compressor stage, pumps, alarm, current targets and temperatures; the BME280 reading (temperature, humidity, pressure) of the living room. An "Odśwież" button asks for one extra poll of each device, at most once a minute per device (the MK01 takes one client). It also shows the period (comfort or economy) of CWU and room.
-
F2 History for the last 7 days, and per-day heat pump statistics for 7 or 30 days (compressor, CWU and CO hours, starts "at least" because polls are 5 minutes apart).
-
F3 Change four setpoints: economy and comfort temperature of CWU (4143, 4144) and CO (4131, 4132). Any value within limits, or one tap on a named preset. Limits and presets live in the backend config: CWU 5–48 °C (decided; the gateway's ceiling stays 50), CO 5–25 °C.
- Boost: raise the economy temperature for a duration the user picks; the previous value is restored automatically.
- Away: lower the comfort temperature (CWU 10 °C, CO 15 °C by default, lower if the user chooses) until a return date and time the user picks (at most 30 days). Held by the gateway without renewals (ADR-012), lowering only.
- Lease (ADR-011): every other change also needs the backend to renew it every 5 minutes; after 15 minutes without, the gateway restores the previous value.
- Restore now: the user can end any pending change at once; the previous value comes back through the same checks.
-
F4 Alarms with notifications: heat pump alarm, controller link lost, grid outage (house on battery).
-
F5 Health of backend, device links and tunnel.
-
F6 Show the weekly program as the history shows it (comfort intervals per weekday, from the polls; nothing is asked of the controller, since reading its program needs selector writes). Editing it stays out.
-
F7 Energy view: PV production, battery state, grid import/export, house consumption; day totals; a details panel per device (Deye, Fronius, house meter); a battery view with SOC, power, the Deye's program schedule and limits.
-
F8 Battery settings (ADR-013, off until enabled): change the Deye's Time of Use switch and the six slots (start, SOC, grid charge) from the Bateria view, and the app's backup reserve (default 50 %, floor 30 %) that every enabled slot SOC must hold. Refused when the settings reading is older than 30 min or changed meanwhile; read back, no restore.
-
F9 Weather and warnings: a 3-day forecast (Open-Meteo) with a PV estimate calibrated on the last 14 measured days, and IMGW warnings for Krosno, as a card and banners on Podgląd; a storm or wind warning hints on Bateria to keep the battery full. Read-only, from the backend in Kraków.
Safety, for every write:
- S1 Only allowlisted registers, each with min, max and step.
- S2 Read back and verify after writing.
- S3 Audit log: who, when, register, old and new value; shown in the app as the change history.
- S4 Rate limit per register.
- S5 Tests and development never write to a real device or the production database.
- S6 The gateway accepts commands only from the backend: a shared token, and a firewall rule on the Pi that allows only the backend's address.
Non-functional:
- N1 Reachable from outside only through Traefik (Krosno addresses), and behind the app's own sign-in: local accounts and Keycloak, independent (ADR-014).
- N2 Recovers on its own from device, InfluxDB and tunnel outages.
- N3 Existing Grafana dashboards keep working.
- N4 Usable by my parents: large controls, few screens, Polish UI.