fix discharge battery
Some checks failed
CI and deploy / migration-check (push) Failing after 18s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-04-19 23:46:16 +02:00
parent e3776226a4
commit 7f3b0957cc
4 changed files with 19 additions and 24 deletions

View File

@@ -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 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). 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í), - **PASSIVE** — všude jinde: reg. **108** / **109** na **max. proud** invertoru; přetok FVE / chování
**109** (vybíjení, škálované podle plánu), **142** dle ``deye_zero_export_mode`` instalace, vůči zátěži drží **142** dle ``deye_zero_export_mode``, **TOU výkon** z plánu, **145** solar sell.
**145** solar sell. Žádné odhady PV vs. load v této funkci.
- **CHARGE** — nabíjení ze sítě (``battery_w`` > 500 a ``grid_setpoint_w`` > 200). - **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ší ``battery_w=None`` (SELF_SUSTAIN) → bat_w 0 → PASSIVE zde; **108/109 max** stejně jako u běžného
``self_sustain_local_use`` v ``write_inverter_setpoints``. PASSIVE v ``write_inverter_setpoints`` (viz ``self_sustain_local_use`` pro TOU SOC).
""" """
grid_w = int(setpoints.grid_setpoint_w or 0) grid_w = int(setpoints.grid_setpoint_w or 0)
bat_w = 0 if setpoints.battery_w is None else int(setpoints.battery_w) 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) charge_a = int(inv.max_charge_a)
discharge_a = int(inv.max_discharge_a) discharge_a = int(inv.max_discharge_a)
else: else:
# PASSIVE: škáluj 108/109 podle |battery_w| — ne plný max při malém setpointu z LP. # PASSIVE (AUTO): plný strop 108/109 — stejná idea jako SELF_SUSTAIN.
if bat_w > 0: # Dříve škálování podle |battery_w| z LP usekávalo fyzický výkon baterie (např. 23 A při
charge_a = battery_watts_to_amps(bat_w, inv.max_charge_a) # ~1,2 kW plánu) a velká akumulace pak neuměla rychle doplnit síť při nárazové zátěži.
discharge_a = 0 # Ekonomiku a směr toku drží TOU časové body (výkon W / SOC %) + režim 142/178, ne reg. 108/109.
elif bat_w < 0: charge_a = int(inv.max_charge_a)
charge_a = 0 discharge_a = int(inv.max_discharge_a)
discharge_a = battery_watts_to_amps(bat_w, inv.max_discharge_a)
else:
charge_a = 0
discharge_a = 0
zero_exp_mode = int(inv.deye_zero_export_mode or 1) zero_exp_mode = int(inv.deye_zero_export_mode or 1)
selling_mode = 0 if deye_mode == "SELL" else zero_exp_mode selling_mode = 0 if deye_mode == "SELL" else zero_exp_mode

View File

@@ -119,22 +119,22 @@ def apply_overrides(plan, overrides) -> Setpoints:
| **CHARGE** | `battery_w` > 500 **a** `grid_setpoint_w` > 200 (nabíjení ze sítě) | | **CHARGE** | `battery_w` > 500 **a** `grid_setpoint_w` > 200 (nabíjení ze sítě) |
| **PASSIVE** | vše ostatní | | **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 ### Klíčové registry podle typu slotu
| Registr | Charge | Pass-through / PASSIVE | SELL (battery-led) | Self-consumption | | 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** | | **108** (charge A) | škálo dle `battery_w` | **max z DB** | **0** | **max z DB** |
| **109** (discharge A) | **0** | škálo dle `\|battery_w\|` (<0) / **0** | **max z DB** | škálo dle `\|battery_w\|` | | **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` | | **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 | | **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 | | **145** (solar sell) | 1 | 1 | 1 | 1 |
| **178** (peak shaving) | 48 | 48 | **32** | 48 | | **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). 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).

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 | | 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. | | 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 | Stejně: `COALESCE(deye_register_max_discharge_a, FLOOR(LEAST(W)/51.2))` + **clamp 350 A** jako u 108. | | 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]`**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. | | 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]`**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ě | | 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) | | 141 | Energy mgmt mode | bitmask | — | EMS vždy **0** (neměnit jinak) |

View File

@@ -3,7 +3,7 @@
## Keep it simple ## 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. - **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“. - 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 ## 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. - **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`). - **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**. **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**.