tune microcycling
All checks were successful
deploy / deploy (push) Successful in 25s
test / smoke-test (push) Successful in 6s

This commit is contained in:
Dusan Vojacek
2026-04-13 00:49:36 +02:00
parent 3b33594354
commit fd06811753
10 changed files with 587 additions and 62 deletions

View File

@@ -62,8 +62,9 @@ DEYE_REGISTER_NAMES: dict[int, str] = {
108: "max_charge_a (max nabíjecí proud baterie)",
109: "max_discharge_a (max vybíjecí proud baterie)",
141: "energy_mode (0, EMS nemění)",
142: "limit_control (0=selling first, 1=zero export built-in CT)",
142: "limit_control (0=selling first, 1=zero export to load, 2=zero export to CT)",
143: "export_limit_w (max export do sítě)",
145: "solar_sell (0=disabled, 1=enabled)",
178: "grid_peak_shaving_switch (SELL=32 bit4-5=10, PASSIVE/CHARGE=48 bit4-5=11)",
148: "time_point_1_time",
149: "time_point_2_time",
@@ -152,6 +153,7 @@ class InverterConfig:
deye_last_system_time_sync_at: datetime | None = None
deye_last_tou_inactive_write_prague_date: date | None = None
deye_tou_inactive_signature: str | None = None
deye_zero_export_mode: int = 1
def _prague_minute_start_utc() -> datetime:
@@ -972,6 +974,7 @@ async def _load_inverter_config(
ai.deye_tou_inactive_signature,
ai.deye_register_max_charge_a,
ai.deye_register_max_discharge_a,
COALESCE(ai.deye_zero_export_mode, 1) AS deye_zero_export_mode,
LEAST(
COALESCE(ab.bms_max_charge_w, ai.max_battery_charge_w),
ai.max_battery_charge_w
@@ -1047,6 +1050,7 @@ async def _load_inverter_config(
"deye_last_tou_inactive_write_prague_date"
],
deye_tou_inactive_signature=row["deye_tou_inactive_signature"],
deye_zero_export_mode=int(row["deye_zero_export_mode"]),
)
@@ -1266,15 +1270,15 @@ def _deye_tou_reserve_soc_pct(inv: InverterConfig) -> int:
def get_deye_mode(setpoints: ControlSetpoints) -> str:
"""
Fyzický režim Deye: SELL | CHARGE | PASSIVE.
Solver: záporný grid_setpoint_w = export; kladný výrazný + nabíjení = CHARGE ze sítě.
battery_w=None (SELF_SUSTAIN) → bat_w považuj za 0 → typicky PASSIVE při grid_setpoint_w=0.
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.
"""
grid_w = int(setpoints.grid_setpoint_w or 0)
if setpoints.battery_w is None:
bat_w = 0
else:
bat_w = int(setpoints.battery_w)
if grid_w < -200:
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"
@@ -1339,18 +1343,20 @@ async def write_inverter_setpoints(
deye_mode = get_deye_mode(setpoints_now)
bat_w = int(raw_bat) if raw_bat is not None else 0
if setpoints_now.lock_battery:
charge_a = 0
discharge_a = 0
elif deye_mode == "CHARGE":
battery_w = int(raw_bat) if raw_bat is not None else 0
charge_a = battery_watts_to_amps(battery_w, eff_ca)
charge_a = battery_watts_to_amps(bat_w, eff_ca)
discharge_a = 0
else:
charge_a = int(eff_ca)
charge_a = int(eff_ca) if bat_w > 0 else 0
discharge_a = int(eff_da)
selling_mode = 0 if deye_mode == "SELL" else 1
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
reg178_val = REG178_SELL if deye_mode == "SELL" else REG178_PASSIVE
@@ -1358,8 +1364,7 @@ async def write_inverter_setpoints(
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={'0=SELL' if deye_mode == 'SELL' else '1=ZERO_EXP'} "
f"reg178={reg178_val}"
f"reg142={selling_mode} reg145={solar_sell} reg178={reg178_val}"
)
now_cet, time_rows = _deye_system_time_register_rows()
@@ -1430,20 +1435,23 @@ async def write_inverter_setpoints(
(108, "", charge_a),
(109, "", discharge_a),
(141, "energy_mode (0)", 0),
(142, "limit_control (0=selling, 1=zero_export)", selling_mode),
(178, "grid_peak_shaving_switch", reg178_val),
(142, "limit_control", selling_mode),
(143, "", export_limit),
(145, "solar_sell", solar_sell),
(178, "grid_peak_shaving_switch", reg178_val),
]
)
logger.info(
"[control] %s: deye_mode=%s charge=%sA discharge=%sA limit_control=%s export=%sW "
"time_point1=%s time_point2=%s soc_telemetry=%s%% (batt=%r grid=%sW)",
"[control] %s: deye_mode=%s charge=%sA discharge=%sA "
"reg142=%s reg145=%s export=%sW "
"tp1=%s tp2=%s soc=%s%% (batt=%r grid=%sW)",
inv.code,
deye_mode,
charge_a,
discharge_a,
selling_mode,
solar_sell,
export_limit,
hh_cur,
hh_nxt,
@@ -1541,7 +1549,7 @@ async def write_inverter_setpoints(
async def read_deye_registers_live(site_id: int, db: asyncpg.Connection) -> dict[str, Any]:
"""
Živé čtení holding registrů Deye 108, 109, 141, 142, 143, 178, 191 (stejné TCP spojení jako telemetrie/export).
Živé čtení holding registrů Deye 108, 109, 141145, 178, 191 (stejné TCP spojení jako telemetrie/export).
Vše pod jedním mutexem + sdružené FC3 bloky — mezi jednotlivými read_register dřív telemetrie
střídavě brala lock a RS485 brány házely cizí transaction_id / I/O timeouty.
"""
@@ -1555,11 +1563,12 @@ async def read_deye_registers_live(site_id: int, db: asyncpg.Connection) -> dict
try:
async with client.batch(uid) as mb:
b108 = await mb.read_holding_registers(108, 2)
b141 = await mb.read_holding_registers(141, 3)
b141 = await mb.read_holding_registers(141, 5)
r178 = await mb.read_holding_registers(178, 1)
r191 = await mb.read_holding_registers(191, 1)
r108, r109 = b108[0], b108[1]
r141, r142, r143 = b141[0], b141[1], b141[2]
r145 = b141[4]
r178 = r178[0]
r191 = r191[0]
except Exception:
@@ -1572,6 +1581,7 @@ async def read_deye_registers_live(site_id: int, db: asyncpg.Connection) -> dict
"reg141_energy_mode": int(r141),
"reg142_limit_control": int(r142),
"reg143_export_limit_w": int(r143),
"reg145_solar_sell": int(r145),
"reg178_peak_shaving_switch": int(r178),
"reg191_peak_shaving_w": int(r191),
"read_at": read_at.isoformat(),