third version before modbus cleanup
This commit is contained in:
@@ -25,6 +25,10 @@ BATT_VOLTAGE_V = 51.2
|
||||
REG178_SELL = 0b00100000 # 32, grid peak shaving disable
|
||||
REG178_PASSIVE = 0b00110000 # 48, grid peak shaving enable (PASSIVE i CHARGE)
|
||||
|
||||
# Neaktivní TOU bloky (3–6): „konec dne“ — Deye často 23:59 (2359) neuloží a vrátí např. 2355,
|
||||
# verify pak hlásí mismatch. 23:55 je na zařízeních stabilní (viz HHMM jako desítkové číslo).
|
||||
DEYE_TOU_INACTIVE_HHMM = 2355
|
||||
|
||||
DEYE_REGISTER_NAMES: dict[int, str] = {
|
||||
108: "max_charge_a (max nabíjecí proud baterie)",
|
||||
109: "max_discharge_a (max vybíjecí proud baterie)",
|
||||
@@ -97,6 +101,7 @@ class InverterConfig:
|
||||
max_battery_charge_w: int | None
|
||||
max_battery_discharge_w: int | None
|
||||
reserve_soc_percent: int | None
|
||||
max_soc_percent: int | None
|
||||
usable_capacity_wh: int | None
|
||||
max_charge_a: int
|
||||
max_discharge_a: int
|
||||
@@ -195,13 +200,14 @@ async def execute_modbus_commands(
|
||||
)
|
||||
if cmd is None:
|
||||
continue
|
||||
client = await get_modbus_client(
|
||||
cmd["device_host"], int(cmd["device_port"]), int(cmd["device_unit_id"])
|
||||
)
|
||||
unit = int(cmd["device_unit_id"])
|
||||
client = await get_modbus_client(cmd["device_host"], int(cmd["device_port"]))
|
||||
for attempt in range(MAX_RETRIES):
|
||||
try:
|
||||
await client.write_registers(
|
||||
int(cmd["register"]), [int(cmd["value_to_write"])]
|
||||
int(cmd["register"]),
|
||||
[int(cmd["value_to_write"])],
|
||||
unit,
|
||||
)
|
||||
await db.execute(
|
||||
"""
|
||||
@@ -231,7 +237,7 @@ async def execute_modbus_commands(
|
||||
e,
|
||||
)
|
||||
await asyncio.sleep(RETRY_DELAY)
|
||||
client._client = None # force reconnect
|
||||
await client.force_disconnect()
|
||||
else:
|
||||
await db.execute(
|
||||
"""
|
||||
@@ -290,30 +296,31 @@ async def verify_modbus_commands(
|
||||
continue
|
||||
|
||||
try:
|
||||
client = await get_modbus_client(
|
||||
cmd["device_host"], int(cmd["device_port"]), int(cmd["device_unit_id"])
|
||||
)
|
||||
actual = await client.read_register(int(cmd["register"]))
|
||||
unit = int(cmd["device_unit_id"])
|
||||
client = await get_modbus_client(cmd["device_host"], int(cmd["device_port"]))
|
||||
actual = await client.read_register(int(cmd["register"]), unit)
|
||||
actual_i = int(actual)
|
||||
expected_i = int(cmd["value_to_write"])
|
||||
await db.execute(
|
||||
"""
|
||||
UPDATE ems.modbus_command
|
||||
SET value_verified=$1, verified_at=now(),
|
||||
status=CASE WHEN $1=$2 THEN 'verified' ELSE 'mismatch' END
|
||||
WHERE id=$3
|
||||
SET value_verified=$1::int, verified_at=now(),
|
||||
status=CASE WHEN $1::int = $2::int THEN 'verified' ELSE 'mismatch' END
|
||||
WHERE id=$3::int
|
||||
""",
|
||||
actual,
|
||||
int(cmd["value_to_write"]),
|
||||
actual_i,
|
||||
expected_i,
|
||||
cmd_id,
|
||||
)
|
||||
|
||||
if actual != int(cmd["value_to_write"]):
|
||||
if actual_i != expected_i:
|
||||
logger.error(
|
||||
"[cmd %s] MISMATCH %s 0x%04X: expected=%s actual=%s",
|
||||
cmd_id,
|
||||
cmd["asset_code"],
|
||||
int(cmd["register"]),
|
||||
cmd["value_to_write"],
|
||||
actual,
|
||||
expected_i,
|
||||
actual_i,
|
||||
)
|
||||
row_ac = await db.fetchrow(
|
||||
"SELECT attempt_count FROM ems.modbus_command WHERE id=$1", cmd_id
|
||||
@@ -323,8 +330,8 @@ async def verify_modbus_commands(
|
||||
cmd["asset_code"],
|
||||
int(cmd["register"]),
|
||||
cmd["register_name"] or "",
|
||||
int(cmd["value_to_write"]),
|
||||
actual,
|
||||
expected_i,
|
||||
actual_i,
|
||||
attempts,
|
||||
)
|
||||
|
||||
@@ -356,8 +363,8 @@ async def verify_modbus_commands(
|
||||
site["code"],
|
||||
(
|
||||
f"Modbus mismatch: {cmd['asset_code']} "
|
||||
f"0x{cmd['register']:04X} expected={cmd['value_to_write']} "
|
||||
f"actual={actual}"
|
||||
f"0x{cmd['register']:04X} expected={expected_i} "
|
||||
f"actual={actual_i}"
|
||||
),
|
||||
)
|
||||
all_ok = False
|
||||
@@ -367,7 +374,7 @@ async def verify_modbus_commands(
|
||||
cmd_id,
|
||||
cmd["asset_code"],
|
||||
int(cmd["register"]),
|
||||
actual,
|
||||
actual_i,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error("[cmd %s] verify read failed: %s", cmd_id, e)
|
||||
@@ -436,6 +443,7 @@ async def _load_inverter_config(
|
||||
ai.max_battery_charge_w,
|
||||
ai.max_battery_discharge_w,
|
||||
ab.reserve_soc_percent,
|
||||
ab.max_soc_percent,
|
||||
ab.usable_capacity_wh,
|
||||
LEAST(
|
||||
COALESCE(ab.bms_max_charge_w, ai.max_battery_charge_w),
|
||||
@@ -489,6 +497,9 @@ async def _load_inverter_config(
|
||||
reserve_soc_percent=int(row["reserve_soc_percent"])
|
||||
if row["reserve_soc_percent"] is not None
|
||||
else None,
|
||||
max_soc_percent=int(row["max_soc_percent"])
|
||||
if row["max_soc_percent"] is not None
|
||||
else None,
|
||||
usable_capacity_wh=int(row["usable_capacity_wh"])
|
||||
if row["usable_capacity_wh"] is not None
|
||||
else None,
|
||||
@@ -729,7 +740,8 @@ def _deye_tou_params(
|
||||
if deye_mode == "CHARGE":
|
||||
raw_bat = setpoints.battery_w
|
||||
battery_w = int(raw_bat) if raw_bat is not None else 0
|
||||
target_soc = min(95, setpoints.target_soc_pct or 80)
|
||||
cap = int(inv.max_soc_percent) if inv.max_soc_percent is not None else 95
|
||||
target_soc = max(10, min(95, cap))
|
||||
tp_charge_w = battery_watts_to_amps(battery_w, inv.max_charge_a) * int(BATT_VOLTAGE_V)
|
||||
return tp_charge_w, target_soc, True
|
||||
return tp_discharge_w, reserve_soc, False
|
||||
@@ -798,7 +810,7 @@ async def write_inverter_setpoints(
|
||||
for idx in range(2, 6):
|
||||
registers.extend(
|
||||
_deye_time_point_rows(
|
||||
idx, 2359, tp_discharge_w, reserve_soc, False
|
||||
idx, DEYE_TOU_INACTIVE_HHMM, tp_discharge_w, reserve_soc, False
|
||||
)
|
||||
)
|
||||
|
||||
@@ -857,21 +869,26 @@ 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).
|
||||
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.
|
||||
"""
|
||||
inv = await _load_inverter_config(site_id, db)
|
||||
if inv is None:
|
||||
raise ValueError("no controllable Modbus inverter for site")
|
||||
|
||||
client = await get_modbus_client(inv.host, inv.port, inv.unit_id)
|
||||
uid = int(inv.unit_id)
|
||||
client = await get_modbus_client(inv.host, inv.port)
|
||||
read_at = datetime.now(timezone.utc)
|
||||
try:
|
||||
r108 = await client.read_register(108)
|
||||
r109 = await client.read_register(109)
|
||||
r141 = await client.read_register(141)
|
||||
r142 = await client.read_register(142)
|
||||
r143 = await client.read_register(143)
|
||||
r178 = await client.read_register(178)
|
||||
r191 = await client.read_register(191)
|
||||
async with client.batch(uid) as mb:
|
||||
b108 = await mb.read_holding_registers(108, 2)
|
||||
b141 = await mb.read_holding_registers(141, 3)
|
||||
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]
|
||||
r178 = r178[0]
|
||||
r191 = r191[0]
|
||||
except Exception:
|
||||
logger.exception("read_deye_registers_live site=%s failed", site_id)
|
||||
raise
|
||||
|
||||
Reference in New Issue
Block a user