Files
ems/docs/04-modules/telemetry.md
Dusan Vojacek 02f0ab66e4
Some checks failed
CI and deploy / migration-check (pull_request) Failing after 27s
CI and deploy / deploy (pull_request) Has been skipped
gpt5.5 - odladeni dokumentace dle kodu
2026-05-02 19:17:04 +02:00

190 lines
8.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Modul: Telemetry (Sběr dat ze zařízení)
## Co modul dělá
- Čte data ze střídače Deye přes Modbus TCP
- EV nabíječky Teltonika a tepelné čerpadlo Samsung mají zatím placeholder
vzorky; konkrétní registry jsou TODO
- 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 | zatím placeholder `available`, 0 W |
| Teltonika EV nabíječka 2 | 60 s | zatím placeholder `available`, 0 W |
| Samsung tepelné čerpadlo | 60 s | zatím placeholder hodnoty |
### Chování při chybě
- Chyba komunikace: záznam se nezapíše, chyba se loguje
- Kód zatím nedrží počítadlo po sobě jdoucích chyb podle zařízení; chyby se logují
při jednotlivých poll pokusech
- 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 z Deye; v DB `battery_power_w` platí **+ nabíjení, vybí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):**
Aktuální řízení Deye je popsané v [`control.md`](control.md) a
[`modbus-registers.md`](modbus-registers.md). Nepoužívá starý `write_register`
model, ale journal `ems.modbus_command` a FC 0x10 (`write_registers`) pro
registry 108/109/141/142/143/145/178/340 + TOU bloky.
Historická orientační mapa níže neplatí jako implementační kontrakt:
| 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 (632A) |
| 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 sdíleného Modbus klienta z `services.modbus_client`; 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 navazujícími
funkcemi pro baseline/accuracy. Spouští ji Python APScheduler: audit filler v
minutách `:01,:16,:31,:46`, forecast accuracy v `:02,:17,:32,:47`.
```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__071_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
MODBUS_CONNECT_TIMEOUT_SEC=5
MODBUS_READ_TIMEOUT_SEC=3
```
`TELEMETRY_POLL_INTERVAL_SEC` a chybové prahy zatím nejsou v kódu používány;
smyčka běží každých 60 s přímo v `run_telemetry_loop_wrapper`.
---
## Otevřené body
- [x] Základní mapování Deye (holding registry 500673) 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ů