Files
rootandClaude Opus 5.5 33aead7352
CI / Check (gateway, backend, frontend) (push) Successful in 1m8s
refactor: remove the CWU heater, the house has none
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>
2026-10-25 09:20:35 +01:00

7.4 KiB
Raw Permalink Blame History

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

  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/
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.