fix cutoff a grid peak shaving register
Some checks failed
CI and deploy / migration-check (push) Failing after 13s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-04-29 13:36:38 +02:00
parent 2c884e2135
commit dede8d604d
12 changed files with 79 additions and 125 deletions

View File

@@ -111,18 +111,19 @@ def apply_overrides(plan, overrides) -> Setpoints:
**Princip:** držet mapování plán → Deye **jednoduché**; detail a zdůvodnění v [`operating-modes.md`](operating-modes.md) (sekce *Keep it simple*).
### BA81: GEN port cut-off (mikroinvertory na GEN) přes reg 179
### BA81: GEN port cut-off (mikroinvertory na GEN) přes reg 178 (0-based)
U instalací typu **BA81** (AC coupling / mikroinvertory na GEN portu) může solver uložit do plánu flag
`planning_interval.deye_gen_cutoff_enabled` (true/false). Pokud je na střídači zapnutý feature flag
`asset_inverter.deye_gen_microinverter_cutoff_enabled = true`, exporter provede **masked read-modify-write**
registru **179**:
`asset_inverter.deye_gen_microinverter_cutoff_enabled = true`, exporter provede **read-modify-write**
registru **178** (v některých manuálech/UI uváděno jako “register 179” 1-based):
- `deye_gen_cutoff_enabled = true` → reg **179** bits **01** = **3** (`11b`, enable = cut-off **ON** / export blokován)
- `deye_gen_cutoff_enabled = false` → reg **179** bits **01** = **2** (`10b`, disable = cut-off **OFF** / export povolen)
- `deye_gen_cutoff_enabled = true` → reg **178** bits **01** = **3** (`11b`, enable = cut-off **ON** / export blokován)
- `deye_gen_cutoff_enabled = false` → reg **178** bits **01** = **2** (`10b`, disable = cut-off **OFF** / export povolen)
Zápisy se ukládají do `ems.modbus_command` a ověřují v `verify_modbus_commands` (porovnává se pouze maska
bits 01). Detail registrů: [`modbus-registers.md`](modbus-registers.md) (reg 179).
bits 01). Detail registrů: [`modbus-registers.md`](modbus-registers.md) (reg 178).
### Fyzický režim (`get_deye_mode` v `exporter_monolith.py`)

View File

@@ -100,7 +100,7 @@ def calculate_pv_power(
- Endpoint `GET /api/v1/sites/{site_id}/forecast/pv?date=YYYY-MM-DD` vrací vždy poslední `ok` run per `(interval_start, pv_array_id)` (`DISTINCT ON`), takže UI nevidí duplikáty z historických běhů.
- **Kalibrace delty:** `GET /api/v1/sites/{site_id}/forecast/pv-delta-profile?from=…&to=…` vrací JSON z `ems.fn_pv_forecast_delta_profile` (`deltas`, `deltas_by_array`, `delta_learn_min_ts` z `ems.site_pv_forecast_calibration`). Volitelné query parametry: `half_life_days`, `threshold_w`, `top_n_days`, `non_top_day_factor`, `day_weight_gamma` (NULL u numerických přepsání = hodnota z kalibrační tabulky / default funkce).
- **Úprava kalibrace z API:** `PATCH /api/v1/sites/{site_id}/configuration/pv-forecast-calibration` s JSON tělem (částečný update); odpověď je aktuální řádek kalibrace. Souhrn konfigurace v `GET …/configuration` obsahuje klíč `pv_forecast_calibration`.
- **Telemetrie pro učení delty:** `telemetry_collector` při Modbus poll přidá čte reg. **145** a **179**; `fn_telemetry_inverter_sample` ukládá `is_export_limited` / `pv_derating_flags` (bity 1 = solar sell off, 2 = GEN/MI cut-off aktivní dle masky `(reg179 & 3) == 2`). `fn_fill_forecast_accuracy` sloty s těmito signály označí `telemetry_derating`.
- **Telemetrie pro učení delty:** `telemetry_collector` při Modbus poll čte reg. **145** a **178**; `fn_telemetry_inverter_sample` ukládá `is_export_limited` / `pv_derating_flags` (bity 1 = solar sell off, 2 = GEN/MI cut-off aktivní dle masky `(reg178 & 3) == 3`). `fn_fill_forecast_accuracy` sloty s těmito signály označí `telemetry_derating`.
---

View File

@@ -25,8 +25,9 @@ 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. **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`** s očekáváním; `value_verified` = přečtená surová hodnota. Při nesouladu masky se **jednou** znovu přečte reg. 178 (druhé FC3) kvůli glitchům na RS485 — pokud druhé čtení maskou sedí, stav je **`verified`**.
4. **Reg 179** (control board special 1, BA81 GEN cut-off): exporter zapisuje hodnotu 2/3 (clean write).
Při ověření se za shodu považuje jen maska **bits 01** (`0x0003`) vůči očekávání (**3 = cutoff ON**, **2 = cutoff OFF**).
4. **Reg 178** (control board special 1, BA81 GEN cut-off): exporter nastavuje bits **01** (2/3) pomocí
**read-modify-write**, protože reg 178 je bitové pole i pro další volby (např. peak shaving bits 45).
Při ověření se za shodu považuje maska **bits 01 a 45** (`0x0033`) vůči očekávání.
4. **TOU výkon W (154159):** firmware často vrátí **max. výkon z reg. 108/109 × 51.2 V** místo přesně zapsaného W; verify to akceptuje jako **shodu** (skutečný výkon je stejně omezen proudy 108/109).
5. **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.
6. Po třech neúspěšných cyklech ověření:
@@ -48,8 +49,9 @@ Implementace: `services/control_exporter.py` — `verify_modbus_commands`, `_ver
`write_inverter_setpoints` přidá do journalu podle potřeby **6264** (čas — po čtení z invertoru jen při driftu / 24h intervalu; viz `modbus-registers.md`) a **time pointy 148177** (bloky 36 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`.
Pokud je zapnutý feature `asset_inverter.deye_gen_microinverter_cutoff_enabled = true`, exporter navíc zapisuje
**reg 179** (masked RMW) podle `planning_interval.deye_gen_cutoff_enabled` (BA81 GEN port cut-off).
Pokud je zapnutý feature `asset_inverter.deye_gen_microinverter_cutoff_enabled = true`, exporter nastavuje
**MI export cutoff** přes **reg 178 bits01** (BA81 GEN port cut-off) — stále jako jeden záznam `modbus_command`
pro **reg 178** (spolu s peak shaving bity 45).
**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`.
@@ -72,7 +74,7 @@ Vrátí počty `checked` / `verified` / `mismatch` a seznam dotčených příkaz
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.
Poznámka: **GEN port cut-off na BA81** se aktuálně provádí přímo přes Deye **reg 179** a loguje se v `ems.modbus_command`.
Poznámka: **GEN port cut-off na BA81** se aktuálně provádí přímo přes Deye **reg 178 (bits01)** a loguje se v `ems.modbus_command`.
`cutoff_switch_log` je oddělená tabulka pro budoucí obecnější “cut-off” akce (nezávisle na konkrétním Modbus registru).
## Konfigurace

View File

@@ -20,8 +20,7 @@ EMS zapisuje řídící hodnoty přes journal (`modbus_command`) a **`write_regi
| 142 | Limit control (System work mode) | 0/1/2 | — | **0** = selling first, **1** = zero export to load, **2** = zero export to CT. Hodnota v non-SELL režimech pochází z `asset_inverter.deye_zero_export_mode` (závisí na instalaci viz tabulka níže). V režimu SELL vždy **0**. |
| 145 | Solar sell | 0/1 | — | **0** = disabled (přebytek FVE na **straně měniče** se nesmí vést do sítě — curtailment vůči síti), **1** = enabled. Platí jen pro **FVE pod kontrolou Deye** (`controllable = true`); druhá pole (např. **pv-b** u home-01) EMS tímto registerem neřídí. EMS dnes **vždy zapisuje 1**; při 108 = 0 a 145 = 1 přebytky z řiditelného stringu typicky tečou do sítě (viz pass-through níže). |
| 143 | Export limit W | závisí na typu (SUN-20K až ~13 500) | 1 W | Max export do sítě; hodnota z `site_grid_connection.max_export_power_w` |
| 178 | Grid peak shaving switch | bitmask | — | EMS zapisuje **pevnou** hodnotu (bez read-modify-write kvůli kolizím s paralelním čtením z Loxone): **32** (`0b00100000`, bit45 = **10**) v režimu **SELL**; **48** (`0b00110000`, bit45 = **11**) v **PASSIVE** a **CHARGE**. |
| 179 | Control board special 1 | bitmask | — | **BA81:** bits **01** ovládají „MI export to Grid cutoff“ (AC coupling / GEN): **2** (`10b`) = disable (cutoff ON), **3** (`11b`) = enable. EMS zapisuje **masked RMW** (zachová ostatní bity) jen pokud `asset_inverter.deye_gen_microinverter_cutoff_enabled = true`. |
| 178 | Control board special 1 | bitmask | — | Bitové pole pro více funkcí. **EMS používá:** (a) bits **45** pro peak shaving switch: **32** (`0b00100000`, bit45 = **10**) v režimu **SELL**; **48** (`0b00110000`, bit45 = **11**) v **PASSIVE/CHARGE**. (b) **BA81:** bits **01** pro „MI export to Grid cutoff“ (AC coupling / GEN): **2** = disable (cutoff OFF), **3** = enable (cutoff ON). EMS zapisuje jako **read-modify-write** (zachová ostatní bity). V některých manuálech/UI je to označené jako „register 179“ (1-based). |
| 190 | GEN peak shaving | 016000 | 1 W | Peak shaving na GEN portu |
| 191 | Grid peak shaving power | 016000 | 1 W | **EMS NEZAPISUJE** nastavit **manuálně v SolarmanApp**. Hodnota určuje výkon peak shavingu v **W**. |
@@ -37,9 +36,9 @@ EMS zapisuje řídící hodnoty přes journal (`modbus_command`) a **`write_regi
- **SELL:** **32** bit45 = **10**, grid peak shaving **disable** (export do sítě).
- **PASSIVE** a **CHARGE:** **48** bit45 = **11**, grid peak shaving **enable**.
EMS **nezapisuje** read-modify-write (paralelní čtení jinými klienty může způsobit nesoulad).
EMS zapisuje **read-modify-write** a zachovává ostatní bity (reg 178 obsahuje více funkcí).
**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. Při nesouladu masky následuje **druhé FC3 čtení** reg. 178 (mitigace RS485 glitchů). U **TOU výkonu W (154159)** verify akceptuje i readback **`max_charge_a × 51.2`** nebo **`max_discharge_a × 51.2`**, pokud firmware hodnotu přepíše na interní maximum (skutečný výkon je stejně omezen reg. 108/109). Detail: `modbus-command-journal.md`.
**Ověření v journalu (`modbus_command`):** u zápisu **178** se při verify porovnává maska **bits 01 a 45** (`0x0033`) s očekávanou hodnotou; `value_verified` zůstává plný readback. Při nesouladu masky následuje **druhé FC3 čtení** reg. 178 (mitigace RS485 glitchů). U **TOU výkonu W (154159)** verify akceptuje i readback **`max_charge_a × 51.2`** nebo **`max_discharge_a × 51.2`**, pokud firmware hodnotu přepíše na interní maximum (skutečný výkon je stejně omezen reg. 108/109). Detail: `modbus-command-journal.md`.
**Idempotence (proti spamu zápisů):** pokud poslední `verified` hodnota už má správně nastavené bity 45 (maska `0x0030`), EMS zápis reg. 178 v dalším běhu přeskočí (i když `value_verified` obsahuje jiné bity).
@@ -61,20 +60,15 @@ Režim **CHARGE_CHEAP** nastaví oba setpointy na stejný kladný výkon (min. 1
**PASSIVE (ZERO):** reg. **108/109** podle `_deye_zero_export_amps_for_passive` — při exportu v plánu bez vybíjení je **108 = 0** (přetok FVE); při importu bez nabíjení je **109 = 0** (držet baterii). Jinak oba max (AUTO). Detail: `operating-modes.md`.
### BA81: GEN port cut-off (reg 179) z plánu
### BA81: GEN port cut-off (reg 178 bits01) z plánu
Pro instalace s AC coupling na GEN portu (mikroinvertory) může solver uložit do `planning_interval` flag **`deye_gen_cutoff_enabled`**.\n
- `true` → exporter nastaví reg **179** bits01 na **3** (`11b`, enable = cut-off ON / export blokován)
- `true` → exporter nastaví reg **178** bits01 na **3** (`11b`, enable = cut-off ON / export blokován)
- `false` → exporter nastaví bits01 na **2** (`10b`, disable = cut-off OFF / export povolen)
Zápis do reg. 179 se v praxi provádí jako **„clean write“** hodnoty **2** nebo **3** (bez read-modify-write),
protože některé firmware/UI varianty nevyhodnocují jen bity 01 maskou, ale očekávají přímo hodnotu 2/3.
Ověření v journalu (`verify_modbus_commands`) přesto porovnává jen bits01 maskou `0x0003` (odolnost vůči
paralelním změnám jiných bitů / verzím FW).
Zápis se provádí jako **read-modify-write** nad **reg 178** (zachová ostatní bity registru).
**Idempotence:** EMS zápis reg. 179 přeskočí jen tehdy, když poslední `verified` hodnota je už **clean 2/3**.
Masková shoda s hodnotami typu `0xfffe` / `0xffff` se záměrně **nepovažuje** za “už zapsáno”, aby se zařízení
dostalo do stabilního stavu, který odpovídá UI i chování firmware.
**Idempotence:** pokud poslední `verified` hodnota už má správně nastavené relevantní bity (maska `0x0033`), EMS zápis reg. 178 v dalším běhu přeskočí.
**Pozn.:** Flag se v solveru vůbec nevytváří ani neukládá tam, kde není povolen feature `asset_inverter.deye_gen_microinverter_cutoff_enabled` takové lokality ho nemají ani v UI.
### Provozní režim EMS SELF_SUSTAIN

View File

@@ -67,7 +67,7 @@ Nabíjení ze sítě s vysokým cílovým SoC v TOU řeší větev **CHARGE** (g
**Implementace (BLOCK_EXPORT):** při `effective_sell_price < 0` (slot z plánu) EMS drží fyzicky stále **PASSIVE**, ale zapne **zákaz exportu přebytků** pro řiditelnou FVE:
- **reg 145 = 0** (solar sell disabled) mimo SELL
- **BA81:** navíc přes **reg 179** (bits01) aktivuje „MI export to Grid cutoff“ pro mikroinvertory na GEN portu (jen pokud je `asset_inverter.deye_gen_microinverter_cutoff_enabled = true`).
- **BA81:** navíc přes **reg 178** (bits01; v některých UI jako “register 179”) aktivuje „MI export to Grid cutoff“ pro mikroinvertory na GEN portu (jen pokud je `asset_inverter.deye_gen_microinverter_cutoff_enabled = true`).
Týká se jen výroby, kterou Deye umí ovlivnit; **pv-b / ongrid GEN** u home-01 tímto neomezíš.
#### PV1/PV2 vs. GEN port (důležité pro BLOCK_EXPORT)

View File

@@ -251,7 +251,7 @@ Při `sell_price < 0` tedy nastává problém:
- pokud výroba na GEN portu převýší okamžitou spotřebu + možný charge do baterie, zbytek fyzicky teče do sítě (nechtěný export za zápornou cenu).
Řešení na hardware úrovni:
- **Deye reg 179 bits01** („MI export to Grid cutoff“) umožní GEN port **tvrdě odpojit**.
- **Deye reg 178 bits01** („MI export to Grid cutoff“, často uváděno jako “register 179” v 1-based značení) umožní GEN port **tvrdě odpojit**.
#### Správné rozhodovací pravidlo (záměr)
@@ -278,7 +278,7 @@ kde:
- (případně) explicitní `no_export` politika, pokud je v kontextu dostupná
Mimo tyto případy je `z_gen_cutoff[t]` vynucené na `0`.
- Cut-off je v účelové funkci **penalizované** (za „zahozenou“ GEN výrobu), aby se zapínalo jen jako poslední možnost.
- Výstup se ukládá do `planning_interval.deye_gen_cutoff_enabled` (nullable) a exporter pak jen provede reg 179.
- Výstup se ukládá do `planning_interval.deye_gen_cutoff_enabled` (nullable) a exporter pak nastaví bity reg 178.
**Scope / bezpečnost:** proměnná i flag existují jen na lokalitách, kde je zapnutý `asset_inverter.deye_gen_microinverter_cutoff_enabled` (tj. kde je GEN port s mikroinvertory reálně zapojen). Jinde se nic neřeší ani nezobrazuje.