refactor export limit semantics
Some checks failed
CI and deploy / migration-check (pull_request) Failing after 15s
CI and deploy / deploy (pull_request) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-05-03 22:24:35 +02:00
parent 349a15e96a
commit e8eb867a2a
16 changed files with 134 additions and 17 deletions

View File

@@ -19,9 +19,6 @@ DEYE_CLOCK_RESYNC_INTERVAL_HOURS = 24
# Deye LV baterie: převod výkon -> proud pro registry 108/109.
BATT_VOLTAGE_V = 51.2
# Reg 143 ve SELL: min(|grid_setpoint_w|, ...) nesmí klesnout pod tuto podlahu (W).
REG143_SELL_CAP_MIN_W = 200
# Reg 178 - bitové pole: bity 4-5 (peak shaving switch) a bity 0-1 (MI export cutoff).
REG178_SELL = 0b00100000
REG178_PASSIVE = 0b00110000

View File

@@ -13,7 +13,6 @@ from services.control.deye_helpers import (
DEYE_TOU_INACTIVE_HHMM,
DEYE_TOU_POWER_REGS,
PRAGUE_TZ,
REG143_SELL_CAP_MIN_W,
REG178_MI_EXPORT_DISABLE,
REG178_MI_EXPORT_ENABLE,
REG178_MI_EXPORT_MASK,

View File

@@ -15,7 +15,6 @@ from services.control.deye_helpers import (
DEYE_CLOCK_RESYNC_INTERVAL_HOURS,
DEYE_TOU_INACTIVE_HHMM,
PRAGUE_TZ,
REG143_SELL_CAP_MIN_W,
REG178_MI_EXPORT_DISABLE,
REG178_MI_EXPORT_ENABLE,
REG178_MI_EXPORT_MASK,
@@ -103,8 +102,6 @@ async def write_inverter_setpoints(
selling_mode = 0 if deye_mode == "SELL" else zero_exp_mode
solar_sell = 0 if (setpoints_now.export_ban and deye_mode != "SELL") else 1
export_limit = export_lim
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
logger.info(

View File

@@ -39,6 +39,7 @@ class InverterConfig:
@dataclass
class ControlSetpoints:
battery_w: int | None
#: Tvrdý limit exportu do sítě v daném slotu (W), ne forecastová cílová hodnota.
grid_export_limit: int
ev1_current_a: int
ev2_current_a: int

View File

@@ -81,6 +81,8 @@ def _build_setpoints(
if pi is None:
return None
grid_sp = int(pi["grid_setpoint_w"] or 0)
export_limit_raw = pi.get("export_limit_w")
export_limit = int(export_limit_raw) if export_limit_raw is not None else abs(min(grid_sp, 0))
ev1_w = int(pi["ev1_setpoint_w"] or 0) if "ev1_setpoint_w" in pi else 0
ev2_w = int(pi["ev2_setpoint_w"] or 0) if "ev2_setpoint_w" in pi else 0
hp_en = bool(pi["heat_pump_enabled"])
@@ -90,6 +92,10 @@ def _build_setpoints(
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
export_mode_raw = pi.get("export_mode")
export_mode = str(export_mode_raw).strip().upper() if export_mode_raw is not None else None
if export_mode == "NONE":
export_limit = 0
# Záporný výkup sám o sobě neblokuje export, pokud plán export explicitně žádá.
export_ban = sell_f is not None and float(sell_f) < 0 and grid_sp >= 0
gen_cutoff_raw = pi.get("deye_gen_cutoff_enabled")
@@ -112,7 +118,7 @@ def _build_setpoints(
pv_a_allowed = 0
return ControlSetpoints(
battery_w=int(pi["battery_setpoint_w"] or 0),
grid_export_limit=abs(min(grid_sp, 0)),
grid_export_limit=max(0, export_limit),
ev1_current_a=watts_to_amps(ev1_w, phases=3),
ev2_current_a=watts_to_amps(ev2_w, phases=1),
heat_pump_enable=hp_en,