From 4c1f2e19918521ae13e30f751c95c9a95ec0d1e4 Mon Sep 17 00:00:00 2001 From: Bastian de Byl Date: Mon, 18 May 2026 14:39:13 -0400 Subject: [PATCH] feat: S10 - udclim RX on 0x212 bytes 1-2 for VCU-driven force-stop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix B on the VCU side (b24864e) opens HV contactors when BMS reports cell voltage above the user's ChargeLimit_pct target. That isolates the pack but doesn't tell the OBC modules to stop drawing AC. Result: ~0.55 kW continuous EVSE draw dissipated as conversion loss in the OBC, water pumps stay engaged, app reports "Charging <1 kW" indefinitely. Root cause: ChargerStateMachine's EVSEACTIVATE state has only four exits (CheckVoltage udc>udclim, CheckTimeout, CheckUnplugged, CheckChargerFaults). With contactors open the VCU can't drive pack voltage above udclim, so none of the exits fire — state machine wedges. Fix: make udclim CAN-driven on 0x212 bytes 1-2 (uint16 LE, range 50-420V). The VCU normally sends 398 V (no change in behavior). When VCU is in CHARGE_COMPLETE state it drops to 200 V — well below any realistic pack voltage — which fires the existing CheckVoltage() path within 1 second (10 × 100 ms ticks) and transitions EVSEACTIVATE → STOP → OFF. OBC modules disable, AC draw drops to zero. Elegant because we reuse the existing CheckVoltage() logic instead of adding new state-machine code, new params, or new exit paths. Pre-S10 firmware silently ignored bytes 1-2; pre-S10 VCU with S10 teslacharger leaves udclim at its persistent flash default (398V). Safe upgrade in both directions. Pool: 42 → 43 / 50 slots used, still comfortable headroom. Pairs with stm32-hal-vcu PR #47's matching dynamic-udclim TX commit. CAUTION: udclim updates here are RAM-only; running `save` while VCU is forcing 200V would persist that value and require canclear-and- rebuild to recover. Documented in chargercan.cpp comment block. #patch Co-Authored-By: Claude Opus 4.7 (1M context) --- include/param_prj.h | 7 ++++++- src/chargercan.cpp | 22 +++++++++++++++++----- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/include/param_prj.h b/include/param_prj.h index 01bd5cd..cd565e6 100644 --- a/include/param_prj.h +++ b/include/param_prj.h @@ -139,7 +139,12 @@ // -S9 restores 8 c2/c3 RX entries (uac/flag/idc/udc per module). -S8 over- // trimmed multi-module Tesla OBC support: CalcTotals was summing only // module 1's contribution, so reported pack power was 1/3 of actual delivery. -#define VERSTR STRINGIFY(4=VER-S9) +// -S10 adds udclim RX on 0x212 bytes 1-2. With this, the VCU can force a +// hard stop by sending udclim=200V (below pack), which fires the existing +// ChargerStateMachine CheckVoltage() exit (udc > udclim → STOP). Closes the +// gap where Fix B's contactor-open left the OBC modules drawing ~0.55 kW AC +// indefinitely because EVSEACTIVATE had no other exit path. +#define VERSTR STRINGIFY(4=VER-S10) /***** enums ******/ diff --git a/src/chargercan.cpp b/src/chargercan.cpp index 96e381b..bb90cb4 100644 --- a/src/chargercan.cpp +++ b/src/chargercan.cpp @@ -100,12 +100,24 @@ void ChargerCAN::MapMessages(CanMap* can) can->AddSend(Param::aclim, 0x44c, 16, 16, 1500); can->AddSend(Param::opmode, 0x44c, 32, 8, 154, 100); - /***** SKUDAK VCU command RX (0x212) — SKUDAK-516 user ChargeLimit *****/ - // Byte 0: ChargeLimit_pct (0-100). Bytes 1-7 reserved 0x00. - // Param::Change(vcuchglim) translates % to udcspnt setpoint (see src/main.cpp). - // VCU broadcasts every 200 ms once teslacharger detected; charger holds last - // value if VCU goes silent. + /***** SKUDAK VCU command RX (0x212) — SKUDAK-516 user ChargeLimit + S10 stop *****/ + // Byte 0: ChargeLimit_pct (0-100). Param::Change(vcuchglim) translates + // % to udcspnt setpoint (see src/main.cpp). + // Bytes 1-2: udclim_V (uint16 LE, 50-420 V). SKUDAK-S10: VCU drives this + // dynamically so it can force-stop the OBC modules when its + // BMS-side SOC gate trips. Normally 398 V (= default udclim, no + // effect). On the VCU's CHARGE_COMPLETE state it drops to 200 V, + // which fires the existing ChargerStateMachine CheckVoltage() + // path (udc > udclim → STOP within 1 s). Pre-S10 firmware + // ignored these bytes; safe upgrade. NOTE: udclim updates here + // are RAM-only — `save` command WHILE the VCU is forcing 200 + // would persist that value and brick subsequent charging. Don't + // issue `save` mid-stop. + // Bytes 3-7: reserved 0x00. + // VCU broadcasts every 200 ms once teslacharger detected; charger holds + // last received values if VCU goes silent (held udclim defaults to 398 V). can->AddRecv(Param::vcuchglim, 0x212, 0, 8, 1); + can->AddRecv(Param::udclim, 0x212, 8, 16, 1); /***** SKUDAK VCU telemetry (0x211) — SKUDAK-448 expanded charger telemetry *****/ // Byte 0: state (0=Off, 1=WaitStart, 2=Enable, 3=Activate, 4=Run, 5=Stop)