EV řízení: zápis Amps-to-use přes journal + watchdog + okamžitý replan po příjezdu
Bod 1 — write_ev_setpoints reálně (konec TODO stubu):
- reg 15 (0=stop, 6–32 A) z plánu přes _current_limit_for_charger; plná
journal pipeline (create_modbus_commands → execute, verify job 2 min generic)
- watchdog reg 19=300 s + reg 20=8 A: výpadek EMS → wallbox po 5 min failsafe
8 A (auto se přes noc nabije); drop-unchanged → zapisuje se jen při změně
- fn_modbus_last_verified_map: + p_asset_type (drop 2-arg; dosud hardcoded
'inverter' — pro chargery vracela {})
- verify: SELF_SUSTAIN fallback explicitně jen pro asset_type='inverter' —
mismatch wallboxu nesmí degradovat režim celé site
- journal register_name: mimo inverter platí jméno od volajícího
Bod 2 — telemetry_collector: přechod available→connected spustí fire-and-forget
run_rolling_replan(triggered_by=ev_arrival:<code>) + export_setpoints přes BG
pool — reakce na příjezd ~60 s místo až 15 min.
Bod 3 (Tesla API SoC) čeká na developer credentials.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,39 @@ from services.modbus_client import get_modbus_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
#: Pool pro fire-and-forget akce mimo hlavní poll spojení (např. replan po
|
||||
#: příjezdu EV). Nastavuje run_telemetry_loop_wrapper.
|
||||
_BG_POOL: asyncpg.Pool | None = None
|
||||
|
||||
|
||||
async def _on_ev_arrival(site_id: int, charger_code: str) -> None:
|
||||
"""Okamžitý replan + export po příjezdu EV (jinak by se čekalo až na */15).
|
||||
|
||||
Deferred importy: planning_engine/control_exporter importují control balík,
|
||||
který se importuje nezávisle — vyhýbáme se importnímu cyklu při startu.
|
||||
"""
|
||||
if _BG_POOL is None:
|
||||
logger.warning("EV arrival: BG pool není k dispozici, čeká se na rolling tick")
|
||||
return
|
||||
try:
|
||||
from services.control_exporter import export_setpoints
|
||||
from services.planning_engine import run_rolling_replan
|
||||
|
||||
async with _BG_POOL.acquire() as conn:
|
||||
await run_rolling_replan(
|
||||
site_id, conn, triggered_by=f"ev_arrival:{charger_code}"
|
||||
)
|
||||
await export_setpoints(site_id, conn)
|
||||
logger.info(
|
||||
"EV arrival replan+export done (site=%s, charger=%s)",
|
||||
site_id,
|
||||
charger_code,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"EV arrival replan failed (site=%s, charger=%s)", site_id, charger_code
|
||||
)
|
||||
|
||||
# Deye SUN – holding registry (decimal adresa = přímo pro read_holding_registers)
|
||||
DEYE_REG_RUN_STATE = 500
|
||||
DEYE_REG_BATT_CHARGE_TODAY = 514
|
||||
@@ -254,6 +287,7 @@ async def poll_ev_chargers(site_id: int, db: asyncpg.Connection) -> None:
|
||||
)
|
||||
if previous_status == "available" and current_status != "available":
|
||||
logger.info("EV arrival detected on charger %s", code)
|
||||
asyncio.create_task(_on_ev_arrival(site_id, str(code)))
|
||||
elif previous_status != "available" and current_status == "available":
|
||||
logger.info("EV departure detected on charger %s", code)
|
||||
|
||||
@@ -307,6 +341,8 @@ async def run_telemetry_loop(conn: asyncpg.Connection) -> float:
|
||||
|
||||
async def run_telemetry_loop_wrapper(pool: asyncpg.Pool) -> None:
|
||||
"""Background task: každá iterace získá spojení z poolu; neblokuje pool během sleep."""
|
||||
global _BG_POOL
|
||||
_BG_POOL = pool
|
||||
while True:
|
||||
try:
|
||||
async with pool.acquire() as conn:
|
||||
|
||||
Reference in New Issue
Block a user