fix: unbrick boot — revert -S6 Clear(), add reentrancy guard, -S7 #9

Open
bastian wants to merge 4 commits from fix/canmap-revert-clear-add-reentrancy-guard into main

4 Commits

Author SHA1 Message Date
Bastian de Byl d75027b17c feat: S11 - explicit VCU stop command on 0x212 byte 3 (replaces udclim hack)
Build Firmware / Build stm32-teslacharger (pull_request) Successful in 58s
The -S10 udclim=200V mechanism was a hack — VCU lowered udclim below
pack to fire CheckVoltage() udc>udclim and force STOP. It didn't work
reliably because once VCU opens HV contactors, the OBC per-module
voltage probes (c1udc/c2udc/c3udc) collapse below 200V and udc =
MAX(c1,c2,c3) never accumulates 10 ticks above udclim. Bench symptom:
VCU reaches CHARGE_COMPLETE, opens contactors, but teslacharger stays
in EVSEACTIVATE, OBC modules keep drawing ~0.5kW AC, water pumps run,
CP signal stays charging, EVSE doesn't shut down.

S11 adds an explicit vcustop byte on 0x212 byte 3 — VCU sets it to 1
when it wants charging stopped. ChargerStateMachine checks vcustop
FIRST in the EVSEACTIVATE case and transitions to STOP immediately.
Mirrors how the Dilong OBC obeys OBC_ControlCMD = Stopped.

Wire format (additive, no breaking change to S10):
  Byte 0:    vcuchglim_pct  (existing)
  Bytes 1-2: udclim_V       (existing, DEPRECATED — VCU sends 398V no-op)
  Byte 3:    vcustop        (NEW, 0=normal / 1=force stop)
  Bytes 4-7: reserved

Defensive: Param::Change(udclim) now clamps received <50V values to the
398V flash default. Protects against a pre-PR47 VCU sending all-zeros
in bytes 1-2 (which would otherwise make CheckVoltage() fire constantly
and brick charging).

After this VCU also flashed: the WPUMP issue resolves naturally because
VCU_IsCharging() reads OIOBC.State < ACTIVATE once teslacharger goes
to STOP. The VCU/teslacharger ownership becomes symmetric with the
Dilong path — VCU is authoritative for "should we charge."

Pool: 43 → 44 / 50 slots used.

#patch

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 15:39:36 -04:00
Bastian de Byl 4c1f2e1991 feat: S10 - udclim RX on 0x212 bytes 1-2 for VCU-driven force-stop
Build Firmware / Build stm32-teslacharger (pull_request) Successful in 58s
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) <noreply@anthropic.com>
2026-05-18 14:39:13 -04:00
Bastian de Byl c905e1236b feat: S8/S9 - trim chargercan.cpp + restore c2/c3 RX for multi-module Tesla OBC
Build Firmware / Build stm32-teslacharger (pull_request) Successful in 59s
S8 trimmed the 73-entry chargercan.cpp down to 37 entries to fit
libopeninv's 50-slot CANPOS pool — needed because the upstream firmware
silently overflowed the pool and dropped the SKUDAK 0x211 broadcast at
the tail. Without that broadcast the VCU couldn't see idc/udc/tmpobcmax.

S9 restores 8 of the entries S8 dropped: c2/c3 uac, flag, idc, udc.
These turn out to be NECESSARY for 3-module Tesla OBC operation:

- CalcTotals (charger.cpp:41/48) sums c1+c2+c3 idc and takes the max
  udc. Without c2/c3 idc mapped, idc reports only module 1's
  contribution. Bench observed 2.7 kW reported vs 9.7 kW actual EVSE
  draw — exactly the 1/3 ratio expected.
- CheckChargerFaults (charger.cpp:226-261) gates active2/active3
  detection on c2uac/c3uac > 70V. Without those mapped, the active
  bits short-circuit to false — which masked the c2flag/c3flag
  CheckAlive timeout. We were getting away with the trim by accident.

Kept dropped (still need pool headroom for future):
- c2/c3 iac (AC current — c1iac × 3 is a fine proxy, not used in
  aggregation anywhere)
- c2/c3 stt (state — all modules report same value for healthy operation)
- c2/c3 tmp1/2/in (temps — tmpobcmax from c1's 3 probes is sufficient;
  full 9-probe worst-case is a nice-to-have)
- CHAdeMO RX/TX (0x102/0x108/0x109) — Polarity VCU doesn't use CHAdeMO
- 0x368 idle frame — cosmetic only
- hwaclim duplicates on 0x209/0x20B — all modules report identical limits

Pool math: 42 / 50 slots used, 8-slot headroom for future additions.

VERSTR S8 → S9 so any bench reporting -S8 in the openinverter UI is
visibly the regressed build and needs canclear + reflash.

After flash: user must issue `canclear` from the openinverter Commands
tab so the persisted map gets rebuilt from the new chargercan.cpp.
Power-cycle, verify `can p` shows the 8 new c2/c3 lines, then EVSE
test should report ~9.5 kW in the app (was 2.7 kW).

#patch

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 14:07:19 -04:00
Bastian de Byl 23716a260b fix: unbrick boot — revert -S6 Clear(), add reentrancy guard, S7
Build Firmware / Build stm32-teslacharger (pull_request) Successful in 1m1s
PR #8 (-S6) caused chip boot fault: MapChargerMessages() uncommented
canMap->Clear(). Clear() ends with canHardware->ClearUserMessages(),
which fires the HandleClear callback (main.cpp:170-173) — and
HandleClear calls MapChargerMessages(). Recursive chain:

  main() -> MapChargerMessages
         -> Clear() -> ClearUserMessages -> HandleClear
                                         -> MapChargerMessages (re-entry)
                                            -> Clear() (recurse...)
                                            -> ... stack overflow -> HardFault

Bench symptom (Kyle): ESP8266 web UI stops responding after flashing
-S6. Recovery required re-flashing an older firmware (3-5 attempts).

This commit:
1. Re-commented canMap->Clear() inside MapChargerMessages — the safe
   path. The upgrade rebuild now requires a manual "canclear" via the
   openinverter terminal/web UI, which fires HandleClear once and
   triggers an additive MapMessages + Save.
2. Added a static `in_progress` reentrancy guard so any future
   accidental Clear() inside this function or its callees turns into
   a no-op re-entry instead of a chip-bricking fault.
3. Kept the canary as Param::tmpobcmax (from PR #8) — useful as a
   schema marker, just no longer auto-triggers a destructive rebuild.
4. Bumped VERSTR S5 -> S7 (skips S6 so any bench reporting "S6" in
   the openinverter UI is visibly the broken build and gets flashed).

Note: this does NOT solve the underlying "73 entries trying to fit in
50-slot CANPOS pool" issue — chargercan.cpp still has more AddRecv +
AddSend calls than MAX_ITEMS. Step 2 (reduce entries) is a separate
follow-up PR.

#patch

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 11:30:56 -04:00