refactor control output writers
This commit is contained in:
@@ -3,15 +3,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from collections import defaultdict
|
||||
from typing import Any
|
||||
from datetime import datetime, timezone
|
||||
|
||||
import asyncpg
|
||||
import httpx
|
||||
|
||||
from app.config import get_settings
|
||||
from services.control.deye_helpers import (
|
||||
BATT_VOLTAGE_V,
|
||||
DEYE_CLOCK_DRIFT_OK_SEC,
|
||||
@@ -55,6 +52,12 @@ from services.control.modbus_journal import (
|
||||
create_modbus_commands,
|
||||
execute_modbus_commands,
|
||||
)
|
||||
from services.control.outputs import (
|
||||
_current_limit_for_charger,
|
||||
send_loxone_setpoints,
|
||||
write_ev_setpoints,
|
||||
write_heat_pump_setpoint,
|
||||
)
|
||||
from services.control.repository import (
|
||||
_fetch_max_charge_power_w,
|
||||
_fetch_operating_mode,
|
||||
@@ -877,134 +880,6 @@ async def read_deye_registers_live(site_id: int, db: asyncpg.Connection) -> dict
|
||||
}
|
||||
|
||||
|
||||
def _current_limit_for_charger(charger_code: str, sp: ControlSetpoints) -> int:
|
||||
c = (charger_code or "").strip().lower()
|
||||
if c == "ev-charger-1":
|
||||
a = sp.ev1_current_a
|
||||
elif c == "ev-charger-2":
|
||||
a = sp.ev2_current_a
|
||||
elif c.endswith("-1") or c == "ev1":
|
||||
a = sp.ev1_current_a
|
||||
elif c.endswith("-2") or c == "ev2":
|
||||
a = sp.ev2_current_a
|
||||
else:
|
||||
a = 0
|
||||
if a < 6:
|
||||
a = 0
|
||||
return a
|
||||
|
||||
|
||||
async def write_ev_setpoints(site_id: int, setpoints: ControlSetpoints, db: asyncpg.Connection) -> str:
|
||||
rows = await db.fetch(
|
||||
"""
|
||||
SELECT ec.code, se.host, se.port, se.unit_id
|
||||
FROM ems.asset_ev_charger ec
|
||||
JOIN ems.site_endpoint se ON se.id = ec.endpoint_id
|
||||
WHERE ec.site_id = $1
|
||||
AND ec.schedulable = true
|
||||
AND se.enabled = true
|
||||
AND se.endpoint_type = 'modbus_tcp'
|
||||
ORDER BY ec.code
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
if not rows:
|
||||
return "OK EV: no schedulable chargers"
|
||||
|
||||
for row in rows:
|
||||
code = row["code"]
|
||||
current_a = _current_limit_for_charger(code, setpoints)
|
||||
logger.info(
|
||||
"EV setpoint [%s]: %sA (TODO: Modbus registers)",
|
||||
code,
|
||||
current_a,
|
||||
)
|
||||
return f"OK EV: logged {len(rows)} charger(s) (Modbus TODO)"
|
||||
|
||||
|
||||
async def write_heat_pump_setpoint(site_id: int, setpoints: ControlSetpoints, db: asyncpg.Connection) -> str:
|
||||
rows = await db.fetch(
|
||||
"""
|
||||
SELECT hp.code, se.host, se.port, se.unit_id
|
||||
FROM ems.asset_heat_pump hp
|
||||
JOIN ems.site_endpoint se ON se.id = hp.endpoint_id
|
||||
WHERE hp.site_id = $1
|
||||
AND hp.schedulable = true
|
||||
AND se.enabled = true
|
||||
AND se.endpoint_type = 'modbus_tcp'
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
if not rows:
|
||||
return "OK heat pump: no schedulable unit"
|
||||
for row in rows:
|
||||
logger.info(
|
||||
"HP setpoint [%s]: enable=%s (TODO: Modbus registers)",
|
||||
row["code"],
|
||||
setpoints.heat_pump_enable,
|
||||
)
|
||||
return "OK heat pump: logged (Modbus TODO)"
|
||||
|
||||
|
||||
async def send_loxone_setpoints(
|
||||
site_id: int,
|
||||
setpoints: ControlSetpoints,
|
||||
mode: OperatingModeInfo,
|
||||
db: asyncpg.Connection,
|
||||
) -> str:
|
||||
endpoint = await db.fetchrow(
|
||||
"""
|
||||
SELECT host, port, protocol
|
||||
FROM ems.site_endpoint
|
||||
WHERE site_id = $1 AND endpoint_type = 'loxone_http' AND enabled = true
|
||||
ORDER BY id
|
||||
LIMIT 1
|
||||
""",
|
||||
site_id,
|
||||
)
|
||||
if not endpoint:
|
||||
return "OK Loxone: no endpoint, skipped"
|
||||
|
||||
proto = (endpoint["protocol"] or "http").lower()
|
||||
if proto not in ("http", "https"):
|
||||
proto = "http"
|
||||
host = endpoint["host"]
|
||||
port = int(endpoint["port"] or (443 if proto == "https" else 80))
|
||||
base = f"{proto}://{host}:{port}/dev/sps/io"
|
||||
|
||||
settings = get_settings()
|
||||
user = settings.loxone_user or os.getenv("LOXONE_USER") or ""
|
||||
password = settings.loxone_password or os.getenv("LOXONE_PASSWORD") or ""
|
||||
auth = (user, password) if user else None
|
||||
|
||||
batt_display = 0 if setpoints.battery_w is None else int(setpoints.battery_w)
|
||||
|
||||
paths: list[tuple[str, int]] = [
|
||||
(f"{base}/EMS_Mode/{mode.loxone_mode_value}", mode.loxone_mode_value),
|
||||
(f"{base}/EMS_Battery_Setpoint_W/{batt_display}", batt_display),
|
||||
(f"{base}/EMS_Grid_Setpoint_W/{setpoints.grid_setpoint_w}", setpoints.grid_setpoint_w),
|
||||
(f"{base}/EMS_EV1_Power_W/{setpoints.ev1_power_w}", setpoints.ev1_power_w),
|
||||
(f"{base}/EMS_EV2_Power_W/{setpoints.ev2_power_w}", setpoints.ev2_power_w),
|
||||
(f"{base}/EMS_HeatPump_Enable/{1 if setpoints.heat_pump_enable else 0}", 1 if setpoints.heat_pump_enable else 0),
|
||||
]
|
||||
|
||||
errs: list[str] = []
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||
for url, _ in paths:
|
||||
try:
|
||||
r = await client.get(url, auth=auth)
|
||||
r.raise_for_status()
|
||||
except Exception as e:
|
||||
errs.append(f"{url!s}: {e}")
|
||||
except Exception as e:
|
||||
return f"FAIL Loxone: client {e}"
|
||||
|
||||
if errs:
|
||||
return "FAIL Loxone: " + "; ".join(errs[:3])
|
||||
return "OK Loxone: all virtual inputs updated"
|
||||
|
||||
|
||||
async def export_setpoints(site_id: int, db: asyncpg.Connection) -> None:
|
||||
mode = await _fetch_operating_mode(site_id, db)
|
||||
if mode is None:
|
||||
|
||||
Reference in New Issue
Block a user