6.9 KiB
Modbus command journal
Účel
Každý zápis na Modbus TCP (Deye a později další aktiva) se ukládá do tabulky ems.modbus_command jako samostatný řádek: cílový registr, hodnota, endpoint, vazba na site_id a volitelně na planning_run_id. Po zápisu má řádek stav written; samostatný verifikační job (každé 2 minuty) nebo ruční GET /api/v1/sites/{site_id}/control/verify přečte registr zpět a nastaví value_verified a stav verified nebo mismatch. Výjimka: Deye 62–64 (systémový čas) se vždy ověřují jako celek jedním čtením 62–64 a tolerančně podle dekódovaného data/času — řádky 62–64 se neprohánějí striktní větví po jednom registru (jinak by zejména 64 způsoboval falešné mismatch a SELF_SUSTAIN). Podmnožina written řádků (např. jen 64) se sloučí s dotazem na všechny written 62–64 pro daný invertor; viz modbus-registers.md.
Schéma ems.modbus_command
| Sloupec | Význam |
|---|---|
asset_type / asset_id / asset_code |
Typ aktiva (inverter, …), FK logicky na příslušnou tabulku, čitelný kód |
device_* |
Host, port, Modbus unit ID |
register |
Číslo registru (decimal); v logu též hex |
register_name |
Např. charge_limit, export_limit |
value_to_write / value_written / value_verified |
Požadavek, potvrzený zápis, ověření čtením |
status |
pending, written, verified, failed, mismatch, retrying |
planning_run_id |
Volitelná vazba na aktivní plán |
deye_physical_mode |
U zápisů z write_inverter_setpoints: PASSIVE / SELL / CHARGE (stejná hodnota na všech řádcích daného běhu exportu); jinak NULL |
attempt_count |
Počet pokusů o zápis (pro limity retry) |
Indexy: podle (site_id, status, created_at) a částečný index pro pending / retrying.
Verifikace a bezpečnost
- Po
mismatchse odešle Discord alert (notify_modbus_mismatch), pokud je nastavenDISCORD_WEBHOOK_URL. - Retry zápisu max. 3× (počítáno přes
attempt_countpo zápisech). - Reg 178 (grid peak shaving switch): journal ukládá celé 16bit
value_to_write(32 nebo 48). Při ověření se za shodu považuje shoda bitů 4–5 maskou0x0030— readback může mít jiné ostatní bity (firmware / paralelní čtení).value_verified= přečtená surová hodnota; stavverified, pokud maska sedí s očekáváním. - Pojistka 62–64: pokud by se řádek registru 62, 63 nebo 64 omylem dostal do striktní větve po jednom registru, verify to zachytí a zpracuje jako toleranční celek 62–64 (stejně jako primární clock větev) — bez přepnutí do SELF_SUSTAIN jen kvůli tomu.
- Po třech neúspěšných cyklech ověření:
- Obyčejné registry (mimo souvislý blok Deye 62–64): přepnutí lokality na SELF_SUSTAIN přes
run_fn_set_mode_with_discord→ems.fn_set_mode(activated_by=system:mismatch, poznámka = důvod). Při skutečné změněmode_codejde na Discord kritická zpráva (stejný formát jako u ostatních přepnutí režimu). - Výjimka — systémový čas 62–64: přepnutí režimu se neprovádí. Po 3 neúspěšných ověřeních jde kritický Discord (
notify_modbus_clock_verify_exhausted); střídač a EMS režim zůstávají v aktuálním stavu (čas na sběrnici může vyžadovat ruční kontrolu / firmware).
- Obyčejné registry (mimo souvislý blok Deye 62–64): přepnutí lokality na SELF_SUSTAIN přes
Baseline po deployi (operativa): např. počet přepnutí na SELF_SUSTAIN z verify za poslední 2 dny:
SELECT count(*) FROM ems.site_operating_mode_log WHERE mode_code = 'SELF_SUSTAIN' AND activated_by = 'system:mismatch' AND activated_at >= now() - interval '2 days';
Pro diagnostiku času Deye po opravě clock logiky používej u modbus_command krátké okno (např. verified_at >= now() - interval '2 days').
Discord při jakékoli změně režimu (nejen Modbus): notification_service.run_fn_set_mode_with_discord volá ems.fn_set_mode a při změně mode_code oproti stavu před voláním pošle zprávu (notify_operating_mode_changed). Úroveň: user:api → info, obecné system:* → warning, system:mismatch → critical. Použití: HTTP POST /api/v1/sites/{site_id}/mode, _switch_to_self_sustain v control_exporter. Vypršení valid_until: ems.fn_expire_modes() vrací řádky (site_id, site_code, old_mode, new_mode) pro každé provedené přepnutí; scheduler v main.py (a lazy expire v _fetch_operating_mode) z nich pošle Discord.
Implementace: services/control_exporter.py — verify_modbus_commands, _verify_deye_clock_written_bundle, _fetch_written_deye_clock_commands, _switch_to_self_sustain; services/notification_service.py — run_fn_set_mode_with_discord, notify_operating_mode_changed.
Střídač (Deye)
write_inverter_setpoints přidá do journalu podle potřeby 62–64 (čas — po čtení z invertoru jen při driftu / 24h intervalu; viz modbus-registers.md) a time pointy 148–177 (bloky 3–6 typicky jednou denně; viz modbus-registers.md), dále 108, 109, 141, 142, 178, 143. Každý řádek daného exportního běhu má deye_physical_mode (PASSIVE / SELL / CHARGE). Reg 191 EMS nezapisuje (SolarmanApp). Převod výkonu: battery_watts_to_amps v modbus-registers.md.
Dávky: execute_modbus_commands slučuje souvislé adresy do jednoho write_registers (FC 0x10). verify_modbus_commands čte zpět po souvislých blocích (read_holding_registers, FC 0x03). Detail režimů: modbus-registers.md.
APScheduler
| Job | Frekvence | Popis |
|---|---|---|
verify_modbus |
každé 2 min | Pro každou aktivní site vybere written příkazy s written_at v posledních 20 min a zavolá verify_modbus_commands. |
Ruční API
GET /api/v1/sites/{site_id}/control/verify?minutes=10
Vrátí počty checked / verified / mismatch a seznam dotčených příkazů s aktuálním stavem po verifikaci.
ems.cutoff_switch_log
Tabulka pro budoucí logování cut-off přepínačů (mikroinvertory / GEN při záporné prodejní ceně). Záznam při změně stavu: asset_code, new_state, previous_state, reason, sell_price_czk, triggered_by. Zatím jen schéma; logika napojení v control_exporter je v TODO.
Konfigurace
.env:DISCORD_WEBHOOK_URL— prázdné = notifikace vypnuté (jen log).
Související soubory
- Migrace:
db/migration/V023__modbus_command_journal.sql,V025__deye_physical_mode.sql,V030__deye_clock_sync_at.sql,V044__deye_register_max_current_a.sql; repeatablesdb/routines/R__fn_set_mode.sql(fn_expire_modesvrací detail přepnutí pro notifikace) - Backend:
backend/services/control_exporter.py,backend/services/modbus_client.py,backend/services/notification_service.py,backend/app/main.py - Registry Deye:
docs/04-modules/modbus-registers.md