prekopani SELL
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-04-19 22:48:51 +02:00
parent ee4355f17f
commit d8221e3169
7 changed files with 120 additions and 35 deletions

View File

@@ -32,6 +32,9 @@ 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.
REG143_SELL_CAP_MIN_W = 200
# Reg 178 pevné hodnoty (bit45); bez read-modify-write (kolize s Loxone / transaction ID)
REG178_SELL = 0b00100000 # 32, grid peak shaving disable
REG178_PASSIVE = 0b00110000 # 48, grid peak shaving enable (PASSIVE i CHARGE)
@@ -1374,18 +1377,30 @@ def get_deye_mode(setpoints: ControlSetpoints) -> str:
"""
Fyzický režim Deye: SELL | CHARGE | PASSIVE.
SELL only when battery actively discharges for grid export (bat_w < -500
AND grid_w < -200). Pass-through (PV → grid, battery idle) stays PASSIVE
with reg 108 = 0 + reg 145 = 1 (solar sell).
battery_w=None (SELF_SUSTAIN) → bat_w considered 0 → PASSIVE; při exportu se ale
zapíše plný reg. 108/109 (viz ``self_sustain_local_use`` v ``write_inverter_setpoints``).
Pravidlo držet jednoduché (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).
- **PASSIVE** — všude jinde: přetok FVE do sítě / do baterie řeší reg. **108** (nabíjení),
**109** (vybíjení, škálované podle plánu), **142** dle ``deye_zero_export_mode`` instalace,
**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).
``battery_w=None`` (SELF_SUSTAIN) → bat_w 0 → PASSIVE zde; plné 108/109 řeší
``self_sustain_local_use`` v ``write_inverter_setpoints``.
"""
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:
return "SELL"
if bat_w > 500 and grid_w > 200:
return "CHARGE"
if grid_w < 0 and bat_w < 0 and abs(bat_w) >= abs(grid_w):
return "SELL"
return "PASSIVE"
@@ -1451,26 +1466,40 @@ async def write_inverter_setpoints(
elif deye_mode == "CHARGE":
charge_a = battery_watts_to_amps(bat_w, inv.max_charge_a)
discharge_a = 0
elif deye_mode == "SELL":
# Záměrný výdej baterie do sítě: plný vybíjecí proud; export strop dle plánu níže.
charge_a = 0
discharge_a = int(inv.max_discharge_a)
elif setpoints_now.self_sustain_local_use:
# SELF_SUSTAIN: plný nabíjecí i vybíjecí proud invertoru — přebytek FVE jde do baterie,
# reg. 142 = zero export to load/CT (viz selling_mode níže), ne reg. 108 = 0.
charge_a = int(inv.max_charge_a)
discharge_a = int(inv.max_discharge_a)
else:
charge_a = int(inv.max_charge_a) if bat_w > 0 else 0
discharge_a = int(inv.max_discharge_a)
# PASSIVE: škáluj 108/109 podle |battery_w| — ne plný max při malém setpointu z LP.
if bat_w > 0:
charge_a = battery_watts_to_amps(bat_w, inv.max_charge_a)
discharge_a = 0
elif bat_w < 0:
charge_a = 0
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)
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:
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(
f"[control] site={site_id} fyzický režim Deye: {deye_mode} | "
f"battery_w={raw_bat!r} grid_w={grid_w} | "
f"charge_a={charge_a} discharge_a={discharge_a} | "
f"reg142={selling_mode} reg145={solar_sell} reg178={reg178_val}"
f"reg142={selling_mode} reg145={solar_sell} reg143={export_limit}W reg178={reg178_val}"
)
now_cet, time_rows = _deye_system_time_register_rows()