fix mismatch rs485
All checks were successful
deploy / deploy (push) Successful in 28s
test / smoke-test (push) Successful in 7s

This commit is contained in:
Dusan Vojacek
2026-04-12 20:28:17 +02:00
parent 71d8405cee
commit 0ba72c7704
4 changed files with 144 additions and 30 deletions

View File

@@ -24,10 +24,16 @@ Indexy: podle `(site_id, status, created_at)` a částečný index pro `pending`
1. Po `mismatch` se odešle **Discord** alert (`notify_modbus_mismatch`), pokud je nastaven `DISCORD_WEBHOOK_URL`.
2. **Retry** zápisu max. **3×** (počítáno přes `attempt_count` po zápisech).
3. Po třech neúspěšných cyklech ověření:
3. **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ů 45** maskou **`0x0030`** — readback může mít jiné ostatní bity (firmware / paralelní čtení). `value_verified` = přečtená surová hodnota; stav **`verified`**, pokud maska sedí s očekáváním.
4. **Pojistka 6264**: 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 6264** (stejně jako primární clock větev) — bez přepnutí do SELF_SUSTAIN jen kvůli tomu.
5. Po třech neúspěšných cyklech ověření:
- **Obyčejné registry** (mimo souvislý blok Deye **6264**): 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_code` jde na Discord **kritická** zpráva (stejný formát jako u ostatních přepnutí režimu).
- **Výjimka — systémový čas 6264:** 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).
**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`.
@@ -42,7 +48,7 @@ Implementace: `services/control_exporter.py` — `verify_modbus_commands`, `_ver
| Job | Frekvence | Popis |
|-----|-----------|--------|
| `verify_modbus` | každé **2 min** | Pro každou aktivní site vybere `written` příkazy s `written_at` v posledních **10 min** a zavolá `verify_modbus_commands`. |
| `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
@@ -60,6 +66,6 @@ Tabulka pro budoucí logování **cut-off** přepínačů (mikroinvertory / GEN
## Související soubory
- Migrace: `db/migration/V023__modbus_command_journal.sql`, `V025__deye_physical_mode.sql`, `V030__deye_clock_sync_at.sql`; repeatables `db/routines/R__fn_set_mode.sql` (`fn_expire_modes` vrací detail přepnutí pro notifikace)
- 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`; repeatables `db/routines/R__fn_set_mode.sql` (`fn_expire_modes` vrací 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`

View File

@@ -12,8 +12,8 @@ EMS zapisuje řídící hodnoty přes journal (`modbus_command`) a **`write_regi
| Reg | Název | Rozsah | Jednotka | Použití v EMS |
|-----|-------|--------|----------|---------------|
| 108 | Max charge current | 0 … **max dle modelu** (manuál Deye) | 1 A | Limit nabíjení baterie; horní mez není napříč modely stejná (nižší výkonové řady mívají jiný strop než např. SUN-20K) |
| 109 | Max discharge current | 0 … **max dle modelu** (manuál Deye) | 1 A | Limit vybíjení baterie; viz výše |
| 108 | Max charge current | 0 … **max dle modelu** (manuál Deye) | 1 A | Limit nabíjení baterie; horní mez není napříč modely stejná (nižší výkonové řady mívají jiný strop než např. SUN-20K). Volitelně **`asset_inverter.deye_register_max_charge_a`**: tvrdý strop v **A** pro zápis do registru (firmware může oříznout pod hodnotou odvozenou z W/51,2, např. 351→350). |
| 109 | Max discharge current | 0 … **max dle modelu** (manuál Deye) | 1 A | Limit vybíjení baterie; viz výše. Obdobně **`deye_register_max_discharge_a`**. |
| 128 | Grid charge current | 0 … **max dle modelu** (manuál Deye) | 1 A | Nabíjení ze sítě |
| 130 | Grid charge enable | 0/1 | — | 1 = povolit nabíjení ze sítě |
| 141 | Energy mgmt mode | bitmask | — | EMS vždy **0** (neměnit jinak) |
@@ -37,6 +37,8 @@ EMS zapisuje řídící hodnoty přes journal (`modbus_command`) a **`write_regi
EMS **nezapisuje** read-modify-write (paralelní čtení jinými klienty může způsobit nesoulad).
**Ověření v journalu (`modbus_command`):** u zápisu **178** se při verify porovnávají jen **bity 45** maskou **`0x0030`** s očekávanou hodnotou (32/48); `value_verified` zůstává plný readback. Detail: `modbus-command-journal.md`.
## Klíčové registry podle fyzického režimu Deye
Provozní režimy EMS (AUTO, SELF_SUSTAIN, SELL, …) se mapují na **tři fyzické režimy** střídače: **PASSIVE**, **SELL**, **CHARGE**. Ostatní je politika solveru / EMS, ne samostatný „režim“ invertoru.
@@ -64,7 +66,7 @@ Vychází z **`grid_setpoint_w`** a **`battery_w`** z `ControlSetpoints` (aktivn
Režim **CHARGE_CHEAP** v EMS nastaví `grid_setpoint_w` tak, aby platila podmínka importu (> 200 W), jinak by fyzicky zůstal PASSIVE.
Všechny limity (`max_charge_a`, `max_discharge_a`, `max_export_power_w` / reg 143) pocházejí **výhradně z DB** (`_load_inverter_config`).
Limity `max_charge_a` / `max_discharge_a` (odvozené z W a BMS) a volitelné stropy **`deye_register_max_charge_a` / `deye_register_max_discharge_a`** pocházejí z DB (`_load_inverter_config`, migrace **V044**). `max_export_power_w` / reg 143 také z DB.
## Time Points řízení podle fyzického režimu