Files
ems/docs/04-modules/telemetry.md
Dusan Vojacek 815a233049
Some checks failed
CI and deploy / migration-check (push) Successful in 28s
CI and deploy / deploy (push) Failing after 17m56s
feat(telemetry): idle-skip zápisů — neukládat 1min řádky idle zařízení
Slabý server: dict (tabulka, asset_id) → (signature, last_stored_at);
_idle_skip ukládá vždy při změně signature, aktivitě, po startu procesu
a heartbeat po > 840 s (každý 15min bucket má ≥ 1 řádek).

- telemetry_ev_charger: aktivní = status != 'available' nebo power > 50 W;
  signature (status, výkon na 100 W)
- telemetry_pool_pump: aktivní = is_on nebo power > 5 W (ON řádky 1/min
  kvůli on_minutes); signature (is_on, výkon na 10 W)
- telemetry_loxone_sensor: jen změna hodnoty ≥ 0.1 / heartbeat
- telemetry_heat_pump: aktivní = mode != 'off' nebo defrost; signature
  (mode, teploty na 0.2 °C)
- telemetry_inverter: beze změny — NIKDY se nepřeskakuje (audit Wh split,
  baseline, SoC plánovače)

Detekce příjezdu/odjezdu EV: previous_status přesunut z posledního řádku DB
do in-memory _EV_LAST_STATUS (po startu seed z vw_latest_ev_charger —
přechod během výpadku se pozná, prázdná DB nevystřelí falešný příjezd);
fn_ev_session_transition se volá jen při změně statusu.

PoolCard: staleness práh 5 → 16 min (> heartbeat 840 s).
Docs: telemetry.md sekce „Idle-skip zápisů" (pravidla pro nové čtecí dotazy:
sumy/gapfill, ne avg přes řádky), planning-changelog (TUV °C/min).
Testy: tests/test_telemetry_idle_skip.py — _idle_skip jednotkově + EV
arrival/departure přežije skip i restart procesu (303 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 19:06:41 +02:00

11 KiB
Raw Blame History

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 a 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.pypoll_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.


Idle-skip zápisů (úspora zápisů na slabém serveru)

Zařízení v klidu negeneruje nový 1min řádek — vzorek se zahodí, pokud je zařízení idle a signature (kvantizovaný stav) se nezměnila. Mechanismus: _idle_skip(key, signature, is_active, now, max_gap_s=840) v telemetry_collector.py, modulový stav (tabulka, asset_id) → (signature, last_stored_at).

Uloží se vždy, když: signature se změnila; zařízení je aktivní; od posledního uložení uplynulo > 840 s (heartbeat — každý 15min bucket má ≥ 1 řádek); nebo jde o první vzorek po startu procesu.

Tabulka Aktivní = ukládá se 1/min Signature
telemetry_ev_charger status != 'available' nebo power_w > 50 (status, výkon na 100 W)
telemetry_pool_pump is_on nebo power_w > 5 (is_on, výkon na 10 W)
telemetry_loxone_sensor nikdy (čidlo) — jen změna/heartbeat hodnota na 0.1
telemetry_heat_pump operating_mode != 'off' nebo defrost (mode, teploty na 0.2 °C)
telemetry_inverter vždy — NIKDY se nepřeskakuje

Střídač se nepřeskakuje: je vstupem auditu (per-minute Wh split, 7 směrových toků), baseline a SoC plánovače — hustá řada je nutná.

Detekce příjezdu/odjezdu EV už nečte předchozí status z posledního řádku DB (ten je při idle-skip řidší), ale z in-memory _EV_LAST_STATUS; po startu procesu se seeduje z vw_latest_ev_charger (přechod během výpadku backendu se pozná, prázdná DB nevystřelí falešný příjezd). fn_ev_session_transition se volá jen při změně statusu.

Důsledky pro čtecí dotazy (POVINNÉ pravidlo): nad idle-skip tabulkami nesmí nový dotaz počítat avg(power) přes přítomné řádky — chybějící minuta znamená „zařízení idle ≈ 0 W“ a avg by aktivitu části okna nadhodnotil. Správně: suma / počet minut okna (sum(power_w) / 15.0 pro 15min slot), time_bucket_gapfill, nebo delta čítače energie. Poslední hodnota (vw_latest_*, TUV teplota, teplota bazénu) je díky heartbeatu max. ~14 min stará — staleness prahy musí být > 840 s (PoolCard používá 16 min).

Přizpůsobené čtecí cesty:

  • fn_fill_audit_interval (R__019): EV a TČ sum(power_w)/15 místo avg.
  • fn_update_tuv_usage_stats (R__018): delta TUV normalizovaná na °C/min délkou mezery mezi řádky (gap_min), mezery > 30 min vyloučeny.
  • fn_update_baseline_stats (R__003): beze změny — coalesce(avg, 0) v okně ±30 s; chybějící řádek = 0 W, což při idle-skip platí.
  • vw_pool_pump_day_energy (R__097): on_minutes počítá ON řádky — drží, protože zapnuté čerpadlo se ukládá každou minutu; kWh je delta čítače.
  • fn_pool_daily_runtime_min, fn_planning_site_context (TUV), fn_load_planning_slots_full (EV status): poslední hodnota — heartbeat stačí.

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.

-- 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é)

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

  • 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ů