fix rizeni pole pres reg340 jen home01
This commit is contained in:
@@ -203,9 +203,10 @@ class InverterConfig:
|
||||
deye_tou_inactive_signature: str | None = None
|
||||
deye_zero_export_mode: int = 1
|
||||
deye_gen_microinverter_cutoff_enabled: bool = False
|
||||
manufacturer: str = ""
|
||||
#: Součet nominal_power_wp controllable PV na invertoru; 0 = EMS nezapisuje reg 340.
|
||||
pv_a_cap_w: int = 0
|
||||
#: True = EMS smí řídit Deye reg 340 (max solar power); z SQL `fn_site_has_active_green_bonus_pv(site_id)` — není DB sloupec na invertoru.
|
||||
deye_reg340_pv_a_control_enabled: bool = False
|
||||
|
||||
|
||||
def compute_pv_a_reg340_max_solar_w(cap_w: int, forecast_w: int, curtail_w: int) -> int:
|
||||
@@ -1075,7 +1076,6 @@ async def _load_inverter_config(
|
||||
"""
|
||||
SELECT
|
||||
ai.id, ai.code,
|
||||
coalesce(ai.manufacturer, '') AS manufacturer,
|
||||
coalesce(ems.fn_inverter_pv_a_max_w(ai.id), 0) AS pv_a_cap_w,
|
||||
se.host, se.port, se.unit_id,
|
||||
sgc.max_export_power_w,
|
||||
@@ -1093,6 +1093,8 @@ async def _load_inverter_config(
|
||||
ai.deye_tou_inactive_signature,
|
||||
COALESCE(ai.deye_zero_export_mode, 1) AS deye_zero_export_mode,
|
||||
COALESCE(ai.deye_gen_microinverter_cutoff_enabled, false) AS deye_gen_microinverter_cutoff_enabled,
|
||||
coalesce(ems.fn_site_has_active_green_bonus_pv(ai.site_id), false)
|
||||
AS deye_reg340_pv_a_control_enabled,
|
||||
COALESCE(
|
||||
ai.deye_register_max_charge_a,
|
||||
FLOOR(
|
||||
@@ -1177,8 +1179,10 @@ async def _load_inverter_config(
|
||||
deye_tou_inactive_signature=row["deye_tou_inactive_signature"],
|
||||
deye_zero_export_mode=int(row["deye_zero_export_mode"]),
|
||||
deye_gen_microinverter_cutoff_enabled=bool(row["deye_gen_microinverter_cutoff_enabled"] or False),
|
||||
manufacturer=str(row["manufacturer"] or ""),
|
||||
pv_a_cap_w=int(row["pv_a_cap_w"] or 0),
|
||||
deye_reg340_pv_a_control_enabled=bool(
|
||||
row["deye_reg340_pv_a_control_enabled"] or False
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -1262,7 +1266,7 @@ def _build_setpoints(
|
||||
pi: asyncpg.Record | None,
|
||||
*,
|
||||
pv_a_cap_w: int = 0,
|
||||
inverter_manufacturer: str = "",
|
||||
reg340_pv_a_control_enabled: bool = False,
|
||||
) -> ControlSetpoints | None:
|
||||
code = mode.mode_code
|
||||
if code == "MANUAL":
|
||||
@@ -1286,7 +1290,7 @@ def _build_setpoints(
|
||||
gen_cutoff_raw = pi.get("deye_gen_cutoff_enabled")
|
||||
gen_cutoff = bool(gen_cutoff_raw) if gen_cutoff_raw is not None else False
|
||||
pv_a_allowed: int | None = None
|
||||
if (inverter_manufacturer or "").strip().lower() == "deye" and int(pv_a_cap_w) > 0:
|
||||
if bool(reg340_pv_a_control_enabled) and int(pv_a_cap_w) > 0:
|
||||
forecast = int(pi.get("pv_a_forecast_solver_w") or 0)
|
||||
curtail = int(pi.get("pv_a_curtailed_w") or 0)
|
||||
pv_a_allowed = compute_pv_a_reg340_max_solar_w(int(pv_a_cap_w), forecast, curtail)
|
||||
@@ -1677,9 +1681,8 @@ async def write_inverter_setpoints(
|
||||
]
|
||||
)
|
||||
|
||||
mfr = (inv.manufacturer or "").strip().lower()
|
||||
if (
|
||||
mfr == "deye"
|
||||
bool(inv.deye_reg340_pv_a_control_enabled)
|
||||
and int(inv.pv_a_cap_w) > 0
|
||||
and setpoints_now.pv_a_allowed_w is not None
|
||||
):
|
||||
@@ -1826,7 +1829,8 @@ 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–145, 178, 191, 340 (stejné TCP spojení jako telemetrie/export).
|
||||
Živé čtení holding registrů Deye 108, 109, 141–145, 178, 191 a volitelně 340
|
||||
(jen pokud `deye_reg340_pv_a_control_enabled`, jinak `reg340_max_solar_power_w` = null).
|
||||
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.
|
||||
"""
|
||||
@@ -1843,13 +1847,20 @@ async def read_deye_registers_live(site_id: int, db: asyncpg.Connection) -> dict
|
||||
b141 = await mb.read_holding_registers(141, 5)
|
||||
r178 = await mb.read_holding_registers(178, 1)
|
||||
r191 = await mb.read_holding_registers(191, 1)
|
||||
r340 = await mb.read_holding_registers(340, 1)
|
||||
if inv.deye_reg340_pv_a_control_enabled:
|
||||
r340 = await mb.read_holding_registers(340, 1)
|
||||
else:
|
||||
r340 = None
|
||||
r108, r109 = b108[0], b108[1]
|
||||
r141, r142, r143 = b141[0], b141[1], b141[2]
|
||||
r145 = b141[4]
|
||||
r178 = r178[0]
|
||||
r191 = r191[0]
|
||||
r340v = int(r340[0]) if r340 and len(r340) >= 1 else 0
|
||||
r340v = (
|
||||
int(r340[0])
|
||||
if r340 is not None and len(r340) >= 1
|
||||
else None
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("read_deye_registers_live site=%s failed", site_id)
|
||||
raise
|
||||
@@ -1866,7 +1877,7 @@ async def read_deye_registers_live(site_id: int, db: asyncpg.Connection) -> dict
|
||||
"reg178_mi_export_cutoff_bits": int(r178) & int(REG178_MI_EXPORT_MASK),
|
||||
"reg178_mi_export_cutoff_is_on": (int(r178) & int(REG178_MI_EXPORT_MASK)) == int(REG178_MI_EXPORT_ENABLE),
|
||||
"reg191_peak_shaving_w": int(r191),
|
||||
"reg340_max_solar_power_w": int(r340v),
|
||||
"reg340_max_solar_power_w": r340v,
|
||||
"read_at": read_at.isoformat(),
|
||||
}
|
||||
|
||||
@@ -2012,14 +2023,24 @@ async def export_setpoints(site_id: int, db: asyncpg.Connection) -> None:
|
||||
try:
|
||||
inv_for_pv = await _load_inverter_config(site_id, db)
|
||||
cap_pv = int(inv_for_pv.pv_a_cap_w) if inv_for_pv is not None else 0
|
||||
mfr_pv = (inv_for_pv.manufacturer or "") if inv_for_pv is not None else ""
|
||||
reg340_en = (
|
||||
bool(inv_for_pv.deye_reg340_pv_a_control_enabled)
|
||||
if inv_for_pv is not None
|
||||
else False
|
||||
)
|
||||
pi_now = await _fetch_plan_row_for_slot_offset(site_id, db, 0)
|
||||
pi_next = await _fetch_plan_row_for_slot_offset(site_id, db, 1)
|
||||
sp_now = _build_setpoints(
|
||||
mode, pi_now, pv_a_cap_w=cap_pv, inverter_manufacturer=mfr_pv
|
||||
mode,
|
||||
pi_now,
|
||||
pv_a_cap_w=cap_pv,
|
||||
reg340_pv_a_control_enabled=reg340_en,
|
||||
)
|
||||
sp_next = _build_setpoints(
|
||||
mode, pi_next, pv_a_cap_w=cap_pv, inverter_manufacturer=mfr_pv
|
||||
mode,
|
||||
pi_next,
|
||||
pv_a_cap_w=cap_pv,
|
||||
reg340_pv_a_control_enabled=reg340_en,
|
||||
)
|
||||
|
||||
if mode.mode_code == "AUTO" and sp_now is None:
|
||||
|
||||
@@ -53,12 +53,12 @@ class ComputePvAReg340Tests(unittest.TestCase):
|
||||
|
||||
|
||||
class BuildSetpointsReg340Tests(unittest.TestCase):
|
||||
def test_deye_with_cap_sets_pv_a_allowed(self) -> None:
|
||||
def test_with_cap_sets_pv_a_allowed(self) -> None:
|
||||
sp = _build_setpoints(
|
||||
_auto_mode(),
|
||||
_pi_base(pv_a_forecast_solver_w=8000, pv_a_curtailed_w=2000),
|
||||
pv_a_cap_w=10_000,
|
||||
inverter_manufacturer="Deye",
|
||||
reg340_pv_a_control_enabled=True,
|
||||
)
|
||||
assert sp is not None
|
||||
self.assertEqual(sp.pv_a_allowed_w, 6000)
|
||||
@@ -68,17 +68,7 @@ class BuildSetpointsReg340Tests(unittest.TestCase):
|
||||
_auto_mode(),
|
||||
_pi_base(),
|
||||
pv_a_cap_w=0,
|
||||
inverter_manufacturer="Deye",
|
||||
)
|
||||
assert sp is not None
|
||||
self.assertIsNone(sp.pv_a_allowed_w)
|
||||
|
||||
def test_skipped_for_non_deye(self) -> None:
|
||||
sp = _build_setpoints(
|
||||
_auto_mode(),
|
||||
_pi_base(),
|
||||
pv_a_cap_w=10_000,
|
||||
inverter_manufacturer="Foo",
|
||||
reg340_pv_a_control_enabled=True,
|
||||
)
|
||||
assert sp is not None
|
||||
self.assertIsNone(sp.pv_a_allowed_w)
|
||||
@@ -92,9 +82,7 @@ class BuildSetpointsReg340Tests(unittest.TestCase):
|
||||
heat_pump_enabled_def=False,
|
||||
loxone_mode_value=0,
|
||||
)
|
||||
sp = _build_setpoints(
|
||||
mode, None, pv_a_cap_w=10_000, inverter_manufacturer="Deye"
|
||||
)
|
||||
sp = _build_setpoints(mode, None, pv_a_cap_w=10_000)
|
||||
assert sp is not None
|
||||
self.assertIsNone(sp.pv_a_allowed_w)
|
||||
|
||||
@@ -109,11 +97,21 @@ class BuildSetpointsReg340Tests(unittest.TestCase):
|
||||
pv_a_curtailed_w=0,
|
||||
),
|
||||
pv_a_cap_w=3333,
|
||||
inverter_manufacturer="Deye",
|
||||
reg340_pv_a_control_enabled=True,
|
||||
)
|
||||
assert sp is not None
|
||||
self.assertEqual(sp.pv_a_allowed_w, 0)
|
||||
|
||||
def test_skipped_when_reg340_control_disabled(self) -> None:
|
||||
sp = _build_setpoints(
|
||||
_auto_mode(),
|
||||
_pi_base(pv_a_forecast_solver_w=8000, pv_a_curtailed_w=2000),
|
||||
pv_a_cap_w=10_000,
|
||||
reg340_pv_a_control_enabled=False,
|
||||
)
|
||||
assert sp is not None
|
||||
self.assertIsNone(sp.pv_a_allowed_w)
|
||||
|
||||
|
||||
class Reg340VerifyPolicyTests(unittest.TestCase):
|
||||
def test_reg340_not_critical_for_self_sustain(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user