idempotence zapisu 178 a 179 grid peak shaveing a grid cuttoff
Some checks failed
CI and deploy / migration-check (push) Failing after 10s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-04-20 11:41:57 +02:00
parent a07f5d57cb
commit 6cf14ed25b
3 changed files with 59 additions and 5 deletions

View File

@@ -332,7 +332,16 @@ def _drop_registers_matching_last_verified(
skipped: list[int] = [] skipped: list[int] = []
for reg, meta, val in registers: for reg, meta, val in registers:
lv = last_verified.get(int(reg)) lv = last_verified.get(int(reg))
if lv is not None and lv == int(val): if lv is not None:
# reg178: porovnáváme jen masku bitů 45 (Deye si v dalších bitech drží vlastní stav).
if int(reg) == 178 and _deye_reg178_verify_match(int(val), int(lv)):
skipped.append(int(reg))
continue
# reg179: porovnáváme jen bits01 maskou 0x0003 (masked RMW zachovává ostatní bity).
if int(reg) == 179 and _deye_reg179_verify_match(int(val), int(lv)):
skipped.append(int(reg))
continue
if int(lv) == int(val):
skipped.append(int(reg)) skipped.append(int(reg))
continue continue
out.append((reg, meta, val)) out.append((reg, meta, val))

View File

@@ -0,0 +1,38 @@
from services.control.exporter_monolith import (
REG178_PASSIVE,
_drop_registers_matching_last_verified,
)
def test_drop_registers_skips_reg178_when_mask_matches():
# last_verified contains extra bits beyond 0x0030; we still want to skip if bits 45 match.
registers = [(178, "grid_peak_shaving_switch", REG178_PASSIVE)]
last_verified = {178: 12030} # real-world example from home-01 (bits4-5 still == 0b11)
out, skipped = _drop_registers_matching_last_verified(registers, last_verified)
assert out == []
assert skipped == [178]
def test_drop_registers_keeps_reg178_when_mask_differs():
registers = [(178, "grid_peak_shaving_switch", REG178_PASSIVE)]
last_verified = {178: 32} # SELL mask 0b10
out, skipped = _drop_registers_matching_last_verified(registers, last_verified)
assert out == registers
assert skipped == []
def test_drop_registers_skips_reg179_when_mask_matches():
registers = [(179, "control_board_special_1", 2)] # bits01 = 2 (cutoff ON)
last_verified = {179: 0x1236} # ...0110b => bits01 still == 2
out, skipped = _drop_registers_matching_last_verified(registers, last_verified)
assert out == []
assert skipped == [179]
def test_drop_registers_keeps_reg179_when_mask_differs():
registers = [(179, "control_board_special_1", 2)] # want cutoff ON
last_verified = {179: 0x1237} # ...0111b => bits01 == 3 (cutoff OFF)
out, skipped = _drop_registers_matching_last_verified(registers, last_verified)
assert out == registers
assert skipped == []

View File

@@ -32,7 +32,7 @@ EMS zapisuje řídící hodnoty přes journal (`modbus_command`) a **`write_regi
- **EMS NEZAPISUJE** nastavit **manuálně v SolarmanApp**. - **EMS NEZAPISUJE** nastavit **manuálně v SolarmanApp**.
- Hodnota určuje výkon peak shavingu v **W** (typicky 016 000). - Hodnota určuje výkon peak shavingu v **W** (typicky 016 000).
### Reg 178 hodnoty podle fyzického režimu ### Reg 178 hodnoty podle fyzického režimu + idempotence
- **SELL:** **32** bit45 = **10**, grid peak shaving **disable** (export do sítě). - **SELL:** **32** bit45 = **10**, grid peak shaving **disable** (export do sítě).
- **PASSIVE** a **CHARGE:** **48** bit45 = **11**, grid peak shaving **enable**. - **PASSIVE** a **CHARGE:** **48** bit45 = **11**, grid peak shaving **enable**.
@@ -41,6 +41,8 @@ EMS **nezapisuje** read-modify-write (paralelní čtení jinými klienty může
**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á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`.
**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).
## Klíčové registry podle fyzického režimu Deye ## 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**. Solver navíc rozlišuje **čtyři typy slotů** každý typ určuje specifickou kombinaci registrů. Provozní režimy EMS (AUTO, SELF_SUSTAIN, SELL, …) se mapují na **tři fyzické režimy** střídače: **PASSIVE**, **SELL**, **CHARGE**. Solver navíc rozlišuje **čtyři typy slotů** každý typ určuje specifickou kombinaci registrů.
@@ -62,7 +64,12 @@ Režim **CHARGE_CHEAP** nastaví oba setpointy na stejný kladný výkon (min. 1
### BA81: GEN port cut-off (reg 179) z plánu ### BA81: GEN port cut-off (reg 179) 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 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 **2** (`10b`, disable = cut-off ON)\n+- `false` → exporter nastaví bits01 na **3** (`11b`, enable = cut-off OFF)\n+\n+Zápis je **masked read-modify-write** (zachová ostatní bity reg. 179). Ověření v journalu (`verify_modbus_commands`) porovnává jen bits01 maskou `0x0003`.\n+ - `true` → exporter nastaví reg **179** bits01 na **2** (`10b`, disable = cut-off ON)
- `false` → exporter nastaví bits01 na **3** (`11b`, enable = cut-off OFF)
Zápis je **masked read-modify-write** (zachová ostatní bity reg. 179). Ověření v journalu (`verify_modbus_commands`) porovnává jen bits01 maskou `0x0003`.
**Idempotence:** pokud poslední `verified` hodnota už má správně nastavené bits01 (maska `0x0003`), EMS zápis reg. 179 v dalším běhu přeskočí (ostatní bity se ignorují).
**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. **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 ### Provozní režim EMS SELF_SUSTAIN