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>
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>
- udev: 70-canable.rules — gs_usb VID/PID 1d50:606f, names iface can0 and brings it up at 500 kbps on plug-in
- systemd: can-bringup.service — oneshot service bound to sys-subsystem-net-devices-can0.device
- scripts: can_setup.sh — manual up/down/verify helper; candump verify for VESC IDs 61 (0x3D) and 79 (0x4F)
- install_systemd.sh updated to install can-bringup.service and all udev rules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces ui/index.html (old USB-serial HUD) with a full rosbridge
dashboard. Adds ui/dashboard.{css,js}.
Top bar:
- Robot name + ⚡ SALTYBOT logo
- Live battery % + voltage with fill bar (4S LiPo: 12.0V–16.8V)
- Safety state from /saltybot/safety_zone/status (GREEN/AMBER/RED)
- E-stop state display
- Drive mode display
- ROS uptime counter
- rosbridge WS input + CONNECT button
Panel grid (auto-fill responsive):
- MAP VIEW (#587) — /saltybot/pose/fused liveness dot
- GAMEPAD TELEOP (#598) — /cmd_vel activity indicator
- DIAGNOSTICS (#562) — /diagnostics liveness dot
- EVENT LOG (#576) — /rosout liveness dot
- SETTINGS (#614) — param service (config state, no topic)
- GIMBAL (#551) — /gimbal/state liveness dot
Each card shows: icon, title, issue #, description, topic chips,
and a LIVE/IDLE/OFFLINE status badge updated every second. Cards
open the linked standalone panel in the same tab.
Auto-detect rosbridge:
- Probes: page hostname:9090, localhost:9090, saltybot.local:9090
- Progress dots per candidate (trying/ok/fail)
- Falls back to manual URL entry
- Saves last successful URL to localStorage
Bottom bar:
- ⛔ E-STOP button (latches, publishes zero Twist to /cmd_vel)
Space bar shortcut from dashboard
- RESUME button
- Drive mode switcher: MANUAL / AUTO / FOLLOW / DOCK
(publishes to /saltybot/drive_mode std_msgs/String)
- Session timer (HH:MM:SS since page load)
Info strip: rosbridge URL · msg rate · latency (5s ping via
/rosapi/get_time) · robot IP
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
phone/voice_cmd.py — listens via termux-speech-to-text, parses commands
(go forward/back, turn left/right, stop, e-stop, go to waypoint, speed
up/down, status) and publishes structured JSON to saltybot/phone/voice_cmd.
TTS confirmation via termux-tts-speak. Manual text fallback via --text flag.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>