feat(safety): remote e-stop over 4G MQTT (Issue #63) #69

Merged
sl-jetson merged 1 commits from sl-firmware/remote-estop into main 2026-03-01 04:58:59 -05:00
Collaborator

Summary

Implements remote e-stop over 4G cellular MQTT — closes Issue #63 (P0 safety-critical).

STM32 Firmware

  • safety.h/c: EstopSource enum (ESTOP_CLEAR/TILT/RC_KILL/REMOTE/CELLULAR_TIMEOUT); safety_remote_estop(), safety_remote_estop_clear(), safety_get_estop(), safety_remote_estop_active() — latched until explicit Z clear + robot DISARMED
  • usbd_cdc_if.c/h: CDC commands E (ESTOP_REMOTE), F (ESTOP_CELLULAR_TIMEOUT), Z (clear latch)
  • status.h/c: status_update() +remote_estop param; both LEDs fast-blink 200ms (highest priority)
  • main.c: Immediate motor cutoff on E/F (before RC handling); arming blocked while safety_remote_estop_active(); motor estop auto-clear gated; "es" field in JSON telemetry (0–4); status_update() updated to 5-arg signature

Safety Guarantees

  • Motor cutoff is IMMEDIATE (bypasses PID, latches motors.estop)
  • Latch persists until explicit Z + robot must be DISARMED
  • Cannot re-arm via MQTT alone — requires RC arm hold after clear
  • IWDG-safe: no blocking code in e-stop path

Jetson Bridge

  • remote_estop_node.py: paho-mqtt subscriber + pyserial writer; cellular watchdog fires F\n after 5s MQTT drop in AUTO mode
  • estop_params.yaml: mqtt_host, cellular_timeout_s, auto_estop_on_disconnect
  • remote_estop.launch.py + setup.py + package.xml updates
  • docker-compose.yml: remote-estop service, depends_on: stm32-bridge
  • test_remote_estop.py: kill/clear/watchdog/latency unit tests

Test Plan

  • STM32: Send E\n via USB CDC → verify motors stop immediately, LED fast-blinks 200ms
  • STM32: Send Z\n → verify estop cleared, robot re-armable via RC hold
  • STM32: F\n → same as E\n but telemetry shows "es":4
  • Telemetry: verify "es" field in JSON stream
  • Jetson: MQTT {"kill":true} → verify E\n reaches STM32 in <500ms
  • Jetson: Disconnect 4G in AUTO mode → verify watchdog fires F\n after 5s
  • Unit tests: pytest test/test_remote_estop.py

Fixes #63

🤖 Generated with Claude Code

## Summary Implements remote e-stop over 4G cellular MQTT — closes Issue #63 (P0 safety-critical). ### STM32 Firmware - **`safety.h/c`**: `EstopSource` enum (`ESTOP_CLEAR/TILT/RC_KILL/REMOTE/CELLULAR_TIMEOUT`); `safety_remote_estop()`, `safety_remote_estop_clear()`, `safety_get_estop()`, `safety_remote_estop_active()` — latched until explicit `Z` clear + robot DISARMED - **`usbd_cdc_if.c/h`**: CDC commands `E` (ESTOP_REMOTE), `F` (ESTOP_CELLULAR_TIMEOUT), `Z` (clear latch) - **`status.h/c`**: `status_update()` +`remote_estop` param; both LEDs fast-blink 200ms (highest priority) - **`main.c`**: Immediate motor cutoff on `E`/`F` (before RC handling); arming blocked while `safety_remote_estop_active()`; motor estop auto-clear gated; `"es"` field in JSON telemetry (0–4); `status_update()` updated to 5-arg signature ### Safety Guarantees - Motor cutoff is **IMMEDIATE** (bypasses PID, latches `motors.estop`) - Latch persists until explicit `Z` + robot must be DISARMED - **Cannot re-arm via MQTT alone** — requires RC arm hold after clear - IWDG-safe: no blocking code in e-stop path ### Jetson Bridge - **`remote_estop_node.py`**: paho-mqtt subscriber + pyserial writer; cellular watchdog fires `F\n` after 5s MQTT drop in AUTO mode - **`estop_params.yaml`**: `mqtt_host`, `cellular_timeout_s`, `auto_estop_on_disconnect` - **`remote_estop.launch.py`** + `setup.py` + `package.xml` updates - **`docker-compose.yml`**: `remote-estop` service, `depends_on: stm32-bridge` - **`test_remote_estop.py`**: kill/clear/watchdog/latency unit tests ## Test Plan - [ ] STM32: Send `E\n` via USB CDC → verify motors stop immediately, LED fast-blinks 200ms - [ ] STM32: Send `Z\n` → verify estop cleared, robot re-armable via RC hold - [ ] STM32: `F\n` → same as `E\n` but telemetry shows `"es":4` - [ ] Telemetry: verify `"es"` field in JSON stream - [ ] Jetson: MQTT `{"kill":true}` → verify `E\n` reaches STM32 in <500ms - [ ] Jetson: Disconnect 4G in AUTO mode → verify watchdog fires `F\n` after 5s - [ ] Unit tests: `pytest test/test_remote_estop.py` Fixes #63 🤖 Generated with [Claude Code](https://claude.com/claude-code)
sl-firmware added 1 commit 2026-03-01 00:57:22 -05:00
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>
sl-webui force-pushed sl-firmware/remote-estop from 3a7576939e to d41a9dfe10 2026-03-01 04:55:59 -05:00 Compare
sl-jetson merged commit fc8faa0dab into main 2026-03-01 04:58:59 -05:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: seb/saltylab-firmware#69
No description provided.