Implement telemetry enhancements: add reading of Deye registers 145 and 179 in telemetry collector to derive is_export_limited and pv_derating_flags. Update fn_telemetry_inverter_sample to store these flags, and adjust related documentation and API endpoints accordingly.
Some checks failed
CI and deploy / migration-check (push) Failing after 19s
CI and deploy / deploy (push) Has been skipped

This commit is contained in:
Dusan Vojacek
2026-04-22 23:02:14 +02:00
parent 1dfab8c7a1
commit c928e2234d
6 changed files with 74 additions and 15 deletions

View File

@@ -28,6 +28,9 @@ DEYE_REG_GRID_EXPORT_TOTAL_LO = 524
DEYE_REG_GRID_EXPORT_TOTAL_HI = 525
DEYE_REG_PV1_POWER = 672
DEYE_REG_PV2_POWER = 673
# Solar sell (0 = přebytek řiditelné FVE nesmí do sítě) a GEN/MI cut-off (bits01 == 2 → cut-off ON); viz modbus-registers.md
DEYE_REG_SOLAR_SELL = 145
DEYE_REG_CONTROL_BOARD_SPECIAL1 = 179
def aggregate_pv_production_w(pv1_w: int, pv2_w: int, gen_port_w: int) -> int:
@@ -38,6 +41,18 @@ def aggregate_pv_production_w(pv1_w: int, pv2_w: int, gen_port_w: int) -> int:
return max(0, int(pv1_w)) + max(0, int(pv2_w)) + max(0, int(gen_port_w))
def _export_limit_flags_from_deye_regs(reg145: int | None, reg179: int | None) -> tuple[bool | None, int | None]:
"""Odvoď is_export_limited / pv_derating_flags z přečtených holding registrů (NULL = neznámé)."""
if reg145 is None and reg179 is None:
return None, None
flags = 0
if reg145 is not None and int(reg145) == 0:
flags |= 1
if reg179 is not None and (int(reg179) & 3) == 2:
flags |= 2
return (flags != 0), flags
async def poll_inverter(site_id: int, db: asyncpg.Connection) -> None:
rows = await db.fetch(
"""
@@ -70,14 +85,17 @@ async def poll_inverter(site_id: int, db: asyncpg.Connection) -> None:
grid_energy_regs = await mb.read_holding_registers(
DEYE_REG_GRID_IMPORT_TOTAL_LO, 4
)
reg145 = await mb.read_register(DEYE_REG_SOLAR_SELL)
reg179 = await mb.read_register(DEYE_REG_CONTROL_BOARD_SPECIAL1)
pv_power_w = aggregate_pv_production_w(pv1_power, pv2_power, gen_port_power)
grid_import_total_wh = (grid_energy_regs[1] << 16 | grid_energy_regs[0]) * 100
grid_export_total_wh = (grid_energy_regs[3] << 16 | grid_energy_regs[2]) * 100
is_export_limited, pv_derating_flags = _export_limit_flags_from_deye_regs(reg145, reg179)
logger.debug("inverter:%s Deye run_state raw=%s", code, run_state)
await db.execute(
"select ems.fn_telemetry_inverter_sample($1::int, $2::int, $3::timestamptz, $4::int, $5::int, $6::int, $7::int, $8::float8, $9::int, $10::int, $11::int, $12::int, $13::int, $14::bigint, $15::bigint, $16::int)",
"select ems.fn_telemetry_inverter_sample($1::int, $2::int, $3::timestamptz, $4::int, $5::int, $6::int, $7::int, $8::float8, $9::int, $10::int, $11::int, $12::int, $13::int, $14::bigint, $15::bigint, $16::int, $17::boolean, $18::int)",
site_id,
inv_id,
measured_at,
@@ -94,6 +112,8 @@ async def poll_inverter(site_id: int, db: asyncpg.Connection) -> None:
grid_import_total_wh,
grid_export_total_wh,
run_state,
is_export_limited,
pv_derating_flags,
)
inv_temp: float | None = None
await manager.broadcast_telemetry(
@@ -108,6 +128,8 @@ async def poll_inverter(site_id: int, db: asyncpg.Connection) -> None:
"load_power_w": load_power,
"gen_port_power_w": gen_port_power,
"inverter_temp_c": inv_temp,
"is_export_limited": is_export_limited,
"pv_derating_flags": pv_derating_flags,
}
)
except Exception as e:

View File

@@ -0,0 +1,28 @@
"""Logika is_export_limited / pv_derating_flags z Deye reg 145 a 179."""
from services.telemetry_collector import _export_limit_flags_from_deye_regs
def test_both_none_unknown() -> None:
lim, flags = _export_limit_flags_from_deye_regs(None, None)
assert lim is None and flags is None
def test_solar_sell_disabled() -> None:
lim, flags = _export_limit_flags_from_deye_regs(0, None)
assert lim is True and flags == 1
def test_solar_sell_enabled_only() -> None:
lim, flags = _export_limit_flags_from_deye_regs(1, None)
assert lim is False and flags == 0
def test_gen_mi_cutoff_bits() -> None:
lim, flags = _export_limit_flags_from_deye_regs(None, 2)
assert lim is True and flags == 2
def test_combined_flags() -> None:
lim, flags = _export_limit_flags_from_deye_regs(0, 2)
assert lim is True and flags == 3