From 7f3b0957ccbce5fe2b23d28e5573218be2c9c36d Mon Sep 17 00:00:00 2001 From: Dusan Vojacek Date: Sun, 19 Apr 2026 23:46:16 +0200 Subject: [PATCH] fix discharge battery --- backend/services/control/exporter_monolith.py | 25 ++++++++----------- docs/04-modules/control.md | 10 ++++---- docs/04-modules/modbus-registers.md | 4 +-- docs/04-modules/operating-modes.md | 4 +-- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/backend/services/control/exporter_monolith.py b/backend/services/control/exporter_monolith.py index 1b0b38f..305573d 100644 --- a/backend/services/control/exporter_monolith.py +++ b/backend/services/control/exporter_monolith.py @@ -1383,14 +1383,13 @@ def get_deye_mode(setpoints: ControlSetpoints) -> str: záporný export z portu sítě a zároveň **|battery_w| ≥ |grid_setpoint_w|** (výdej z baterie není menší než plánovaný čistý export). Pak Deye „selling first“ (reg. 142=0). - - **PASSIVE** — všude jinde: přetok FVE do sítě / do baterie řeší reg. **108** (nabíjení), - **109** (vybíjení, škálované podle plánu), **142** dle ``deye_zero_export_mode`` instalace, - **145** solar sell. Žádné odhady PV vs. load v této funkci. + - **PASSIVE** — všude jinde: reg. **108** / **109** na **max. proud** invertoru; přetok FVE / chování + vůči zátěži drží **142** dle ``deye_zero_export_mode``, **TOU výkon** z plánu, **145** solar sell. - **CHARGE** — nabíjení ze sítě (``battery_w`` > 500 a ``grid_setpoint_w`` > 200). - ``battery_w=None`` (SELF_SUSTAIN) → bat_w 0 → PASSIVE zde; plné 108/109 řeší - ``self_sustain_local_use`` v ``write_inverter_setpoints``. + ``battery_w=None`` (SELF_SUSTAIN) → bat_w 0 → PASSIVE zde; **108/109 max** stejně jako u běžného + PASSIVE v ``write_inverter_setpoints`` (viz ``self_sustain_local_use`` pro TOU SOC). """ grid_w = int(setpoints.grid_setpoint_w or 0) bat_w = 0 if setpoints.battery_w is None else int(setpoints.battery_w) @@ -1476,16 +1475,12 @@ async def write_inverter_setpoints( charge_a = int(inv.max_charge_a) discharge_a = int(inv.max_discharge_a) else: - # PASSIVE: škáluj 108/109 podle |battery_w| — ne plný max při malém setpointu z LP. - if bat_w > 0: - charge_a = battery_watts_to_amps(bat_w, inv.max_charge_a) - discharge_a = 0 - elif bat_w < 0: - charge_a = 0 - discharge_a = battery_watts_to_amps(bat_w, inv.max_discharge_a) - else: - charge_a = 0 - discharge_a = 0 + # PASSIVE (AUTO): plný strop 108/109 — stejná idea jako SELF_SUSTAIN. + # Dříve škálování podle |battery_w| z LP usekávalo fyzický výkon baterie (např. 23 A při + # ~1,2 kW plánu) a velká akumulace pak neuměla rychle doplnit síť při nárazové zátěži. + # Ekonomiku a směr toku drží TOU časové body (výkon W / SOC %) + režim 142/178, ne reg. 108/109. + charge_a = int(inv.max_charge_a) + discharge_a = int(inv.max_discharge_a) zero_exp_mode = int(inv.deye_zero_export_mode or 1) selling_mode = 0 if deye_mode == "SELL" else zero_exp_mode diff --git a/docs/04-modules/control.md b/docs/04-modules/control.md index 23f4e0f..9574e1a 100644 --- a/docs/04-modules/control.md +++ b/docs/04-modules/control.md @@ -119,22 +119,22 @@ def apply_overrides(plan, overrides) -> Setpoints: | **CHARGE** | `battery_w` > 500 **a** `grid_setpoint_w` > 200 (nabíjení ze sítě) | | **PASSIVE** | vše ostatní | -**PASSIVE** (včetně FVE přetoku do sítě): reg. **108/109** škálované podle `battery_w` z plánu, reg. **145** = 1 (solar sell), reg. **142** = `deye_zero_export_mode`. +**PASSIVE** (AUTO, včetně FVE přetoku do sítě): reg. **108** i **109** na **maximum z DB** (plný proudový rozsah baterie); jemnější výkon drží **TOU časové body** z plánu. Reg. **145** = 1 (solar sell), reg. **142** = `deye_zero_export_mode`. -**SELF_SUSTAIN** (záložní režim po Modbus mismatch apod.) zůstává **PASSIVE** z hlediska `get_deye_mode`, ale `write_inverter_setpoints` nastaví **reg. 108 i 109 na maximum z DB** (`self_sustain_local_use=True` v `ControlSetpoints`), **reg. 142** na `asset_inverter.deye_zero_export_mode` (1 = zero export to load, 2 = zero export to CT) a **TOU SOC** na **`min_soc_percent`** (typicky 12 %), aby střídač maximalizoval využití baterie lokálně místo zákazu nabíjení při `battery_w=None`. +**SELF_SUSTAIN** (záložní režim po Modbus mismatch apod.) zůstává **PASSIVE** z hlediska `get_deye_mode`; **108/109** jsou stejně **max z DB** jako u AUTO PASSIVE. Rozdíl je **`self_sustain_local_use=True`**: **TOU SOC** se drží na **`min_soc_percent`** (typicky 12 %) a `battery_w=None`, aby střídač prioritizoval lokální buffer při zero-export, ne ekonomiku LP. ### Klíčové registry podle typu slotu | Registr | Charge | Pass-through / PASSIVE | SELL (battery-led) | Self-consumption | |---|---|---|---|---| -| **108** (charge A) | škálo dle `battery_w` | škálo dle `battery_w` (>0) / **0** | **0** | **0** | -| **109** (discharge A) | **0** | škálo dle `\|battery_w\|` (<0) / **0** | **max z DB** | škálo dle `\|battery_w\|` | +| **108** (charge A) | škálo dle `battery_w` | **max z DB** | **0** | **max z DB** | +| **109** (discharge A) | **0** | **max z DB** | **max z DB** | **max z DB** | | **142** (limit control) | `deye_zero_export_mode` | `deye_zero_export_mode` | **0** (selling first) | `deye_zero_export_mode` | | **143** (export cap) | max z DB | max z DB | `min(max_site, max(200, \|grid_setpoint_w\|))` | max z DB | | **145** (solar sell) | 1 | 1 | 1 | 1 | | **178** (peak shaving) | 48 | 48 | **32** | 48 | -Při provozním režimu **SELF_SUSTAIN** je v PASSIVE **108 = max** i **109 = max** (viz odstavec výše), nikoli hodnoty ve sloupci „Pass-through“. +Sloupce **Pass-through / PASSIVE** (AUTO) a **Self-consumption** (typicky SELF_SUSTAIN / záloha) mají u **108/109** stejně **max z DB**; liší se hlavně **TOU SOC** a `battery_w` (viz výše). Hodnota `deye_zero_export_mode` (1 = zero export to load, 2 = zero export to CT) pochází z `asset_inverter.deye_zero_export_mode` a závisí na fyzické instalaci (přítomnost CT). Detail v [`modbus-registers.md`](modbus-registers.md). diff --git a/docs/04-modules/modbus-registers.md b/docs/04-modules/modbus-registers.md index f4c3e13..e3aa602 100644 --- a/docs/04-modules/modbus-registers.md +++ b/docs/04-modules/modbus-registers.md @@ -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 | EMS počítá proud v **SQL**: `COALESCE(deye_register_max_charge_a, FLOOR(LEAST(W)/51.2))` — sloupec stropu v **A** je volitelný (NULL = jen odvod z kW); při vyplnění např. 350 při W→351 A se použije 350. V Pythonu se navíc **clampuje horní strop 350 A** (`DEYE_LV_BATTERY_MAX_CHARGE_DISCHARGE_A`), aby firmware nevracel 350 při zápisu 351. | -| 109 | Max discharge current | 0 … **max dle modelu** (manuál Deye) | 1 A | Stejně: `COALESCE(deye_register_max_discharge_a, FLOOR(LEAST(W)/51.2))` + **clamp 350 A** jako u 108. | +| 108 | Max charge current | 0 … **max dle modelu** (manuál Deye) | 1 A | Strop **A** z DB (`COALESCE(deye_register_max_charge_a, odvod z kW)` + clamp **350 A**). Ve **PASSIVE** (AUTO) `write_inverter_setpoints` zapisuje **vždy plný strop** — škálování podle malého `battery_w` z LP se **nepoužívá** (TOU výkon drží jemnější signál). Režim **CHARGE** stále odvádí proud z plánovaného výkonu přes `battery_watts_to_amps`. | +| 109 | Max discharge current | 0 … **max dle modelu** (manuál Deye) | 1 A | Obdobně jako 108; ve **PASSIVE** plný strop, **SELL** plný vybíjecí proud, **CHARGE** typicky 0. | | 128 | Grid charge current | 0 … **max dle modelu** (manuál Deye) | 1 A | Nabíjení ze sítě. Firmware automaticky sníží reálný proud tak, aby `load + battery_charge` nepřekročil velikost jističe — proto LP v `planning_engine.py` plánuje `gi[t]` až **do `max_import_power_w + BMS_max_charge`**, aby uměl využít cenově nejlepší 15min okna pro nabíjení na plný BMS strop (viz `planning.md` sekce „Plánovací strop gi[t] vs. fyzický jistič"). Fyzické dodržení jističe drží reg 128 + firmware. | | 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) | diff --git a/docs/04-modules/operating-modes.md b/docs/04-modules/operating-modes.md index 7613476..d4deb11 100644 --- a/docs/04-modules/operating-modes.md +++ b/docs/04-modules/operating-modes.md @@ -3,7 +3,7 @@ ## Keep it simple - **Méně heuristik a pevných wattových práhů v řízení** — každá magická konstanta je místo, kde se rodí neshody s plánem a ekonomikou. -- **SELL na Deye** používej jen tam, kde produktově opravdu chceme režim „**vylít baterii do sítě**“ (selling first). Vše ostatní patří do **PASSIVE** a tok FVE ↔ baterie ↔ síť se řeší **reg. 108 / 109 / 142** podle plánu a instalace. +- **SELL na Deye** používej jen tam, kde produktově opravdu chceme režim „**vylít baterii do sítě**“ (selling first). Vše ostatní patří do **PASSIVE**: **108/109** dávají **plný proudový rozsah** baterie, směr toku a ekonomiku drží **142** + **TOU** z plánu a instalace. - Novou logiku vždy ověřit proti **reálnému řádku plánu** (audit / `planning_interval`) a typické chybě „plán říká kW, měnič jede na MW“. ## Přehled @@ -24,7 +24,7 @@ Detekce v `get_deye_mode` (`battery_w` = `battery_setpoint_w` z plánu, záporn - **CHARGE:** `battery_w` > 500 **a** `grid_setpoint_w` > 200 → nabíjení ze sítě; reg. **142** dle CHARGE větve v exporteru, **178** = 48. - **SELL:** `grid_setpoint_w` < 0 **a** `battery_w` < 0 **a** `|battery_w| ≥ |grid_setpoint_w|` → záměr **vybíjet baterii do sítě** (selling first); reg. **142** = 0, **178** = 32, **108** = 0, **109** = max, reg. **143** omezen podle `|grid_setpoint_w|` (viz `control_exporter.py`). -- **PASSIVE:** vše ostatní — tok FVE a baterie řídí **reg. 108** (nabíjení) a **109** (vybíjení, škálované podle `|battery_w|`), **142** = `deye_zero_export_mode`, **145** = 1, **178** = 48. +- **PASSIVE:** vše ostatní — **reg. 108** a **109** na **plný strop** z konfigurace střídače (jako SELF_SUSTAIN); jemné řízení výkonu/SOC jde přes **TOU časové body** z plánu, **142** = `deye_zero_export_mode`, **145** = 1, **178** = 48. **SELF_SUSTAIN:** `battery_w = None` ⇒ v `get_deye_mode` jako 0 ⇒ typicky **PASSIVE**; v `write_inverter_setpoints` při `self_sustain_local_use=True` → **108 i 109 = max**, reg. **142** = zero export dle DB, TOU SOC = **`min_soc_percent`**. **PRESERVE:** `lock_battery` → **108 = 0**, **109 = 0**.