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,32 @@ from services.control.models import ControlSetpoints, OperatingModeInfo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Teltonika TeltoCharge – zápisové registry (oficiální protokol rev 0.5;
|
||||
# docs/04-modules/modbus-registers-teltocharge.md). FC 16 přes journal.
|
||||
TELTO_REG_AMPS_TO_USE = 15 # 0 = stop, 6–32 A
|
||||
TELTO_REG_COMM_TIMEOUT_S = 19 # watchdog: bez komunikace → failsafe
|
||||
TELTO_REG_FAILSAFE_CURRENT_A = 20
|
||||
#: Výpadek EMS: po 5 min bez zápisu wallbox přejde na failsafe proud —
|
||||
#: auto se přes noc nabije i bez EMS (pomalu), místo aby stálo na 0 A.
|
||||
TELTO_WATCHDOG_TIMEOUT_S = 300
|
||||
TELTO_WATCHDOG_FAILSAFE_A = 8
|
||||
|
||||
|
||||
def _telto_setpoint_registers(current_a: int) -> list[tuple[int, str, int]]:
|
||||
"""Registry pro jeden export tick: limit proudu + watchdog konfigurace.
|
||||
|
||||
Watchdog (19/20) se posílá s každým tickem, ale journal drop-unchanged ho
|
||||
po prvním verified zápisu přeskakuje — reálně se zapíše jednou.
|
||||
"""
|
||||
a = int(current_a)
|
||||
if a < 6:
|
||||
a = 0
|
||||
return [
|
||||
(TELTO_REG_AMPS_TO_USE, "telto_amps_to_use", min(a, 32)),
|
||||
(TELTO_REG_COMM_TIMEOUT_S, "telto_comm_timeout_s", TELTO_WATCHDOG_TIMEOUT_S),
|
||||
(TELTO_REG_FAILSAFE_CURRENT_A, "telto_failsafe_a", TELTO_WATCHDOG_FAILSAFE_A),
|
||||
]
|
||||
|
||||
|
||||
def _current_limit_for_charger(charger_code: str, sp: ControlSetpoints) -> int:
|
||||
c = (charger_code or "").strip().lower()
|
||||
@@ -34,9 +60,16 @@ def _current_limit_for_charger(charger_code: str, sp: ControlSetpoints) -> int:
|
||||
async def write_ev_setpoints(
|
||||
site_id: int, setpoints: ControlSetpoints, db: asyncpg.Connection
|
||||
) -> str:
|
||||
from services.control.modbus_journal import (
|
||||
_drop_registers_matching_last_verified,
|
||||
_fetch_last_verified_registers,
|
||||
create_modbus_commands,
|
||||
execute_modbus_commands,
|
||||
)
|
||||
|
||||
rows = await db.fetch(
|
||||
"""
|
||||
SELECT ec.code, se.host, se.port, se.unit_id
|
||||
SELECT ec.id AS asset_id, 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
|
||||
@@ -50,15 +83,49 @@ async def write_ev_setpoints(
|
||||
if not rows:
|
||||
return "OK EV: no schedulable chargers"
|
||||
|
||||
written = 0
|
||||
for row in rows:
|
||||
code = row["code"]
|
||||
asset_id = int(row["asset_id"])
|
||||
host = str(row["host"])
|
||||
port = int(row["port"] or 502)
|
||||
unit_id = int(row["unit_id"] if row["unit_id"] is not None else 1)
|
||||
current_a = _current_limit_for_charger(code, setpoints)
|
||||
|
||||
registers = _telto_setpoint_registers(current_a)
|
||||
last_verified = await _fetch_last_verified_registers(
|
||||
site_id, asset_id, db, asset_type="ev_charger"
|
||||
)
|
||||
registers, skipped = _drop_registers_matching_last_verified(
|
||||
registers, last_verified
|
||||
)
|
||||
if not registers:
|
||||
logger.debug("EV setpoint [%s]: beze změny (%s A)", code, current_a)
|
||||
continue
|
||||
|
||||
cmd_ids = await create_modbus_commands(
|
||||
site_id,
|
||||
None,
|
||||
"ev_charger",
|
||||
asset_id,
|
||||
code,
|
||||
host,
|
||||
port,
|
||||
unit_id,
|
||||
registers,
|
||||
db,
|
||||
)
|
||||
ok = await execute_modbus_commands(cmd_ids, db)
|
||||
written += 1
|
||||
logger.info(
|
||||
"EV setpoint [%s]: %sA (TODO: Modbus registers)",
|
||||
"EV setpoint [%s]: %s A (regs %s%s) -> %s",
|
||||
code,
|
||||
current_a,
|
||||
[r for r, _, _ in registers],
|
||||
f", skip {skipped}" if skipped else "",
|
||||
"written" if ok else "FAILED",
|
||||
)
|
||||
return f"OK EV: logged {len(rows)} charger(s) (Modbus TODO)"
|
||||
return f"OK EV: {written}/{len(rows)} charger(s) written"
|
||||
|
||||
|
||||
async def write_heat_pump_setpoint(
|
||||
|
||||
Reference in New Issue
Block a user