Add saltybot_can_e2e_test package with 64 tests covering the full
Orin↔Mamba↔VESC CAN pipeline: drive commands, heartbeat timeout,
e-stop escalation, mode switching, and FC_VESC status broadcasts.
Tests run with plain pytest — no ROS2 or real CAN hardware required.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- UwbTagBleActivity: BLE scan filtered to 'UWB_TAG_XXXX' device names
- Connects to GATT service 12345678-1234-5678-1234-56789abcdef0
- Read/write JSON config char: sleep_timeout_s, display_brightness,
tag_name, uwb_channel, ranging_interval_ms, battery_report
- Subscribes to status + battery notification characteristics
- Material Design UI with scan list, config form, and live status
- Runtime BLE permission handling for API 26+ / API 31+
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add hw_button driver (PC2 active-low, 20ms debounce) with gesture detection:
- Single short press + 500ms quiet -> BTN_EVENT_PARK
- SHORT+SHORT+LONG combo (within 3s) -> BTN_EVENT_REARM_COMBO
New BALANCE_PARKED state: PID frozen, motors off, quick re-arm via button
combo without the 3-second arm interlock required from DISARMED.
FC_BTN (0x404) CAN frame sent to Orin on each event:
event_id 1=PARKED, 2=UNPARKED, 3=UNPARK_FAILED (pitch > 20 deg)
Includes 11 unit tests (1016 assertions) exercising debounce, bounce
rejection, short/long classification, sequence detection, and timeout.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add vesc_mqtt_relay_node.py to saltybot_phone: subscribes to
/vesc/left/state, /vesc/right/state, /vesc/combined ROS2 topics and
publishes JSON telemetry to saltybot/phone/vesc_{left,right,combined}
MQTT topics at 5 Hz per motor. 32 unit tests, no ROS2/paho required.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- VESCCANOdometryNode subscriptions now use left_state_topic/right_state_topic
params (defaulting to /vesc/left/state and /vesc/right/state) instead of
building /vesc/can_<id>/state from CAN IDs — those topics never existed
- Update right_can_id default: 79 → 68 (Mamba F722S architecture update)
- Update vesc_odometry_params.yaml: CAN IDs 61/79 → 56/68; add explicit
left_state_topic and right_state_topic entries; remove stale can_N comments
- All IDs remain fully configurable via ROS2 params
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FSESC 6.7 Pro Mini Dual uses CAN IDs 56/68, not 61/79. Updates all
driver, telemetry, and odometry bridge files to use correct defaults.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Issue #680 — IMU mount angle calibration:
- imu_cal_flash.h/.c: store pitch/roll offsets in flash sector 7
(0x0807FF00, 64 bytes; preserves PID records across sector erase)
- mpu6000_set_mount_offset(): subtracts offsets from pitch/roll output
- mpu6000_has_mount_offset(): reports cal_status=2 to Orin
- 'O' CDC command: capture current pitch/roll → save to flash → ACK JSON
- Load offsets on boot; report in printf log
CAN telemetry correction (Tee: production has no USB to Orin):
- FC_IMU (0x402): pitch/roll/yaw/cal_status/balance_state at 50 Hz
- orin_can_broadcast_imu() rate-limited to ORIN_IMU_TLM_HZ (50 Hz)
- FC_BARO (0x403): pressure_pa/temp_x10/alt_cm at 1 Hz (Issue #672)
- orin_can_broadcast_baro() rate-limited to ORIN_BARO_TLM_HZ (1 Hz)
Issue #685 — LED CAN override:
- ORIN_CAN_ID_LED_CMD (0x304): pattern/brightness/duration_ms from Orin
- orin_can_led_override volatile state + orin_can_led_updated flag
- main.c: apply pattern to LED state machine on each LED_CMD received
Orin side:
- saltybot_can_node.py: production SocketCAN bridge — reads 0x400-0x403,
publishes /saltybot/imu, /saltybot/balance_state, /saltybot/barometer;
subscribes /cmd_vel → 0x301 DRIVE; /saltybot/leds → 0x304 LED_CMD;
sends 0x300 HEARTBEAT at 5 Hz; sends 0x303 ESTOP on shutdown
- setup.py: register saltybot_can_node entry point + uart_bridge launch
Fix: re-apply --defsym __stack_end=_estack-0x1000 linker fix to branch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
UnboundLocalError when _ser is None — lines was only assigned inside
the else branch. Move initialisation to function scope so the for-loop
outside the lock always has a valid list.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- can_driver: add filter bank 15 (all ext IDs → FIFO1) and widen bank 14
to accept all standard IDs; add can_driver_send_ext/std and ext/std
frame callbacks (can_driver_set_ext_cb / can_driver_set_std_cb)
- vesc_can: VESC 29-bit extended CAN protocol driver — send RPM to IDs 56
and 68 (FSESC 6.7 Pro Mini Dual), parse STATUS/STATUS_4/STATUS_5
big-endian payloads, alive timeout, JLINK_TLM_VESC_STATE at 1 Hz
- orin_can: Orin↔FC standard CAN protocol — HEARTBEAT/DRIVE/MODE/ESTOP
commands in, FC_STATUS + FC_VESC broadcast at 10 Hz
- jlink: add JLINK_TLM_VESC_STATE (0x8E), jlink_tlm_vesc_state_t (22 bytes),
jlink_send_vesc_state_tlm()
- main: wire vesc_can_init/orin_can_init; replace can_driver_send_cmd with
vesc_can_send_rpm; inject Orin CAN speed/steer into balance PID; add
Orin CAN estop/clear handling; add orin_can_broadcast at 10 Hz
- test: 56-test host-side suite for vesc_can; test/stubs/stm32f7xx_hal.h
minimal HAL stub for all future host-side tests
Safety: balance PID runs independently on Mamba — if Orin CAN link drops
(orin_can_is_alive() == false) the robot continues balancing in-place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds slcan setup script and saltybot_can_bridge ROS2 package implementing
full CAN bus integration between the Orin and the Mamba motor controller /
VESC motor controllers via a CANable 2.0 USB dongle (slcan interface).
- jetson/scripts/setup_can.sh: slcand-based bring-up/tear-down for slcan0
at 500 kbps with error handling (already up, device missing, retry)
- saltybot_can_bridge/mamba_protocol.py: CAN message ID constants and
encode/decode helpers for velocity, mode, e-stop, IMU, battery, VESC state
- saltybot_can_bridge/can_bridge_node.py: ROS2 node subscribing to /cmd_vel
and /estop, publishing /can/imu, /can/battery, /can/vesc/{left,right}/state
and /can/connection_status; background reader thread, watchdog zero-vel,
auto-reconnect every 5 s on CAN error
- config/can_bridge_params.yaml: default params (slcan0, VESC IDs 56/68,
Mamba ID 1, 0.5 s command timeout)
- test/test_can_bridge.py: 30 unit tests covering encode/decode round-trips
and edge cases — all pass without ROS2 or CAN hardware
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
STM32Cube ld script provides _estack but not __stack_end. Define
__stack_end = _estack - 0x1000 (_Min_Stack_Size) via --defsym so
fault_mpu_guard_init() and fault_mem_c() can locate the stack bottom.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mamba is mounted at ~12° on the frame, causing all three arm-interlock
checks to block arming. Raise fabsf(bal.pitch_deg) < 10.0f to 20.0f
at lines 375, 512, 532 (JLink arm, RC arm rising-edge, CDC arm).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Mamba F722S MK2 does not expose PB12/PB13 externally. Waveshare CAN
module is wired to the SCL (PB8) and SDA (PB9) header pads.
Changes in can_driver_init():
- Drop __HAL_RCC_CAN2_CLK_ENABLE() — CAN1 needs no slave clock
- GPIO: GPIO_PIN_12/13 → GPIO_PIN_8/9, GPIO_AF9_CAN2 → GPIO_AF9_CAN1
- Instance: CAN2 → CAN1
- Filter bank: 14 → 0 (CAN1 master banks start at 0; bank 14 is the
CAN2 slave-start boundary, unused here)
I2C1 is free: BME280 has been moved to I2C2 (PB10/PB11), so PB8/PB9
are available for CAN1 without any peripheral conflict.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Standalone panel ui/vesc_panel.{html,js,css} with live CAN telemetry
via rosbridge. Subscribes to /vesc/left/state, /vesc/right/state
(std_msgs/String JSON) and /vesc/combined for battery voltage.
Features:
- Canvas arc gauge per motor showing RPM + direction (FWD/REV/STOP)
- Current draw bar (motor + input), duty cycle bar, temperature bars
- FET and motor temperature boxes with warn/crit colour coding
- Sparkline charts for RPM and current (last 60 s, 120 samples)
- Battery card: voltage, total draw, both RPMs, SOC progress bar
- Colour-coded health: green/amber/red at configurable thresholds
- E-stop button: publishes zero /cmd_vel + /saltybot/emergency event
- Stale detection (2 s timeout → OFFLINE state)
- Hz counter + last-stamp display in header
- Mobile-responsive layout (single-column below 640 px)
- WS URL persisted in localStorage
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add vesc_mqtt_relay_node.py to saltybot_phone: subscribes to
/vesc/left/state, /vesc/right/state, /vesc/combined ROS2 topics and
publishes JSON telemetry to saltybot/phone/vesc_{left,right,combined}
MQTT topics at 5 Hz per motor. 32 unit tests, no ROS2/paho required.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>