744 Commits

Author SHA1 Message Date
2d60aab79c feat: SLAM map persistence for AMCL (Issue #696)
- New map_persistence.launch.py: launches map_saver_server lifecycle node
  (nav2_map_server) + saltybot_map_saver helper node + lifecycle_manager.
  Configurable map_dir (default /mnt/nvme/saltybot/maps) and map_name.

- New map_saver_node.py: ROS2 node providing /saltybot/save_map (Trigger
  service) that calls nav2_map_server map_saver_cli. On startup logs whether
  a saved map is present. Auto-saves map on shutdown (auto_save_on_shutdown).

- New config/map_saver_params.yaml: map_saver_server params
  (save_map_timeout=5s, free/occupied thresholds, transient-local QoS).

- nav2_slam_bringup.launch.py: adds map_dir + map_name args; includes
  map_persistence.launch.py so map_saver_server runs during SLAM sessions.

- nav2_amcl_bringup.launch.py: adds map_dir arg; auto-detects saved map at
  /mnt/nvme/saltybot/maps/saltybot_map.yaml at launch time and uses it as
  the AMCL map; falls back to placeholder if not found.

- setup.py: registers map_persistence.launch.py, map_saver_params.yaml,
  map_saver_node console_scripts entry point.

- test_nav2_amcl.py: 21 new tests covering params, launch syntax,
  node service/shutdown behaviour, SLAM bringup inclusion, AMCL auto-detect.

Workflow:
  1. ros2 launch saltybot_nav2_slam nav2_slam_bringup.launch.py   (build map)
  2. ros2 service call /saltybot/save_map std_srvs/srv/Trigger {}  (save)
  3. ros2 launch saltybot_nav2_slam nav2_amcl_bringup.launch.py   (auto-loads)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 16:27:52 -04:00
5906af542b Merge pull request 'feat: BME280 baro pressure & ambient temp (Issue #672)' (#673) from sl-firmware/issue-672-bme280-baro-temp into main 2026-03-18 08:27:39 -04:00
4318589496 feat: BME280 baro pressure & ambient temp driver (Issue #672)
- New baro module (include/baro.h, src/baro.c): reads BME280/BMP280
  at 1 Hz on I2C1, computes pressure altitude (ISA formula), publishes
  JLINK_TLM_BARO (0x8D) telemetry to Orin. Runs entirely on Mamba F722S
  with no Orin dependency. baro_get_alt_cm() exposes altitude to balance
  PID slope compensation.
- New JLink telemetry frame 0x8D (jlink_tlm_baro_t, 12 bytes packed):
  pressure_pa (int32), temp_x10 (int16), alt_cm (int32),
  humidity_pct_x10 (int16; -1 = BMP280/absent).
- Wire into main.c: baro_init() after bmp280_init(), baro_tick(now)
  each ms (self-rate-limits to 1 Hz).
- Unit tests (test/test_baro.c): 31 tests, all pass. Build:
    gcc -I include -I test/stubs -DTEST_HOST -lm -o /tmp/test_baro test/test_baro.c

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 08:25:07 -04:00
441c56b1d9 Merge pull request 'feat: Hardware button park/disarm/re-arm (Issue #682)' (#688) from sl-firmware/issue-682-hw-button into main 2026-03-18 08:21:55 -04:00
334ab9249c Merge pull request 'feat: CAN sensor remote monitor panel (Issue #681)' (#687) from sl-webui/issue-681-can-monitor into main 2026-03-18 08:10:41 -04:00
4affd6d0cb feat: Hardware button park/disarm/re-arm (Issue #682)
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>
2026-03-18 08:10:10 -04:00
fe979fdd1f feat: CAN sensor remote monitor panel (Issue #681)
Adds standalone vanilla JS/HTML/CSS panel for live CAN sensor monitoring:
- can_monitor_panel.html: 5-card dashboard grid with VESC L/R, Balance,
  IMU Attitude (span-2), and Barometer cards
- can_monitor_panel.css: dark-theme styles matching existing panel suite;
  bidirectional bars, live-dot flash, stat-grid, responsive layout
- can_monitor_panel.js: rosbridge subscriptions to
    /vesc/left/state + /vesc/right/state (RPM bidir bar, current gauge,
    voltage/duty/temp stats, fault badge, stale detection)
    /saltybot/imu (quaternion→Euler, angular vel, lin accel, cal badge
    from orientation_covariance[0], canvas artificial horizon + compass)
    /saltybot/balance_state (state badge, motor_cmd bidir bar, PID grid)
    /saltybot/barometer (altitude, pressure, temp)
  Auto-connect from localStorage, 1 Hz stale checker, msg rate display

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 08:09:03 -04:00
9e8ea3c411 Merge pull request 'feat: VESC CAN health monitor (Issue #651)' (#666) from sl-jetson/issue-651-vesc-health into main 2026-03-18 08:03:32 -04:00
d57c0bd51d feat: VESC CAN health monitor (Issue #651)
New package: saltybot_vesc_health

- recovery_fsm.py: pure state machine (no ROS2/CAN deps; fully unit-tested)
  - VescHealthState: HEALTHY → DEGRADED (>500 ms) → ESTOP (>2 s) / BUS_OFF
  - VescMonitor.tick(): drives recovery sequence per VESC; startup-safe
  - VescMonitor.on_frame(): resets state on CAN frame arrival
  - VescMonitor.on_bus_off/on_bus_ok(): bus-off override + recovery
  - HealthFsm: dual-VESC wrapper aggregating both monitors

- health_monitor_node.py: ROS2 node
  - Subscribes /vesc/left/state + /vesc/right/state (JSON from vesc_telemetry)
  - Sends GET_VALUES alive frames via SocketCAN on DEGRADED state
  - Publishes /vesc/health (JSON, 10 Hz) — state, elapsed, recent faults
  - Publishes /diagnostics (DiagnosticArray, OK/WARN/ERROR per VESC)
  - Publishes /estop (JSON event) + zero /cmd_vel on e-stop trigger/clear
  - Polls ip link for bus-off state (1 Hz)
  - 200-entry fault event log included in /vesc/health

- test/test_vesc_health.py: 39 unit tests, all passing, no hardware needed
- config/vesc_health_params.yaml, launch/vesc_health.launch.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 08:03:19 -04:00
fdda6fe5ee Merge pull request 'feat: Nav2 AMCL integration (Issue #655)' (#664) from sl-perception/issue-655-nav2-integration into main 2026-03-18 07:57:02 -04:00
3457919c7a Merge pull request 'feat: UWB geofence speed limiting (Issue #657)' (#663) from sl-uwb/issue-657-geofence-speed-limit into main 2026-03-18 07:56:53 -04:00
cfdd74a9dc Merge pull request 'feat: VESC motor dashboard panel (Issue #653)' (#662) from sl-webui/issue-653-vesc-panel into main 2026-03-18 07:56:43 -04:00
4f3a30d871 Merge pull request 'feat: Smooth velocity controller (Issue #652)' (#661) from sl-controls/issue-652-smooth-velocity into main 2026-03-18 07:56:26 -04:00
7eb3f187e2 feat: Smooth velocity controller (Issue #652)
Adds velocity_smoother_node.py with configurable accel/decel ramps,
e-stop bypass, and optional jerk limiting. VESC driver updated to
subscribe /cmd_vel_smoothed instead of /cmd_vel.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 07:56:16 -04:00
sl-android
a50dbe7e56 feat: VESC CAN telemetry MQTT relay (Issue #656)
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>
2026-03-18 07:56:02 -04:00
6561e35fd6 Merge pull request 'feat: VESC MQTT telemetry relay (Issue #656)' (#660) from sl-android/issue-656-vesc-mqtt-relay into main 2026-03-18 07:55:42 -04:00
4dc75c8a70 Merge pull request 'feat: CANable 2.0 mount (Issue #654)' (#659) from sl-mechanical/issue-654-canable-mount into main 2026-03-18 07:55:31 -04:00
4d0a377cee Merge pull request 'feat: VESC CAN odometry (Issue #646)' (#649) from sl-perception/issue-646-vesc-odometry into main 2026-03-18 07:55:17 -04:00
06101371ff fix: Use correct VESC topic names /vesc/left|right/state (Issue #670)
- 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>
2026-03-18 07:55:04 -04:00
cf0ceb4641 Merge pull request 'fix: Configurable VESC CAN IDs, default 56/68 (Issue #667)' (#668) from sl-controls/issue-667-configurable-can-ids into main 2026-03-18 07:50:33 -04:00
ee16bae9fb fix: Make VESC CAN IDs configurable, default 56/68 (Issue #667)
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>
2026-03-18 07:50:20 -04:00
70fa404437 Merge pull request 'fix: Standardize VESC topic naming (Issue #669)' (#671) from sl-jetson/issue-669-vesc-topic-fix into main 2026-03-18 07:49:20 -04:00
c11cbaf3e6 Merge pull request 'feat: IMU mount cal, CAN telemetry, LED CAN override (Issues #680, #672, #685)' (#686) from sl-jetson/issue-681-vesc-telemetry-publish into main 2026-03-18 07:49:12 -04:00
d132b74df0 Merge pull request 'fix: Move lines=[] above lock in _read_cb() (Issue #683)' (#684) from sl-jetson/issue-683-read-cb-fix into main 2026-03-18 07:49:02 -04:00
8985934f29 Merge pull request 'fix: Bump arm pitch threshold to 20° (Issue #678)' (#679) from sl-firmware/issue-678-pitch-threshold into main 2026-03-18 07:48:49 -04:00
9ed678ca35 feat: IMU mount angle cal, CAN telemetry, LED override (Issues #680, #672, #685)
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>
2026-03-17 22:49:21 -04:00
06db56103f feat: Enable VESC driver telemetry publishing (Issue #681)
vesc_driver_node.py:
- Add VescState dataclass with to_dict() serialization
- Add CAN_PACKET_STATUS/STATUS_4/STATUS_5 (9/16/27) RX constants
- Add FAULT_NAMES lookup (11 VESC FW 6.6 fault codes)
- Add background CAN RX thread (_rx_loop / _dispatch_frame) that
  parses STATUS broadcast frames using struct.unpack
- Add publishers for /saltybot/vesc/left and /saltybot/vesc/right
  (std_msgs/String JSON) at configurable telemetry_rate_hz (default 10 Hz)
- Combine watchdog + publish into single timer callback
- Proper thread cleanup in destroy_node()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 22:49:06 -04:00
05ba557dca fix: Move lines=[] above lock in _read_cb() (Issue #683)
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>
2026-03-17 22:36:20 -04:00
0a2f336eb8 Merge pull request 'feat: Orin CAN bus bridge — CANable 2.0 (Issue #674)' (#675) from sl-jetson/issue-674-can-bus-orin into main 2026-03-17 21:41:29 -04:00
5e82878083 feat: bxCAN integration for VESC motor control and Orin comms (Issue #674)
- 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>
2026-03-17 21:41:19 -04:00
92c0628c62 feat: Orin CANable 2.0 bridge for Mamba and VESC CAN bus (Issue #674)
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>
2026-03-17 21:40:07 -04:00
56c59f60fe fix: add __stack_end defsym for fault_handler MPU guard (Issue #678)
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>
2026-03-17 21:39:44 -04:00
7f67fc6abe Merge pull request 'fix: remap CAN from CAN2/PB12-13 to CAN1/PB8-9 (Issue #676)' (#677) from sl-firmware/issue-597-can-driver into main 2026-03-17 21:39:29 -04:00
ea5203b67d fix: bump arm pitch threshold 10°→20° (Issue #678)
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>
2026-03-17 21:38:02 -04:00
14c80dc33c fix: remap CAN from CAN2/PB12-13 to CAN1/PB8-9 (Issue #676)
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>
2026-03-17 20:31:59 -04:00
7d2d41ba9f fix: Standardize VESC topic naming to /vesc/left|right/state (Issue #669)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 15:18:43 -04:00
b74307c58a feat: Nav2 AMCL integration with VESC odometry + LiDAR (Issue #655)
AMCL-based autonomous navigation on pre-built static maps, wired to
VESC CAN differential-drive odometry (/odom, Issue #646) and RPLiDAR
(/scan) as the primary sensor sources.

New files (saltybot_nav2_slam):
- config/amcl_nav2_params.yaml — complete Nav2 + AMCL parameter file
  with inline global/local costmap configs (required by nav2_bringup):
  · AMCL: DifferentialMotionModel, 500–3000 particles, z-weights=1.0,
    odom_frame=/odom, scan_topic=/scan
  · Global costmap: static_layer + obstacle_layer (LiDAR) +
    inflation_layer (0.55m radius)
  · Local costmap: 4m rolling window, obstacle_layer (LiDAR) +
    inflation_layer, global_frame=odom
  · DWB controller: 1.0 m/s max, diff-drive constrained (vy=0)
  · NavFn A* planner
  · Recovery: spin + backup + wait
  · Lifecycle managers for localization and navigation
- launch/nav2_amcl_bringup.launch.py — orchestrates:
  1. sensors.launch.py (RealSense + RPLIDAR, conditional)
  2. odometry_bridge.launch.py (VESC CAN → /odom)
  3. nav2_bringup localization_launch.py (map_server + AMCL)
  4. nav2_bringup navigation_launch.py (full nav stack)
  Exposes: map, use_sim_time, autostart, params_file, include_sensors
- maps/saltybot_map.yaml — placeholder map descriptor (0.05m/cell)
- maps/saltybot_map.pgm — 200×200 P5 PGM, all free space (10m×10m)
- test/test_nav2_amcl.py — 38 unit tests (no ROS2 required):
  params structure, z-weight sum, costmap layers, DWB/NavFn validity,
  recovery behaviors, PGM format, launch file syntax checks

Updated:
- saltybot_bringup/launch/nav2.launch.py — adds nav_mode argument:
  nav_mode:=slam (default, existing RTAB-Map behaviour unchanged)
  nav_mode:=amcl (new, delegates to nav2_amcl_bringup.launch.py)
- saltybot_nav2_slam/setup.py — installs new launch, config, maps
- saltybot_nav2_slam/package.xml — adds nav2_amcl, nav2_map_server,
  nav2_behaviors, dwb_core, nav2_navfn_planner exec_depends

All 58 tests pass (38 new + 20 from Issue #646).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 11:39:08 -04:00
sl-uwb
9d2b19104f feat: UWB geofence speed limiting (Issue #657)
Add saltybot_uwb_geofence ROS2 package — Jetson-side node that subscribes
to /saltybot/pose/authoritative (UWB+IMU fused PoseWithCovarianceStamped),
enforces configurable polygon speed-limit zones (YAML), and publishes
speed-limited /cmd_vel_limited with smooth ramp transitions.

Emergency boundary: if robot exits outer polygon, cmd_vel is zeroed and
/saltybot/geofence_violation (Bool) is latched True, triggering the
existing e-stop cascade.  Publishes /saltybot/geofence/status (JSON).

Pure-geometry helpers (zone_checker.py) have no ROS2 dependency;
35 unit tests pass (pytest).  ESP32 UWB firmware untouched.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 11:36:37 -04:00
89f892e5ef feat: VESC motor dashboard panel (Issue #653)
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>
2026-03-17 11:35:35 -04:00
sl-android
289185e6cf feat: VESC CAN telemetry MQTT relay (Issue #656)
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>
2026-03-17 11:33:59 -04:00
4f81571dd3 feat: CANable 2.0 mount for T-slot rail (Issue #654)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 11:33:06 -04:00
Sebastien Vayrette
d9b4b10b90 Merge remote-tracking branch 'origin/sl-perception/issue-646-vesc-odometry' 2026-03-17 11:27:45 -04:00
Sebastien Vayrette
a96fd91ed7 Merge remote-tracking branch 'origin/sl-firmware/issue-645-vesc-telemetry' 2026-03-17 11:27:36 -04:00
Sebastien Vayrette
bf8df6af8f Merge remote-tracking branch 'origin/sl-controls/issue-644-vesc-can-driver' 2026-03-17 11:27:26 -04:00
d8b25bad77 feat: VESC CAN odometry for nav2 (Issue #646)
Replace single-motor vesc_odometry_bridge with dual-CAN differential
drive odometry for left (CAN 61) and right (CAN 79) VESC motors.

New files:
- diff_drive_odom.py: pure-Python kinematics (eRPM→wheel vel, exact arc
  integration, heading wrap), no ROS deps, fully unit-tested
- test/test_vesc_odometry.py: 20 unit tests (straight, arc, spin,
  invert_right, guard conditions) — all pass
- config/vesc_odometry_params.yaml: configurable wheel_radius,
  wheel_separation, motor_poles, invert_right, covariance tuning

Updated:
- vesc_odometry_bridge.py: rewritten as VESCCANOdometryNode; subscribes
  to /vesc/can_61/state and /vesc/can_79/state (std_msgs/String JSON);
  publishes /odom and /saltybot/wheel_odom (nav_msgs/Odometry) + TF
  odom→base_link with proper 6×6 covariance matrices
- odometry_bridge.launch.py: updated to launch vesc_can_odometry with
  vesc_odometry_params.yaml
- setup.py: added vesc_can_odometry entry point + config install
- pose_fusion_node.py: added optional wheel_odom_topic subscriber that
  feeds DiffDriveOdometry velocities into EKF via update_vo_velocity
- pose_fusion_params.yaml: added use_wheel_odom, wheel_odom_topic,
  sigma_wheel_vel_m_s, sigma_wheel_omega_r_s parameters

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 09:54:19 -04:00
b2c9f368f6 feat: VESC CAN telemetry for dual motors (Issue #645)
New saltybot_vesc_telemetry ROS2 package — SocketCAN (python-can, can0)
telemetry for dual FSESC 6.7 Pro (FW 6.6) on CAN IDs 61 (left) and 79 (right).

- vesc_can_protocol.py: STATUS/STATUS_4/STATUS_5 frame parsers, VescState
  dataclass, GET_VALUES request builder (CAN_PACKET_PROCESS_SHORT_BUFFER)
- vesc_telemetry_node.py: ROS2 node; background CAN RX thread; publishes
  /vesc/left/state, /vesc/right/state, /vesc/combined (JSON String msgs),
  /diagnostics (DiagnosticArray); overcurrent/overtemp/fault alerting;
  configurable poll rate 10-50 Hz (default 20 Hz)
- test_vesc_telemetry.py: 31 unit tests, all passing (no ROS/CAN required)
- config/vesc_telemetry_params.yaml, launch file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 09:53:09 -04:00
a506989af6 feat: CANable 2.0 bringup with udev rule and systemd service (Issue #643)
- 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>
2026-03-17 09:49:21 -04:00
1d87899270 feat: VESC SocketCAN dual-motor driver IDs 61/79 (Issue #644)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 09:47:57 -04:00
0fcad75cb4 Merge pull request 'feat: Encoder odometry and wheel speed feedback (Issue #632)' (#642) from sl-controls/issue-632-encoder-odom into main 2026-03-15 17:29:54 -04:00
5aadf4b5c8 Merge pull request 'feat: Jetson Orin system monitor ROS2 node (Issue #631)' (#640) from sl-jetson/issue-631-system-monitor into main 2026-03-15 17:29:50 -04:00