From 342483b8857db100cb39518971bf7eb97dce3334 Mon Sep 17 00:00:00 2001 From: Dusan Vojacek Date: Wed, 29 Apr 2026 13:24:28 +0200 Subject: [PATCH] invert logic cutoff register --- backend/services/control/exporter_monolith.py | 6 ++++-- backend/services/telemetry_collector.py | 4 ++-- backend/tests/test_telemetry_export_limit_flags.py | 4 ++-- docs/04-modules/control.md | 4 ++-- docs/04-modules/modbus-command-journal.md | 4 ++-- docs/04-modules/modbus-registers.md | 4 ++-- 6 files changed, 14 insertions(+), 12 deletions(-) diff --git a/backend/services/control/exporter_monolith.py b/backend/services/control/exporter_monolith.py index e006deb..528f20a 100644 --- a/backend/services/control/exporter_monolith.py +++ b/backend/services/control/exporter_monolith.py @@ -1648,9 +1648,11 @@ async def write_inverter_setpoints( ) if inv.deye_gen_microinverter_cutoff_enabled: + # Deye UI semantics: "MI export cutoff ENABLE" means export to grid is blocked (GEN effectively cut off). + # Therefore: want_cutoff=True -> ENABLE (3), want_cutoff=False -> DISABLE (2). want_cutoff = bool(setpoints_now.deye_gen_cutoff_enabled) and deye_mode != "SELL" target_bits = ( - REG179_MI_EXPORT_DISABLE if want_cutoff else REG179_MI_EXPORT_ENABLE + REG179_MI_EXPORT_ENABLE if want_cutoff else REG179_MI_EXPORT_DISABLE ) try: mb179 = await get_modbus_client(inv.host, inv.port) @@ -1823,7 +1825,7 @@ async def read_deye_registers_live(site_id: int, db: asyncpg.Connection) -> dict "reg178_peak_shaving_switch": int(r178), "reg179_control_board_special_1": int(r179), "reg179_mi_export_cutoff_bits": int(r179) & int(REG179_MI_EXPORT_MASK), - "reg179_mi_export_cutoff_is_on": (int(r179) & int(REG179_MI_EXPORT_MASK)) == int(REG179_MI_EXPORT_DISABLE), + "reg179_mi_export_cutoff_is_on": (int(r179) & int(REG179_MI_EXPORT_MASK)) == int(REG179_MI_EXPORT_ENABLE), "reg191_peak_shaving_w": int(r191), "read_at": read_at.isoformat(), } diff --git a/backend/services/telemetry_collector.py b/backend/services/telemetry_collector.py index 52e28af..e0e662e 100644 --- a/backend/services/telemetry_collector.py +++ b/backend/services/telemetry_collector.py @@ -28,7 +28,7 @@ DEYE_REG_GRID_EXPORT_TOTAL_LO = 524 DEYE_REG_GRID_EXPORT_TOTAL_HI = 525 DEYE_REG_PV1_POWER = 672 DEYE_REG_PV2_POWER = 673 -# Solar sell (0 = přebytek řiditelné FVE nesmí do sítě) a GEN/MI cut-off (bits0–1 == 2 → cut-off ON); viz modbus-registers.md +# Solar sell (0 = přebytek řiditelné FVE nesmí do sítě) a GEN/MI cut-off (bits0–1 == 3 → cut-off ON); viz modbus-registers.md DEYE_REG_SOLAR_SELL = 145 DEYE_REG_CONTROL_BOARD_SPECIAL1 = 179 @@ -48,7 +48,7 @@ def _export_limit_flags_from_deye_regs(reg145: int | None, reg179: int | None) - flags = 0 if reg145 is not None and int(reg145) == 0: flags |= 1 - if reg179 is not None and (int(reg179) & 3) == 2: + if reg179 is not None and (int(reg179) & 3) == 3: flags |= 2 return (flags != 0), flags diff --git a/backend/tests/test_telemetry_export_limit_flags.py b/backend/tests/test_telemetry_export_limit_flags.py index bfd2396..6d3d5b5 100644 --- a/backend/tests/test_telemetry_export_limit_flags.py +++ b/backend/tests/test_telemetry_export_limit_flags.py @@ -19,10 +19,10 @@ def test_solar_sell_enabled_only() -> None: def test_gen_mi_cutoff_bits() -> None: - lim, flags = _export_limit_flags_from_deye_regs(None, 2) + lim, flags = _export_limit_flags_from_deye_regs(None, 3) assert lim is True and flags == 2 def test_combined_flags() -> None: - lim, flags = _export_limit_flags_from_deye_regs(0, 2) + lim, flags = _export_limit_flags_from_deye_regs(0, 3) assert lim is True and flags == 3 diff --git a/docs/04-modules/control.md b/docs/04-modules/control.md index 65b5a0b..e76972d 100644 --- a/docs/04-modules/control.md +++ b/docs/04-modules/control.md @@ -118,8 +118,8 @@ U instalací typu **BA81** (AC coupling / mikroinvertory na GEN portu) může so `asset_inverter.deye_gen_microinverter_cutoff_enabled = true`, exporter provede **masked read-modify-write** registru **179**: -- `deye_gen_cutoff_enabled = true` → reg **179** bits **0–1** = **2** (`10b`, disable = cut-off **ON**) -- `deye_gen_cutoff_enabled = false` → reg **179** bits **0–1** = **3** (`11b`, enable = cut-off **OFF**) +- `deye_gen_cutoff_enabled = true` → reg **179** bits **0–1** = **3** (`11b`, enable = cut-off **ON** / export blokován) +- `deye_gen_cutoff_enabled = false` → reg **179** bits **0–1** = **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 0–1). Detail registrů: [`modbus-registers.md`](modbus-registers.md) (reg 179). diff --git a/docs/04-modules/modbus-command-journal.md b/docs/04-modules/modbus-command-journal.md index dcd4a3b..a09b353 100644 --- a/docs/04-modules/modbus-command-journal.md +++ b/docs/04-modules/modbus-command-journal.md @@ -25,8 +25,8 @@ 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ů 4–5** 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 masked RMW (zachová ostatní bity). - Při ověření se za shodu považuje jen maska **bits 0–1** (`0x0003`) vůči očekávání (2 = cutoff ON, 3 = cutoff OFF). +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 0–1** (`0x0003`) vůči očekávání (**3 = cutoff ON**, **2 = cutoff OFF**). 4. **TOU výkon W (154–159):** 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 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. 6. Po třech neúspěšných cyklech ověření: diff --git a/docs/04-modules/modbus-registers.md b/docs/04-modules/modbus-registers.md index ff54c22..203922a 100644 --- a/docs/04-modules/modbus-registers.md +++ b/docs/04-modules/modbus-registers.md @@ -64,8 +64,8 @@ Režim **CHARGE_CHEAP** nastaví oba setpointy na stejný kladný výkon (min. 1 ### 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 -- `true` → exporter nastaví reg **179** bits0–1 na **2** (`10b`, disable = cut-off ON) -- `false` → exporter nastaví bits0–1 na **3** (`11b`, enable = cut-off OFF) +- `true` → exporter nastaví reg **179** bits0–1 na **3** (`11b`, enable = cut-off ON / export blokován) +- `false` → exporter nastaví bits0–1 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 0–1 maskou, ale očekávají přímo hodnotu 2/3.