fix soc v TOU (ne 100) pri ne-grid-charge
Some checks failed
CI and deploy / migration-check (push) Failing after 15s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-05-02 12:15:40 +02:00
parent fffe6c7185
commit b20cb6e0f9
5 changed files with 47 additions and 49 deletions

View File

@@ -40,9 +40,6 @@ REG143_SELL_CAP_MIN_W = 200
# Ostatní bity zachovat → read-modify-write.
REG178_SELL = 0b00100000 # 32, grid peak shaving disable
REG178_PASSIVE = 0b00110000 # 48, grid peak shaving enable (PASSIVE i CHARGE)
# TOU reg 166+ ve PASSIVE při prioritě baterie: signál střídači „využij celý dostupný rozsah“,
# ne provozní strop z DB (ten je pro LP / Wh viz asset_battery.max_soc_percent).
DEYE_TOU_SOC_PASSIVE_BATTERY_PRIORITY_PCT = 100
# Verify: jen bity 45 (horní byte layout v dokumentaci); ostatní bity mohou mít firmware / Loxone
REG178_VERIFY_MASK = 0x0030
# Reg 178 bits 01: MI export cutoff (AC coupling / GEN).
@@ -1411,11 +1408,6 @@ def _clamp_deye_tou_soc_pct(pct: int) -> int:
return max(5, min(95, pct))
def _clamp_deye_tou_soc_pct_hi(pct: int, hi: int) -> int:
"""Stejné dolní omezení 5 % jako u TOU; horní mez z parametru (např. 100 u priority baterie)."""
return max(5, min(int(hi), int(pct)))
def _deye_tou_min_soc_pct(inv: InverterConfig) -> int:
if inv.min_soc_percent is not None:
return _clamp_deye_tou_soc_pct(int(inv.min_soc_percent))
@@ -1429,39 +1421,23 @@ def _deye_tou_reserve_soc_pct(inv: InverterConfig) -> int:
def _deye_passive_tou_battery_soc_pct(
inv: InverterConfig,
setpoints: ControlSetpoints,
inv: InverterConfig, _setpoints: ControlSetpoints
) -> int:
"""
Hodnota SOC u Deye TOU řádku (reg 166+) ve fyzickém PASSIVE.
Na home-01 Deye interpretuje TOU % jako „kam má směřovat využití baterie“:
je-li zapsané procento **nižší než skutečný SoC**, přebytek FVE míří spíš do sítě.
Vždy provozní minimum z DB (**``min_soc_percent``**, clamp 595 jako u všech TOU SOC)
— signál „spodní pásmo“ pro firmware, aby baterii šlo použít pro překrytí zátěže bez
snahy o vysoký cílový SoC jen přes TOU.
Při **záporné vykupní** nebo **plánovaném nabíjení** (kladný ``battery_w``) EMS
zapíše **100 %** do TOU (signál střídači „ber přebytek do baterie v celém rozsahu“).
**``max_soc_percent`` v DB** je odděleně: horní limit pro **plánovač / Wh bilance**
(denní provoz, viz komentář sloupce), **nikoli** časové „do kdy“.
Riziko spojené v minulosti s nízkým TOU („přebytek FVE tíhne do sítě“ při nízkém %
oproti skutečnému SoC) řeší **LP**, **145** (**``export_ban``** při záporné vykupní),
řez GEN (**178**) a další páky — ne zvyšování TOU nad **min_soc**. Přímé dobíjení ze
sítě a cílové horní pásmo: větev **CHARGE** v ``_deye_tou_params`` (**``max_soc_percent``**).
Jinak zůstane provozní podlaha ``min_soc_percent`` (typicky nízká % → přetok do sítě
možný dle chování Deye).
Režim **SELF_SUSTAIN** (``self_sustain_local_use``): vždy ``min_soc_percent`` — nízké
TOU drží prioritu „baterie jako buffer“ při plném reg. 108/109 a reg. 142 zero-export;
neaplikuje se sem logika 100 % podle ceny (LP se v SELF_SUSTAIN nepoužívá).
Argument ``_setpoints`` zůstává kvůli volajícím API; hodnoty z něj PASSIVE SOC nebere.
"""
mn = _deye_tou_min_soc_pct(inv)
if setpoints.self_sustain_local_use:
return mn
bat_w = 0 if setpoints.battery_w is None else int(setpoints.battery_w)
sell = setpoints.effective_sell_price_czk_kwh
want_battery_priority = bat_w > 0 or (sell is not None and float(sell) < 0)
if not want_battery_priority:
return mn
return _clamp_deye_tou_soc_pct_hi(DEYE_TOU_SOC_PASSIVE_BATTERY_PRIORITY_PCT, hi=100)
return _deye_tou_min_soc_pct(inv)
def _deye_zero_export_amps_for_passive(
@@ -1521,7 +1497,8 @@ def _deye_tou_params(
) -> tuple[int, int, bool]:
"""
Parametry jednoho Deye time pointu: výkon W, SOC % (TOU reg 166+), grid_charge.
Ve PASSIVE viz _deye_passive_tou_battery_soc_pct (min vs. plný max z DB).
Ve PASSIVE: TOU SOC = ``min_soc_percent`` z DB; v CHARGE: horní hraniční SoC =
``asset_battery.max_soc_percent`` (clamp 10100).
"""
max_batt_w_discharge = int(inv.max_discharge_a * BATT_VOLTAGE_V)
tp_discharge_w = 0 if setpoints.lock_battery else max_batt_w_discharge
@@ -1534,7 +1511,7 @@ def _deye_tou_params(
raw_bat = setpoints.battery_w
battery_w = int(raw_bat) if raw_bat is not None else 0
cap = int(inv.max_soc_percent) if inv.max_soc_percent is not None else 95
target_soc = max(10, min(95, cap))
target_soc = max(10, min(100, cap))
tp_charge_w = (
battery_watts_to_amps(battery_w, int(inv.max_charge_a)) * int(BATT_VOLTAGE_V)
)