fix chargedischarge A #6

Merged
vojacekd merged 1 commits from refactor-control-monolith into main 2026-05-04 19:39:49 +02:00
4 changed files with 26 additions and 8 deletions

View File

@@ -268,7 +268,10 @@ def deye_battery_charge_discharge_amps(
"""
Proud nabíjení / vybíjení (reg 108 / 109) pro zápis Deye.
PASSIVE + plán chce nabíjet z PV přebytku i při exportu do sítě: nenulový charge, discharge 0.
PASSIVE + plán chce nabíjet (typicky z FVE, i při exportu zbytku do sítě): **108 = max_charge_a**
z invertoru — reg. 108 je strop, ne příkaz k proudu; průměrný `battery_w` ze slotu nesmí špičku FVE
stíhat do baterie omezovat. **109 = max_discharge_a** (domácnost z baterie při výpadku PV).
**CHARGE** (import ze sítě + nabíjení): 108 dál z `battery_w` (řízený importní okamžik). **SELL** beze změny.
"""
if lock_battery:
return 0, 0
@@ -279,7 +282,7 @@ def deye_battery_charge_discharge_amps(
if self_sustain_local_use:
return int(max_charge_a), int(max_discharge_a)
if bat_w > 0:
return battery_watts_to_amps(bat_w, max_charge_a), 0
return int(max_charge_a), int(max_discharge_a)
return _deye_zero_export_amps_for_passive(
grid_w, bat_w, int(max_charge_a), int(max_discharge_a)
)

View File

@@ -1,4 +1,4 @@
"""PASSIVE + nabíjení z PV přebytku při současném exportu → nenulový nabíjecí proud."""
"""PASSIVE + plán chce nabíjet: 108 = plný strop z DB, 109 = max (PV špička + domácnost)."""
from __future__ import annotations
@@ -18,6 +18,21 @@ class PassivePvSurplusChargeAmpsTests(unittest.TestCase):
max_charge_a=100,
max_discharge_a=100,
)
self.assertEqual(ch, 100)
self.assertEqual(dis, 100)
def test_charge_mode_still_scales_108_from_battery_w(self) -> None:
"""CHARGE (síť + baterie): 108 podle plánu, ne vždy plný strop."""
ch, dis = deye_battery_charge_discharge_amps(
lock_battery=False,
deye_mode="CHARGE",
self_sustain_local_use=False,
bat_w=2000,
grid_w=3000,
max_charge_a=100,
max_discharge_a=100,
)
self.assertLess(ch, 100)
self.assertGreater(ch, 0)
self.assertEqual(dis, 0)

View File

@@ -152,7 +152,7 @@ bits 01). Detail registrů: [`modbus-registers.md`](modbus-registers.md) (reg
| **CHARGE** | `battery_w` > 0 **a** `grid_setpoint_w` > 0 |
| **PASSIVE (ZERO)** | vše ostatní — reg. **108/109** z `deye_battery_charge_discharge_amps()` v `setpoints.py` (volá `write_inverter_setpoints`) |
**PASSIVE** (AUTO, ZERO): proudy **108/109** počítá **`deye_battery_charge_discharge_amps`**: pokud plán žádá **nabíjení** (`battery_w > 0`) včetně scénáře **PV přebytek + export do sítě** (`grid_setpoint_w < 0`), nastaví se **kladný nabíjecí proud (108)** a **109 = 0** — nesmí se použít čistě „zero export“ větev, která by při exportu vynutila **108 = 0** a rozbila soulad plán ↔ Deye. Jinak platí `_deye_zero_export_amps_for_passive` (export bez nabíjení → 108 = 0, import bez vybíjení → 109 = 0). **TOU** z plánu. Reg. **142** = `deye_zero_export_mode`. Reg. **143** je tvrdý limit exportu z lokality/invertoru (ne forecast). Reg. **145** (solar sell): **0** při `export_ban` mimo SELL, jinak **1** — význam přepínače a rozdíl vůči neřízeným FVE polím je v [`operating-modes.md`](operating-modes.md) (sekce *ZERO a zakázaný export*).
**PASSIVE** (AUTO, ZERO): proudy **108/109** počítá **`deye_battery_charge_discharge_amps`**: pokud plán žádá **nabíjení** (`battery_w > 0`) a režim zůstává **PASSIVE** (typicky FVE přebytek, často i **export** části výroby), **108 = max_charge_a z invertoru** — jde o **horní limit** proudu do baterie; průměrný `battery_w` ze 15min slotu nesmí špičku FVE do baterie uměle omezovat (dřívější odvod z W dával smysl jen u **CHARGE** ze sítě). **109 = max z DB**. Když plán nabíjení nechce (`battery_w ≤ 0`) a exportuje přebytek, platí pass-through: **108 = 0**, **109 = max** (`_deye_zero_export_amps_for_passive`). **TOU** z plánu. Reg. **142** = `deye_zero_export_mode`. Reg. **143** je tvrdý limit exportu z lokality/invertoru (ne forecast). Reg. **145** (solar sell): **0** při `export_ban` mimo SELL, jinak **1** — viz [`operating-modes.md`](operating-modes.md) (sekce *ZERO a zakázaný export*).
**SELF_SUSTAIN** zůstává **PASSIVE** v `get_deye_mode`; **108/109** jsou vždy **max z DB** (bez variant ZERO). Rozdíl je **`self_sustain_local_use=True`**: **TOU SOC** = **`min_soc_percent`**, `battery_w=None`.
@@ -160,8 +160,8 @@ bits 01). Detail registrů: [`modbus-registers.md`](modbus-registers.md) (reg
| Registr | Charge | PASSIVE (ZERO) | SELL | Self-consumption |
|---|---|---|---|---|
| **108** (charge A) | škálo dle `battery_w` | max / **0** (export bez `battery_w>0`) / **>0** při `battery_w>0` i při exportu | **0** | dle varianty |
| **109** (discharge A) | **0** | max / **0** (import, držet bat.) / **0** při `battery_w>0` + export z PV | **max z DB** | dle varianty |
| **108** (charge A) | škálo dle `battery_w` | max / **0** (export bez nabíjení) / **max** při PASSIVE + `battery_w>0` (FVE do baterie až po strop) | **0** | dle varianty |
| **109** (discharge A) | **0** | max / **0** (import, držet bat.) / **max** při PASSIVE + `battery_w>0` | **max z DB** | dle varianty |
| **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 | max z DB (tvrdý limit, bez forecast heuristiky) | max z DB |
| **145** (solar sell) | 1 / 0 při `export_ban` | 1 / 0 při `export_ban` | 1 | 1 / 0 při `export_ban` |

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 |
|-----|-------|--------|----------|---------------|
| 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) podle `_deye_zero_export_amps_for_passive`: výchozí **max**, u exportu v plánu bez vybíjení **0**. **CHARGE:** proud z `battery_w` přes `battery_watts_to_amps`. **SELL:** **0**. |
| 109 | Max discharge current | 0 … **max dle modelu** (manuál Deye) | 1 A | Ve **PASSIVE** (AUTO): výchozí **max**, u importu bez nabíjení **0**; **SELL** max vybíjení; **CHARGE** typicky **0**. |
| 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**). **PASSIVE** + plán chce nabíjet (`battery_w>0`): **108 = max** (špička FVE nesmí být omezená průměrem slotu). **PASSIVE** + export bez nabíjení: **0**. **CHARGE:** z `battery_w` přes `battery_watts_to_amps`. **SELL:** **0**. |
| 109 | Max discharge current | 0 … **max dle modelu** (manuál Deye) | 1 A | Ve **PASSIVE** (AUTO): výchozí **max**, u importu bez nabíjení **0**; při **PASSIVE + `battery_w>0` + export** zůstává **max** (domácnost z baterie při výpadku PV). **SELL** max vybíjení; **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. |
| 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) |