solver nastavuje stavy deye
This commit is contained in:
@@ -32,7 +32,7 @@ DEYE_CLOCK_RESYNC_INTERVAL_HOURS = 24
|
||||
# Deye LV baterie: převod výkon → proud pro registry 108/109 (viz docs/04-modules/modbus-registers.md)
|
||||
BATT_VOLTAGE_V = 51.2
|
||||
|
||||
# Reg 143 ve SELL: strop exportu = min(site max, |grid_setpoint|), dolní podlahová konstanta kvůli nule.
|
||||
# Reg 143 ve SELL: min(|grid_setpoint_w|, …) nesmí klesnout pod tuto podlahu (W) — kvůli chování firmware, ne mapování režimu.
|
||||
REG143_SELL_CAP_MIN_W = 200
|
||||
|
||||
# Reg 178 – pevné hodnoty (bit4–5); bez read-modify-write (kolize s Loxone / transaction ID)
|
||||
@@ -340,6 +340,8 @@ class ControlSetpoints:
|
||||
ev1_power_w: int
|
||||
ev2_power_w: int
|
||||
target_soc_pct: int | None = None
|
||||
#: Explicitní fyzický režim z plánu (PASSIVE/SELL/CHARGE). Pokud je vyplněn, má přednost před detekcí ze znamének.
|
||||
deye_physical_mode: str | None = None
|
||||
#: Efektivní vykupní cena slotu (Kč/kWh z plánu); pro TOU řízení priorit baterie vs. přetok
|
||||
effective_sell_price_czk_kwh: float | None = None
|
||||
#: True = reg 108/109 na 0 (PRESERVE – Deye baterii nepoužívá)
|
||||
@@ -1220,6 +1222,8 @@ def _build_setpoints(mode: OperatingModeInfo, pi: asyncpg.Record | None) -> Cont
|
||||
hp_en = bool(pi["heat_pump_enabled"])
|
||||
tgt = pi["battery_soc_target_pct"]
|
||||
target_soc = int(round(float(tgt))) if tgt is not None else None
|
||||
pm_raw = pi.get("deye_physical_mode")
|
||||
pm: str | None = str(pm_raw).strip().upper() if pm_raw is not None else None
|
||||
sell_raw = pi.get("effective_sell_price")
|
||||
sell_f: float | None = float(sell_raw) if sell_raw is not None else None
|
||||
return ControlSetpoints(
|
||||
@@ -1232,6 +1236,7 @@ def _build_setpoints(mode: OperatingModeInfo, pi: asyncpg.Record | None) -> Cont
|
||||
ev1_power_w=ev1_w,
|
||||
ev2_power_w=ev2_w,
|
||||
target_soc_pct=target_soc,
|
||||
deye_physical_mode=pm,
|
||||
effective_sell_price_czk_kwh=sell_f,
|
||||
)
|
||||
|
||||
@@ -1373,31 +1378,52 @@ def _deye_passive_tou_battery_soc_pct(
|
||||
return _clamp_deye_tou_soc_pct_hi(DEYE_TOU_SOC_PASSIVE_BATTERY_PRIORITY_PCT, hi=100)
|
||||
|
||||
|
||||
def _deye_zero_export_amps_for_passive(
|
||||
grid_w: int,
|
||||
bat_w: int,
|
||||
max_charge_a: int,
|
||||
max_discharge_a: int,
|
||||
) -> tuple[int, int]:
|
||||
"""
|
||||
PASSIVE (zero export k CT/zátěži, reg. 142 dle DB): výchozí plné 108/109.
|
||||
|
||||
- Export v plánu (grid_w < 0) a žádné plánované vybíjení (bat_w >= 0): **108 = 0** — nepřebírat
|
||||
přebytek FVE do baterie, ať může jít přetok do sítě.
|
||||
- Import v plánu (grid_w > 0) a žádné plánované nabíjení (bat_w <= 0): **109 = 0** — nevybíjet
|
||||
baterii, odběr ze sítě.
|
||||
"""
|
||||
if grid_w < 0 and bat_w >= 0:
|
||||
return 0, max_discharge_a
|
||||
if grid_w > 0 and bat_w <= 0:
|
||||
return max_charge_a, 0
|
||||
return max_charge_a, max_discharge_a
|
||||
|
||||
|
||||
def get_deye_mode(setpoints: ControlSetpoints) -> str:
|
||||
"""
|
||||
Fyzický režim Deye: SELL | CHARGE | PASSIVE.
|
||||
|
||||
Pravidlo držet jednoduché (viz ``docs/04-modules/operating-modes.md``):
|
||||
Primárně explicitně z plánu (`setpoints.deye_physical_mode`), fallback jen ze znamének (viz
|
||||
``docs/04-modules/operating-modes.md``):
|
||||
|
||||
- **SELL** — jen když plán explicitně počítá s **vybíjením baterie do sítě** ve smyslu:
|
||||
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).
|
||||
- **CHARGE** — ``battery_w`` > 0 **a** ``grid_setpoint_w`` > 0 (nabíjení ze sítě + import v plánu).
|
||||
- **SELL** — ``grid_setpoint_w`` < 0 **a** ``battery_w`` < 0 (export + vybíjení baterie v plánu).
|
||||
- **PASSIVE** (ZERO) — vše ostatní; reg. **108/109** dle ``_deye_zero_export_amps_for_passive``.
|
||||
|
||||
- **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; **108/109 max** stejně jako u běžného
|
||||
PASSIVE v ``write_inverter_setpoints`` (viz ``self_sustain_local_use`` pro TOU SOC).
|
||||
``battery_w=None`` (SELF_SUSTAIN) → bat_w 0 → typicky PASSIVE; v ``write_inverter_setpoints`` má
|
||||
SELF_SUSTAIN vlastní větev (108/109 max).
|
||||
"""
|
||||
pm = (setpoints.deye_physical_mode or "").strip().upper()
|
||||
if pm in {"PASSIVE", "SELL", "CHARGE"}:
|
||||
return pm
|
||||
|
||||
grid_w = int(setpoints.grid_setpoint_w or 0)
|
||||
bat_w = 0 if setpoints.battery_w is None else int(setpoints.battery_w)
|
||||
|
||||
if bat_w > 500 and grid_w > 200:
|
||||
if bat_w > 0 and grid_w > 0:
|
||||
return "CHARGE"
|
||||
|
||||
if grid_w < 0 and bat_w < 0 and abs(bat_w) >= abs(grid_w):
|
||||
if grid_w < 0 and bat_w < 0:
|
||||
return "SELL"
|
||||
|
||||
return "PASSIVE"
|
||||
@@ -1475,18 +1501,19 @@ async def write_inverter_setpoints(
|
||||
charge_a = int(inv.max_charge_a)
|
||||
discharge_a = int(inv.max_discharge_a)
|
||||
else:
|
||||
# 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)
|
||||
# PASSIVE (ZERO): výchozí plné 108/109; u přetoku FVE do sítě nebo importu bez baterie viz helper.
|
||||
charge_a, discharge_a = _deye_zero_export_amps_for_passive(
|
||||
grid_w,
|
||||
bat_w,
|
||||
int(inv.max_charge_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
|
||||
solar_sell = 1
|
||||
export_limit = export_lim
|
||||
if deye_mode == "SELL" and grid_w < -200:
|
||||
if deye_mode == "SELL" and grid_w < 0:
|
||||
export_limit = min(export_lim, max(REG143_SELL_CAP_MIN_W, abs(grid_w)))
|
||||
reg178_val = REG178_SELL if deye_mode == "SELL" else REG178_PASSIVE
|
||||
|
||||
@@ -1875,15 +1902,15 @@ async def export_setpoints(site_id: int, db: asyncpg.Connection) -> None:
|
||||
|
||||
if mode.mode_code == "CHARGE_CHEAP":
|
||||
max_ch = await _fetch_max_charge_power_w(site_id, db)
|
||||
# Kladný grid_setpoint_w > 200 → fyzický CHARGE (nabíjení ze sítě), viz get_deye_mode
|
||||
grid_for_charge = max(300, max_ch)
|
||||
# Oba setpointy kladné → get_deye_mode CHARGE; min. 1 W, aby režim nebyl PASSIVE při nulové DB.
|
||||
pw = max(1, int(max_ch))
|
||||
sp_now = ControlSetpoints(
|
||||
battery_w=max_ch,
|
||||
battery_w=pw,
|
||||
grid_export_limit=0,
|
||||
ev1_current_a=0,
|
||||
ev2_current_a=0,
|
||||
heat_pump_enable=False,
|
||||
grid_setpoint_w=grid_for_charge,
|
||||
grid_setpoint_w=pw,
|
||||
ev1_power_w=0,
|
||||
ev2_power_w=0,
|
||||
target_soc_pct=None,
|
||||
|
||||
Reference in New Issue
Block a user