# Modul: Telemetry (Sběr dat ze zařízení) ## Co modul dělá - Čte data ze střídače Deye, EV nabíječek Teltonika a tepelného čerpadla Samsung přes Modbus TCP - Ukládá surová měření do DB (1min granularita) - Detekuje výpadky komunikace a loguje chyby - Agreguje 1min data na 15min průměry pro spotřebu, audit a plánování --- ## Komponenta: `telemetry_collector` (Python service) Samostatná Python služba. Běží jako smyčka, nezávislá na FastAPI. ### Polling intervaly | Zařízení | Interval | Důvod | |---|---|---| | Deye střídač | 60 s | 1min granularita telemetrie | | Teltonika EV nabíječka 1 | 60 s | | | Teltonika EV nabíječka 2 | 60 s | | | Samsung tepelné čerpadlo | 60 s | | ### Chování při chybě - Chyba komunikace: záznam se nezapíše, chyba se loguje - 3 po sobě jdoucí chyby = alert (log WARNING) - 10 po sobě jdoucích chyb = log ERROR + pokus o reconnect - Data se neinterpolují – chybějící minuty zůstanou prázdné (audit to pozná) --- ## Deye SUN-20K – Modbus registry Komunikace: Modbus TCP, Unit ID dle DIP přepínače na střídači (typicky 1). > Mapování v kódu: `backend/services/telemetry_collector.py` (holding registry, decimal adresa = offset pro `read_holding_registers`). | Dec (hex) | Typ | Popis | Jednotka | Poznámka | |---|---|---|---|---| | 500 (0x01F4) | uint16 | Provozní stav střídače | enum | raw do `run_state`, ladění | | 514 (0x0202) | uint16 | Dnešní nabití baterie | Wh | `batt_charge_today_wh` | | 515 (0x0203) | uint16 | Dnešní vybití baterie | Wh | `batt_discharge_today_wh` | | 588 (0x024C) | uint16 | Battery SoC | % | `battery_soc_percent` | | 590 (0x024E) | int16 | Tok výkonu baterie | W | signed: **+ vybíjení, − nabíjení** | | 625 (0x0271) | int16 | Výkon sítě | W | signed: **+ import, − export** | | 653 (0x028D) | uint16 | Celková spotřeba | W | `load_power_w` | | 667 (0x029B) | int16 | Výkon GEN portu (FVE pole B) | W (signed) | `gen_port_power_w`; záporné při zpětném toku / bez výroby — **číst signed** | | 672 (0x02A0) | int16 | Výkon PV1 | W (signed) | `pv1_power_w`; při špatném unsigned čtení se záporné hodnoty jeví jako desítky kW | | 673 (0x02A1) | int16 | Výkon PV2 | W (signed) | `pv2_power_w` | `pv1_power_w` / `pv2_power_w` / `gen_port_power_w` v DB = **signed W** z Modbus (mohou být záporné). `pv_power_w` = **max(0, PV1) + max(0, PV2) + max(0, GEN)** — okamžitá **kladná** výroba FVE pro dashboard a souhrny; záporné kanály do součtu nepatří (typicky večer při exportu z baterie do sítě). `gen_port_power_w` zůstává uložen samostatně pro audit zeleného bonusu a diagnostiku. **Ověření po změně sběru:** za denního SVitu zkontrolovat, že `pv_power_w` a jednotlivé kanály odpovídají očekávanému max. výkonu instalace (logika: `aggregate_pv_production_w` v `telemetry_collector.py`, unit testy `tests/test_telemetry_pv_signed.py`). **Zápis setpointů (plánování → Deye):** | Registr (hex) | Typ | Popis | Hodnota | |---|---|---|---| | 0x00F3 | Write Single | Battery charge power limit | W | | 0x00F4 | Write Single | Battery discharge power limit | W | | 0x00F6 | Write Single | Grid export power limit | W | | 0x00F0 | Write Single | Work mode | enum (viz tabulka) | Rychlá kontrola komunikace: `scripts/test_modbus_deye.py`. --- ## Teltonika TeltoCharge – Modbus registry Komunikace: Modbus TCP přes Waveshare, Unit ID = 1 (ověřit). > Registry doplnit z Teltonika TeltoCharge Modbus dokumentace / Loxone šablony. | Registr | Typ | Popis | Jednotka | |---|---|---|---| | TBD | Read | Stav konektoru (OCPP status enum) | enum | | TBD | Read | Aktuální výkon | W | | TBD | Read | Kumulativní energie session | Wh | | TBD | Read | Proud L1/L2/L3 | 0.1A | | TBD | Read | Napětí | 0.1V | | TBD | Read | Session ID | uint | | TBD | Read | Error code | uint | | TBD | Write | Max proud (charge limit) | A (6–32A) | | TBD | Write | Povolení nabíjení (on/off) | bool | --- ## Samsung tepelné čerpadlo – Modbus registry Komunikace: Modbus TCP přes Waveshare. > Registry doplnit ze Samsung NASA Modbus dokumentace / Loxone šablony. | Registr | Typ | Popis | Jednotka | |---|---|---|---| | TBD | Read | Venkovní teplota | 0.1°C | | TBD | Read | Teplota vody vstup | 0.1°C | | TBD | Read | Teplota vody výstup | 0.1°C | | TBD | Read | Teplota zásobníku TUV | 0.1°C | | TBD | Read | Příkon | W | | TBD | Read | Provozní režim | enum | | TBD | Read | Alarm kód | uint | | TBD | Read | Odmrazování aktivní | bool | | TBD | Write | Povolení provozu | bool | | TBD | Write | Požadovaná teplota TUV | °C | --- ## Kód telemetrie (Python) Implementace: `backend/services/telemetry_collector.py` — `poll_inverter()` používá konstanty `DEYE_REG_*` a třídu `ModbusDevice`; hlavní smyčka je `run_telemetry_loop` / `run_telemetry_loop_wrapper`. --- ## Agregace 1min → 15min Prováděna PostgreSQL funkcí `ems.fn_fill_audit_interval()` a `ems.fn_fill_baseline_consumption()`. Spouštěna každých 15 minut jako scheduled task (Python APScheduler nebo pg_cron). ```sql -- Příklad agregace telemetrie na 15min průměr -- (součást fn_fill_audit_interval) SELECT site_id, time_bucket('15 minutes', measured_at) AS interval_start, AVG(pv_power_w)::INT AS avg_pv_power_w, AVG(battery_power_w)::INT AS avg_battery_power_w, AVG(grid_power_w)::INT AS avg_grid_power_w, AVG(load_power_w)::INT AS avg_load_power_w, LAST(battery_soc_percent, measured_at) AS last_soc_pct FROM ems.telemetry_inverter WHERE measured_at >= $1 AND measured_at < $1 + INTERVAL '15 minutes' AND site_id = $2 GROUP BY site_id, time_bucket('15 minutes', measured_at); ``` --- ## Timescale continuous aggregates (střídač → dashboard) Nad `ems.telemetry_inverter` běží dva **continuous aggregate** (TimescaleDB); oba se periodicky obnovují (řádově každých 15 minut). Definice CA je ve **verzovaných** migracích (`V011`, `V039`); **view** nad CA držíme v **repeatable** souborech (`db/views/R__*.sql`), aby šla měnit jedna aktuální definice bez nové V migrace. | Objekt | Bucket | View pro PostgREST / UI | Poznámka | |--------|--------|-------------------------|----------| | `ems.telemetry_inverter_hourly` | 1 hodina | `ems.vw_telemetry_hourly_7d` | CA a view v **V011**; `security_invoker` v **V031**. Hodinové trendy. | | `ems.telemetry_inverter_15m` | 15 minut | `ems.vw_telemetry_15m_7d` | **`db/views/R__vw_telemetry_15m_7d.sql`** – posledních 7 dní, zarovnání s 15min sloty přehledu. | **Frontend přehled** (`frontend/src/hooks/useDashboardData.ts`): skutečné výkony a SoC po slotech bere z **`/vw_telemetry_15m_7d`** (klíč slotu = začátek 15min intervalu v UTC, stejně jako `floorSlotUtcMs` v grafu). Horní karty a **aktuální SoC** v grafu jsou dál z **`vw_site_status`** (poslední 1min vzorek) a z WebSocketu `/ws/telemetry`, aby „teď“ odpovídalo boxu i po refreshi agregátu. **Plánovač** počáteční SoC nečte z těchto view – bere poslední řádek z `ems.telemetry_inverter` (`planning_engine._load_site_context`). --- ## Konfigurace (env proměnné) ```env TELEMETRY_POLL_INTERVAL_SEC=60 TELEMETRY_ERROR_WARN_THRESHOLD=3 # počet chyb před WARNING logem TELEMETRY_ERROR_RECONNECT_THRESHOLD=10 MODBUS_CONNECT_TIMEOUT_SEC=5 MODBUS_READ_TIMEOUT_SEC=3 ``` --- ## Otevřené body - [x] Základní mapování Deye (holding registry 500–673) v `telemetry_collector.py` - [ ] Doplnit Modbus registry Teltonika z dokumentace / Loxone šablony - [ ] Doplnit Modbus registry Samsung z dokumentace / Loxone šablony - [ ] Ověřit Unit ID všech zařízení při instalaci - [ ] Optimalizovat čtení Deye jako jeden `read_holding_registers` blok místo jednotlivých registrů