# Specification Stable description of what we build. The plan lives in [roadmap.md](roadmap.md), the reasons in [decisions/](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 1. The heating must keep working. Safety beats features. 2. Understand a device first, then add features one at a time, bottom-up. 3. Frisko is a source of knowledge about the controller, never of code. 4. 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/](frisko/rx910-se21.md) | | 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](decisions/006-gateway-on-the-pi.md)): 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, heater, alarm, current targets and temperatures; the BME280 reading (temperature, humidity, pressure) of the living room. - F2 History for the last 7 days, and per-day heat pump statistics for 7 or 30 days (compressor, CWU, CO and heater 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 Later, low priority: show the weekly program (reading needs selector writes). - 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. 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. - 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 with authentication. - 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.