2 Commits

Author SHA1 Message Date
d41a9dfe10 feat(safety): remote e-stop over 4G MQTT (Issue #63)
STM32 firmware:
- safety.h/c: EstopSource enum, safety_remote_estop/clear/get/active()
  CDC 'E'=ESTOP_REMOTE, 'F'=ESTOP_CELLULAR_TIMEOUT, 'Z'=clear latch
- usbd_cdc_if: cdc_estop_request/cdc_estop_clear_request volatile flags
- status: status_update() +remote_estop param; both LEDs fast-blink 200ms
- main.c: immediate motor cutoff highest-priority; arming gated by
  !safety_remote_estop_active(); motor estop auto-clear gated; telemetry
  'es' field 0-4; status_update() updated to 5 args

Safety: IMMEDIATE motor cutoff, latched until explicit Z + DISARMED,
cannot re-arm via MQTT alone (requires RC arm hold). IWDG-safe.

Jetson bridge:
- remote_estop_node.py: paho-mqtt + pyserial, cellular watchdog 5s
- estop_params.yaml, remote_estop.launch.py
- setup.py / package.xml: register node + paho-mqtt dep
- docker-compose.yml: remote-estop service
- test_remote_estop.py: kill/clear/watchdog/latency unit tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 04:55:54 -05:00
4dd52b47dc feat(safety): IWDG watchdog, arm hold interlock, tilt alert (bd-3qh)
Safety systems implementation:

IWDG Hardware Watchdog (50ms timeout, config.h WATCHDOG_TIMEOUT_MS):
- safety_init() configures IWDG at PSC/32 (0.8ms tick), reload=62
- safety_refresh() must be called every loop iteration
- Cannot be disabled once started — MCU resets if loop hangs
- Started after 3s USB init delay (avoids spurious startup reset)

Arm Hold Interlock (3s, config.h ARMING_HOLD_MS):
- Arm command starts a hold timer, not immediate motor enable
- Motors only enable after ARMING_HOLD_MS consecutive hold
- Disarm or tilt > 10° cancels pending arm
- Prevents accidental arm from single keypress

Tilt Fault Alert:
- safety_alert_tilt_fault() fires one-shot buzzer on TILT_FAULT edge
- Rider hears alarm when tilt cutoff triggers
- Edge-detected (buzzer only fires once per fault event)

RC Timeout (infrastructure):
- safety_rc_alive() checks crsf_state.last_rx_ms vs RC_TIMEOUT_MS
- RC disarm wired but guarded (no CRSF yet) — remove guard when wired
- Compatible with future CRSF implementation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 13:11:43 -05:00