ecb95c738b
Merge pull request 'feat(social): multi-camera gesture recognition — MediaPipe Hands + Pose (Issue #140 )' ( #162 ) from sl-jetson/issue-140-gestures into main
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 13s
social-bot integration tests / Core integration tests (mock sensors, no GPU) (push) Has been skipped
social-bot integration tests / Latency profiling (GPU, Orin) (push) Has been cancelled
2026-03-02 10:26:03 -05:00
783dedf4d4
feat( #158 ): docking station auto-return with ArUco/IR detection and visual servo
...
Two new ROS2 packages implementing Issue #158 :
saltybot_docking_msgs (ament_cmake)
- DockingStatus.msg: stamp, state, dock_detected, distance_m, lateral_error_m,
battery_pct, charging, aligned
- Dock.srv / Undock.srv: force + resume_mission flags
saltybot_docking (ament_python, 20 Hz)
- dock_detector.py: ArucoDetector (cv2.aruco PnP → DockPose) + IRBeaconDetector
(EMA envelope with amplitude threshold); both gracefully degrade if unavailable
- visual_servo.py: IBVS proportional controller — v = k_lin×(d−target),
ω = −k_ang×yaw; aligned when |lateral| < 5mm AND d < contact_distance
- charge_monitor.py: edge-triggered events (CHARGING_STARTED/STOPPED,
THRESHOLD_LOW at 15%, THRESHOLD_HIGH at 80%)
- docking_state_machine.py: 7-state FSM (IDLE→DETECTING→NAV2_APPROACH→
VISUAL_SERVO→CONTACT→CHARGING→UNDOCKING); mission_resume signal on
auto-dock cycle; contact retry on timeout; lost-detection timeout
- docking_node.py: 20Hz ROS2 node; Nav2 NavigateToPose action client (optional);
/saltybot/dock + /saltybot/undock services; /saltybot/docking_cmd_vel;
/saltybot/resume_mission; /saltybot/docking_status
- config/docking_params.yaml, launch/docking.launch.py
Tests: 64/64 passing (IRBeaconDetector, VisualServo, ChargeMonitor,
DockingStateMachine — all state transitions and guard conditions covered)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:19:22 -05:00
572e578069
feat(vo): visual odometry fallback — CUDA optical flow + EKF fusion + slip failover (Issue #157 )
...
New package: saltybot_visual_odom (13 files, ~900 lines)
Nodes:
visual_odom_node — D435i IR1 stereo VO at 30 Hz
CUDA: SparsePyrLKOpticalFlow + FastFeatureDetector (GPU)
CPU fallback: calcOpticalFlowPyrLK + goodFeaturesToTrack
Essential matrix (5-pt RANSAC) + depth-aided metric scale
forward-backward consistency check on tracked points
Publishes /saltybot/visual_odom (Odometry)
odom_fusion_node — 5-state EKF [px, py, θ, v, ω] (unicycle model)
Fuses: wheel odom (/saltybot/rover_odom or tank_odom)
+ visual odom (/saltybot/visual_odom)
Slip failover: /saltybot/terrain JSON → 10× wheel noise on slip
Loop closure: /rtabmap/odom jump > 0.3m → EKF soft-correct
TF: publishes odom → base_link at 30 Hz
Publishes /saltybot/odom_fused + /saltybot/visual_odom_status
Modules:
optical_flow_tracker.py — CUDA/CPU sparse LK tracker with re-detection,
forward-backward consistency, ROI masking
stereo_vo.py — Essential matrix decomposition, camera→base_link
frame rotation, depth median scale recovery,
loop closure soft-correct, accumulated SE(3) pose
kalman_odom_filter.py — 5-state EKF: predict (unicycle), update_wheel,
update_visual, update_rtabmap (absolute pose);
Joseph-form covariance for numerical stability
Tests:
test/test_kalman_odom.py — 8 unit tests for EKF + StereoVO (no ROS deps)
Topic/TF map:
/camera/infra1/image_rect_raw → visual_odom_node
/camera/depth/image_rect_raw → visual_odom_node
/saltybot/visual_odom ← visual_odom_node (30 Hz)
/saltybot/rover_odom → odom_fusion_node
/saltybot/terrain → odom_fusion_node (slip signal)
/rtabmap/odom → odom_fusion_node (loop closure)
/saltybot/odom_fused ← odom_fusion_node (30 Hz)
odom → base_link TF ← odom_fusion_node (30 Hz)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:15:32 -05:00
8222a0c42e
feat(mechanical): charging dock station (Issue #159 )
...
Universal 5 V/5 A charging dock for SaltyLab/Rover/Tank robots:
- charging_dock.scad: weighted base (ballast pockets, floor anchors),
back wall with 2× 5 A pogo pin housing + wiring channel, V-guide
funnel rails (±20 mm alignment tolerance), ArUco marker mast (100×100 mm,
15° tilt), PSU bracket (IRM-30-5), 4-LED status bezel
(Searching/Aligned/Charging/Full)
- charging_dock_receiver.scad: 3-variant robot-side contact plate with
Ø12 mm brass pad press-fit, V-nose self-alignment; SaltyLab stem
collar, SaltyRover deck flange, SaltyTank skid-plate mount
- charging_dock_BOM.md: hardware list, ASCII wiring diagram, INA219
current-sense LED state logic, pogo height cross-variant shim table,
assembly sequence, export commands
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:15:14 -05:00
ce6d5ee249
feat(social): multi-camera gesture recognition — MediaPipe Hands + Pose (Issue #140 )
...
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 9s
social-bot integration tests / Core integration tests (mock sensors, no GPU) (push) Has been skipped
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 2s
social-bot integration tests / Core integration tests (mock sensors, no GPU) (pull_request) Has been skipped
social-bot integration tests / Latency profiling (GPU, Orin) (push) Has been cancelled
social-bot integration tests / Latency profiling (GPU, Orin) (pull_request) Has been cancelled
Delivers Issue #140 (P2): real-time gesture detection from 4 CSI cameras via
MediaPipe Hands and Pose, publishing classified gestures on /social/gestures.
New files:
- saltybot_social_msgs/msg/Gesture.msg + GestureArray.msg — ROS2 message types
- saltybot_social/gesture_classifier.py — pure-Python geometric classifier
(stop_palm, thumbs_up/down, point, come_here, follow, wave, arms_up,
arms_spread, crouch); WaveDetector temporal sliding-window oscillation tracker
- saltybot_social/gesture_node.py — ROS2 node; round-robin multi-camera
_FrameBuffer, lazy MediaPipe init, person-ID correlation via PersonState
- saltybot_social/test/test_gesture_classifier.py — 70 unit tests, all passing
- saltybot_social/config/gesture_params.yaml — tuned defaults for Orin Nano
- saltybot_social/launch/gesture.launch.py — all params overridable at launch
Modified:
- saltybot_social_msgs/CMakeLists.txt — register Gesture + GestureArray msgs
- saltybot_social/setup.py — add gesture_node console_scripts entry point
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:10:54 -05:00
0f42e701e9
Merge pull request 'feat(firmware): OTA firmware update — USB DFU + dual-bank + CRC32 (Issue #124 )' ( #156 ) from sl-firmware/issue-124-ota into main
2026-03-02 10:08:40 -05:00
8facb80eab
Merge pull request 'feat(webui): mission planner — waypoint editor, routes, geofences, schedule (Issue #145 )' ( #155 ) from sl-webui/issue-145-mission-planner into main
2026-03-02 10:07:43 -05:00
33007fb5ed
Merge pull request 'feat( #142 ): terrain adaptation — surface detection + dynamic speed/PID/bias' ( #154 ) from sl-controls/issue-142-terrain into main
2026-03-02 10:07:38 -05:00
cb961edb9f
Merge pull request 'feat(scene): semantic scene understanding — YOLOv8n TRT + room classification + hazards (Issue #141 )' ( #153 ) from sl-perception/issue-141-scene-understanding into main
2026-03-02 10:07:29 -05:00
52250e28d6
Merge pull request 'feat(mechanical): IP54 weatherproofing kit (Issue #144 )' ( #152 ) from sl-mechanical/issue-144-weatherproofing into main
2026-03-02 10:07:27 -05:00
51dcc01bfa
feat(webui): mission planner — waypoint editor, routes, geofences, schedule (Issue #145 )
...
useMissions.js:
- Waypoints: click-to-place, drag, dwell time, localStorage persistence
- Routes: loop/oneshot/pingpong, per-robot assignment, waypoint sequence
- Geofences: polygon draw (no-go / allowed zones)
- Templates: save/load profiles + JSON export/import
- Schedules: time+day triggers with client-side runner
- Executions: running/paused/aborted state per route
MissionPlanner.jsx:
- Canvas: waypoints, route lines + direction arrows, geofence fills,
robot position overlays, scale bar, zoom/pan, execution progress
- 7 sub-views: Map | Waypoints | Routes | Geofences | Templates | Schedule | Execute
- Execute: start/pause/resume/abort, waypoint-by-waypoint advance,
sends /goal_pose (single) and /outdoor/waypoints (patrol PoseArray)
- Integrates useFleet for robot selection and /goal_pose publishing
App.jsx: adds Missions tab to FLEET group
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:04:38 -05:00
fb3e9f1bf1
feat( #142 ): terrain adaptation — surface detection, speed/PID/bias adaptation
...
Two new ROS2 packages implementing Issue #142 :
saltybot_terrain_msgs (ament_cmake)
- Terrain.msg: stamp, surface_type, roughness, incline_rad, grip, slip_ratio, speed_scale
saltybot_terrain_adaptation (ament_python, 50 Hz)
- terrain_analyzer.py: IMU vibration RMS → roughness [0,1] + surface classification
(smooth/tile/carpet/grass/gravel) with hysteresis-protected switching
- incline_detector.py: quaternion pitch → first-order LPF → incline_rad; motor_bias()
- slip_detector.py: cmd_vel vs odom → slip_ratio with EMA smoothing + speed gate
- terrain_adapter.py: TerrainState, TerrainAdapter (speed_scale, pid_scales, motor_bias),
TerrainHistory (rolling deque, JSON type-change log)
- terrain_adaptation_node.py: 50 Hz node; IMU watchdog; publishes terrain JSON + typed msg,
speed_scale, pid_scales (JSON), motor_bias, terrain_history (on type change)
- config/terrain_params.yaml, launch/terrain_adaptation.launch.py
Tests: 60/60 passing (TerrainAnalyzer, SlipDetector, InclineDetector, TerrainAdapter,
TerrainHistory all covered)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:04:36 -05:00
a9717cd602
feat(scene): semantic scene understanding — YOLOv8n TRT + room classification + hazards (Issue #141 )
...
New packages:
saltybot_scene_msgs — 4 msgs (SceneObject, SceneObjectArray, RoomClassification, BehaviorHint)
saltybot_scene — 3 nodes + launch + config + TRT build script
Nodes:
scene_detector_node — YOLOv8-nano TRT FP16 (target ≥15 FPS @ 640×640);
synchronized RGB+depth input; filters scene classes
(chairs, tables, doors, stairs, pets, appliances);
3D back-projection via aligned depth; depth-based hazard
scan (HazardClassifier); room classification at 2Hz;
publishes /social/scene/objects + /social/scene/hazards
+ /social/scene/room_type
behavior_adapter_node — adapts speed_limit_mps + personality_mode from room
type and hazard severity; publishes BehaviorHint on
/social/scene/behavior_hint (on-change + 1Hz heartbeat)
costmap_publisher_node — converts SceneObjectArray → PointCloud2 disc rings
for Nav2 obstacle_layer + MarkerArray for RViz;
publishes /social/scene/obstacle_cloud
Modules:
yolo_utils.py — YOLOv8 preprocess/postprocess (letterbox, cx/cy/w/h decode,
NMS), COCO+custom class table (door=80, stairs=81, wet=82),
hazard-by-class mapping
room_classifier.py — rule-based (object co-occurrence weights + softmax) with
optional MobileNetV2 TRT/ONNX backend (Places365-style 8-class)
hazard_classifier.py — depth-only hazard patterns: drop (row-mean cliff), stairs
(alternating depth bands), wet floor (depth std-dev), glass
(zero depth + strong Sobel edges in RGB)
scripts/build_scene_trt.py — export YOLOv8n → ONNX → TRT FP16; optionally build
MobileNetV2 room classifier engine; includes benchmark
Topic map:
/social/scene/objects SceneObjectArray ~15+ FPS
/social/scene/room_type RoomClassification ~2 Hz
/social/scene/hazards SceneObjectArray on hazard
/social/scene/behavior_hint BehaviorHint on-change + 1 Hz
/social/scene/obstacle_cloud PointCloud2 Nav2 obstacle_layer
/social/scene/object_markers MarkerArray RViz debug
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:59:53 -05:00
a70d9a2a71
feat(mechanical): IP54 weatherproofing kit (Issue #144 )
...
Add sealed enclosures and sensor housings for outdoor IP54 protection:
- ip54_enclosure.scad: main electronics box (Jetson/FC/ESC), O-ring lid,
fan+filter duct, PG7/PG9 cable glands, quarter-turn latches, heat sink
recesses; gasket DXF export
- ip54_sensor_housings.scad: IMX219 clear PC dome (O-ring + anti-fog
pocket), D435i IR-transparent window housing (PG7 rear cap), RPLIDAR
static clear PC dome base ring (120 mm OD, O-ring, quarter-turn clips)
- ip54_BOM.md: hardware list, thermal analysis (≤52°C at 40°C ambient),
IP54 compliance checklist, mass ~930g total kit
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:58:22 -05:00
4beef8da03
feat(firmware): OTA DFU entry via JLink command and Python flash script (Issue #124 )
...
- Add ota.h / ota.c: ota_enter_dfu() (armed guard, writes BKP15R, resets),
ota_fw_crc32() using STM32F7 hardware CRC peripheral (CRC-32/MPEG-2, 512 KB)
- Add JLINK_CMD_DFU_ENTER (0x06) and dfu_req flag to jlink.h / jlink.c
- Handle dfu_req in main loop: calls ota_enter_dfu(is_armed) — no-op if armed
- Update usbd_cdc_if.c: move DFU magic from BKP0R to BKP15R (OTA_DFU_BKP_IDX)
resolving BKP register conflict with BNO055 calibration (BKP0R–6R, PR #150 )
- Add scripts/flash_firmware.py: CRC-32/MPEG-2 + ISO-HDLC verification,
dfu-util flash, host-side backup/rollback, --trigger-dfu JLink serial path
- Add test/test_ota.py: 42 tests passing (CRC-32/MPEG-2, CRC-16/XMODEM,
DFU_ENTER frame structure, BKP register safety, flash constants)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:56:18 -05:00
07b4edaa2d
Merge pull request 'feat(mechanical): universal sensor mount rail system (Issue #138 )' ( #151 ) from sl-mechanical/issue-138-sensor-rail into main
2026-03-02 09:51:30 -05:00
f5d8180776
Merge pull request 'feat(firmware): BNO055 NDOF IMU driver on I2C1 (Issue #135 )' ( #150 ) from sl-firmware/issue-135-bno055 into main
2026-03-02 09:51:25 -05:00
732045a086
Merge pull request 'feat(controls): adaptive PID balance controller with gain scheduling (Issue #136 )' ( #149 ) from sl-controls/issue-136-adaptive-pid into main
2026-03-02 09:51:19 -05:00
4cda5d0f19
Merge pull request 'feat(social): voice command NLU — 30+ intents with confirmation flow (Issue #137 )' ( #148 ) from sl-jetson/issue-137-voice-commands into main
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 47s
social-bot integration tests / Core integration tests (mock sensors, no GPU) (push) Has been skipped
social-bot integration tests / Latency profiling (GPU, Orin) (push) Has been cancelled
2026-03-02 09:51:12 -05:00
e1acadc2aa
Merge pull request 'feat(webui): fleet management dashboard (Issue #139 )' ( #147 ) from sl-webui/issue-139-fleet-dashboard into main
social-bot integration tests / Core integration tests (mock sensors, no GPU) (push) Has been cancelled
social-bot integration tests / Latency profiling (GPU, Orin) (push) Has been cancelled
social-bot integration tests / Lint (flake8 + pep257) (push) Has been cancelled
2026-03-02 09:51:11 -05:00
3d9a47336a
Merge pull request 'feat(fleet): multi-robot SLAM — map sharing + cooperative exploration (Issue #134 )' ( #146 ) from sl-perception/issue-134-multi-robot-slam into main
2026-03-02 09:51:10 -05:00
3992b80bcb
feat(mechanical): universal sensor mount rail system (Issue #138 )
...
Add 2020 T-slot quick-swap sensor rail for SaltyLab/Rover/Tank variants:
- sensor_rail.scad: 2020 T-slot profile, T-nut, thumbscrew, end cap, index
pins, stem/post/tank clamp adapters
- sensor_rail_brackets.scad: universal T-nut base + RPLIDAR A1M8, D435i,
IMX219, UWB anchor, cable clip brackets (tool-free M3 thumbscrew retention)
- sensor_rail_BOM.md: purchased hardware, print settings, export commands
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:46:27 -05:00
ad2b599420
feat(firmware): BNO055 NDOF IMU driver on I2C1 (Issue #135 )
...
Auto-detected alongside MPU6000. Acts as balance primary when MPU6000
fails, or provides NDOF-fused yaw/pitch when both sensors are present.
- include/bno055.h: full API — bno055_init/read/is_ready/calib_status/
temperature/save_offsets/restore_offsets
- src/bno055.c: I2C1 driver; probes 0x28/0x29, resets via SYS_TRIGGER,
enters NDOF mode; 2-burst 12-byte reads (gyro+euler, LIA+gravity);
Euler/gyro/accel scaling (÷16, ÷16, ÷100); auto-saves offsets to
RTC backup regs BKP0R–BKP6R on first full cal; restores on boot
(bno055_is_ready() returns true immediately); temperature updated 1Hz
- include/config.h: BNO055_BKP_MAGIC = 0xB055CA10
- src/main.c: bno055_init() in I2C probe block (before IWDG); imu_calibrated()
macro dispatches mpu6000_is_calibrated() vs bno055_is_ready();
BNO055 read deferred inside balance gate to avoid stalling main loop;
USB JSON reports bno_cs (calib status) and bno_t (temperature)
- test/test_bno055_data.py: 43 pytest tests (43/43 pass) — calib status
bit extraction, Euler/gyro/accel scaling, burst parsing, offset
round-trip packing, temperature signed-byte encoding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:40:44 -05:00
85e5777994
feat(webui): add FLEET tab group to App.jsx — Issue #139
...
Adds FleetPanel import and FLEET tab group (green) to the main
dashboard navigation. Fleet tab is self-contained via useFleet.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:40:27 -05:00
87b45e1b97
feat(firmware): BNO055 NDOF IMU driver on I2C1 (Issue #135 )
...
Auto-detected alongside MPU6000. Acts as balance primary when MPU6000
fails, or provides NDOF-fused yaw/pitch when both sensors are present.
- include/bno055.h: full API — bno055_init/read/is_ready/calib_status/
temperature/save_offsets/restore_offsets
- src/bno055.c: I2C1 driver; probes 0x28/0x29, resets via SYS_TRIGGER,
enters NDOF mode; 2-burst 12-byte reads (gyro+euler, LIA+gravity);
Euler/gyro/accel scaling (÷16, ÷16, ÷100); auto-saves offsets to
RTC backup regs BKP0R–BKP6R on first full cal; restores on boot
(bno055_is_ready() returns true immediately); temperature updated 1Hz
- include/config.h: BNO055_BKP_MAGIC = 0xB055CA10
- src/main.c: bno055_init() in I2C probe block (before IWDG); imu_calibrated()
macro dispatches mpu6000_is_calibrated() vs bno055_is_ready();
BNO055 read deferred inside balance gate to avoid stalling main loop;
USB JSON reports bno_cs (calib status) and bno_t (temperature)
- test/test_bno055_data.py: 43 pytest tests (43/43 pass) — calib status
bit extraction, Euler/gyro/accel scaling, burst parsing, offset
round-trip packing, temperature signed-byte encoding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:40:18 -05:00
4d2b008c48
feat(controls): adaptive PID balance controller with gain scheduling (Issue #136 )
...
Pure modules (no ROS2 dep, fully unit-tested):
- pid_controller.py:
GainSet — (kp,ki,kd) with safety clamp helper
PIDController — anti-windup integral, D-on-error, output clamping
GainScheduler — 3-class weight table (empty/light/heavy), exponential
gain blending (alpha per tick), safety bounds clamping, manual
override, immediate revert-to-defaults on instability
InstabilityDetector — dual criteria: tilt threshold (>50% of window)
+ sign-reversal count (oscillation)
- weight_estimator.py:
WeightEstimator — rolling-window current→weight, steady-state gating
(|tilt|≤threshold), change detection (threshold_kg)
CalibrationRoutine — IDLE→ROCKING→SETTLING→DONE/FAILED state machine;
sinusoidal rocking output, settling current sampling, weight estimate
from avg current; abort() / restart supported
- adaptive_pid_node.py: 100 Hz ROS2 node
Sub: /saltybot/imu (Imu, pitch from quaternion), /saltybot/motor_current
Pub: /saltybot/balance_effort (Float32), /saltybot/weight_estimate,
/saltybot/pid_state (JSON: gains, class, weight_kg, flags)
Srv: /saltybot/calibrate_balance (std_srvs/Trigger)
IMU watchdog (0.5 s), dynamic reconfigure via override_enabled param,
instability → revert + PID reset, structured INFO/WARN logging
- config/adaptive_pid_params.yaml, launch/adaptive_pid.launch.py,
package.xml, setup.py, setup.cfg
- test/test_adaptive_pid.py: 68/68 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:38:46 -05:00
fabb988e72
feat(social): voice command NLU — 30+ intents, confirmation flow (Issue #137 )
...
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 4s
social-bot integration tests / Core integration tests (mock sensors, no GPU) (push) Has been skipped
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 3s
social-bot integration tests / Core integration tests (mock sensors, no GPU) (pull_request) Has been skipped
social-bot integration tests / Latency profiling (GPU, Orin) (pull_request) Has been cancelled
social-bot integration tests / Latency profiling (GPU, Orin) (push) Has been cancelled
## New files
- saltybot_social_msgs/msg/VoiceCommand.msg
intent + entities[] + confidence + confirmation_token + requires_confirmation
- saltybot_social/voice_command_parser.py
Pure-Python regex NLU, zero ROS2/ML deps, < 1 ms/call
30+ named intents across nav.*, social.*, system.*, config.*, confirm.*
Entity extraction: location, name, mode, level, route name
Dangerous-command flag: system.shutdown, system.restart, social.forget_me
- saltybot_social/voice_command_node.py
Subscribes /social/speech/transcript, publishes /social/voice_command
Confirmation flow with UUID token + 10 s timeout
Below-threshold → intent=fallback → LLM conversation engine
- saltybot_social/test/test_voice_command_parser.py
191 unit tests (all pass), no ROS2 runtime required
- saltybot_social/config/voice_command_params.yaml
- saltybot_social/launch/voice_command.launch.py
## Intent taxonomy
nav: go_to, go_home, follow_me, stop, wait, come_here, patrol,
set_mode (shadow/lead/side/orbit/loose/tight),
teach_route, stop_teaching, replay_route
social: remember_me, forget_me [CONFIRM], whats_my_name, tell_joke
system: battery_status, map_status, shutdown [CONFIRM], restart [CONFIRM],
volume_up, volume_down, volume_set
config: personality, sass_level, follow_mode
## Updated
- saltybot_social_msgs/CMakeLists.txt: register VoiceCommand.msg
- saltybot_social/setup.py: add voice_command_node entry point
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:38:17 -05:00
b09f17b787
feat(webui): fleet management dashboard — Issue #139
...
- useFleet.js: multi-robot ROSLIB.Ros multiplexer with localStorage
persistence; per-robot balance_state/control_mode/odom/diagnostics
subscriptions; sendGoal, sendPatrol, scanSubnet helpers
- FleetPanel.jsx: fleet sub-views (Robots/Map/Missions/Video/Alerts)
plus RobotDetail overlay reusing ImuPanel/BatteryPanel/MotorPanel/
ControlMode/SystemHealth via subscribeRobot adapter pattern
- App.jsx: adds FLEET tab group pointing to FleetPanel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:37:10 -05:00
b9b866854f
feat(fleet): multi-robot SLAM — map sharing + cooperative exploration (Issue #134 )
...
New packages:
saltybot_fleet_msgs — 4 msgs (RobotInfo, MapChunk, Frontier, FrontierArray)
+ 2 srvs (RegisterRobot, RequestFrontier)
saltybot_fleet — 4 nodes + 2 launch files + config + CLI tool
Nodes:
map_broadcaster_node — zlib-compress local OccupancyGrid → /fleet/maps @ 1Hz
+ /fleet/robots heartbeat with battery/status
fleet_manager_node — robot registry, MapMerger (multi-grid SE2-aligned merge),
frontier aggregation, /fleet/request_frontier service,
heartbeat timeout + stale frontier re-assignment
frontier_detector_node — scipy label-based frontier detection on merged map
→ /fleet/exploration_frontiers_raw
fleet_explorer_node — Nav2 NavigateToPose cooperative exploration state machine:
IDLE→request→NAVIGATING→ARRIVED→IDLE + STALLED backoff
Supporting modules:
map_compressor.py — binary serialise + zlib OccupancyGrid encode/decode
map_merger.py — SE(2)-transform-aware multi-grid merge with conservative
obstacle inflation (occupied beats free on conflict)
frontier_detector.py — numpy frontier mask + scipy connected-components + scoring
landmark_aligner.py — ArUco-landmark SE(2) estimation (Horn 2D least-squares)
to align robot map frames into common fleet_map frame
Topic layout:
/fleet/maps MapChunk per-robot compressed grids
/fleet/robots RobotInfo heartbeats + status
/fleet/merged_map OccupancyGrid coordinator merged output
/fleet/exploration_frontiers FrontierArray consolidated frontiers
/fleet/status String (JSON) coordinator health
/<robot_ns>/rtabmap/map input per robot
/<robot_ns>/rtabmap/odom input per robot
/<robot_ns>/navigate_to_pose Nav2 action per robot
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:36:17 -05:00
9281f3bc44
Merge pull request 'feat(bridge): battery management node — SoC, alerts, speed limits (Issue #125 )' ( #133 ) from sl-jetson/issue-125-battery-management into main
2026-03-02 09:27:53 -05:00
ea26cda76a
feat(bridge): battery management node (Issue #125 )
...
Add battery_node.py:
- Subscribes /saltybot/telemetry/battery (JSON from stm32_cmd_node)
- Publishes sensor_msgs/BatteryState on /saltybot/battery at 1 Hz
- SoC source priority: STM32 fuel gauge soc_pct field → fallback to
3S LiPo voltage curve (12-point lookup with linear interpolation)
- Charging detection: current_ma < -100 mA threshold
- Alert levels: WARNING (20%)→speed 60%, CRITICAL (10%)→speed 30%,
EMERGENCY (5%)→zero /cmd_vel + /saltybot/arm(False) disarm
- /saltybot/battery/alert JSON topic on threshold crossings
- /saltybot/speed_limit Float32 (0.0-1.0) for nav speed capping
- SQLite history logging: /var/log/saltybot/battery.db, 7-day retention
- Hourly prune timer to keep DB bounded
Add test_battery.py (70+ tests, no ROS2 runtime):
- SoC lookup: all curve points, interpolation, clamping, 3S/4S packs
- Alert level thresholds and transitions for all levels
- Speed factor assignments per alert level
- Charging detection logic
- sensor_msgs/BatteryState field population
- SQLite insert/retrieve/prune (in-memory and on-disk)
- JSON telemetry parsing: normal, charging, soc_pct=0 fallback
Add battery_params.yaml, battery.launch.py.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:27:06 -05:00
6da4ae885d
Merge pull request 'feat(firmware): Jetson binary serial protocol on USART1 (Issue #120 )' ( #132 ) from sl-firmware/issue-120-serial-protocol into main
2026-03-02 09:26:45 -05:00
0d47632c0a
Merge pull request 'feat: SaltyTank tracked chassis — drive sprockets, tensioners, skid plate ( #121 )' ( #131 ) from sl-mechanical/issue-121-tank-chassis into main
2026-03-02 09:26:40 -05:00
8f0cb8025b
Merge pull request 'feat(ui): telemetry dashboard panels (issue #126 )' ( #130 ) from sl-webui/issue-126-telemetry-dash into main
2026-03-02 09:26:34 -05:00
2e41f05c69
Merge pull request 'feat(mapping): RTAB-Map persistence + multi-session mapping (Issue #123 )' ( #129 ) from sl-perception/issue-123-map-persistence into main
2026-03-02 09:26:29 -05:00
5cfacdda3f
Merge pull request 'feat(bridge): binary STM32 command protocol (Issue #119 )' ( #128 ) from sl-jetson/issue-119-cmd-protocol into main
2026-03-02 09:26:20 -05:00
89c7c5fa3e
Merge pull request 'feat(tank): SaltyTank tracked-vehicle ESC driver (Issue #122 )' ( #127 ) from sl-controls/issue-122-tank-driver into main
2026-03-02 09:26:19 -05:00
6f0ad8e92e
feat(firmware): Jetson binary serial protocol on USART1 (Issue #120 )
...
New jlink module replaces ASCII-over-USB-CDC jetson_cmd with a dedicated
hardware UART binary protocol at 921600 baud for reliable Jetson comms.
- include/jlink.h: JLinkState struct, jlink_tlm_status_t (20-byte packed),
command/telemetry IDs (0x01-0x07 cmd, 0x80 status), API declarations
- src/jlink.c: USART1 DMA2_Stream2_Channel4 circular RX (128 bytes),
IDLE interrupt, CRC16-XModem (poly 0x1021) frame parser state machine,
command dispatch (HEARTBEAT/DRIVE/ARM/DISARM/PID_SET/ESTOP),
jlink_send_telemetry() blocking TX (≈0.28 ms per frame)
- include/config.h: JLINK_BAUD=921600, JLINK_HB_TIMEOUT_MS=1000,
JLINK_TLM_HZ=50, FW_MAJOR/MINOR/PATCH version constants
- src/main.c: jlink_init(), jlink_process() in main loop, arm/disarm/
estop/PID flag handling, 50 Hz STATUS telemetry TX, jlink takes
priority over legacy jetson_cmd for speed/steer injection
- test/test_jlink_frames.py: 39 pytest tests (39/39 pass) — CRC16,
frame building, parser state machine, drive/PID/status encoding
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:22:34 -05:00
d93919e26f
feat: SaltyTank tracked chassis — drive sprockets, tensioners, skid plate ( #121 )
...
Three new chassis design files for the SaltyTank continuous-track variant:
• saltytank_chassis.scad — Deck plate (500×360×8mm Al, DXF export), 2×
side track frames (6mm Al, CNC/laser), idler tensioner sliding block,
4× CSI corner camera mounts (45°/20°), D435i front bracket (8° tilt),
stem collar (Ø25mm shared). Drive sprocket mounts accept hoverboard hub
motors with caliper-verified D-cut bore (16.11mm/13mm flat) + 52mm BC
hub flange bolt pattern. M6 tensioner bolt adjusts idler ±15mm for
track tension. Shared FC 30.5×30.5mm + Jetson 58×49mm M3 patterns.
Electronics bay footprint matches rover_electronics_bay.scad exactly.
• saltytank_skid_plate.scad — Sacrificial underside skid panel (360×500mm).
4mm HDPE (DXF) or PETG print; countersunk M4 FHCS bolt-on. 4× drain/
inspection slots; optional printed ribs (RIB_PRINT=true). Ground
clearance of hull between tracks: 90mm (exceeds 50mm requirement).
• saltytank_BOM.md — Full BOM: deck plate, side frames, drive sprockets,
idler wheels + tensioners, road wheels (2/side), track belts (1109mm
circumference calc), skid plate, sensor brackets, electronics bay
(rover_electronics_bay.scad reused unchanged). Frame mass ≈ 2.98 kg
(just under 3 kg target). Assembly sequence and track tensioning
procedure included.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:20:41 -05:00
78374668bf
feat(ui): telemetry dashboard panels (issue #126 )
...
Adds 6 telemetry tabs to the social-bot dashboard extending PR #112 .
IMU Panel (/saltybot/imu, /saltybot/balance_state):
- Canvas artificial horizon + compass tape
- Three.js 3D robot orientation cube with quaternion SLERP
- Angular velocity readouts, balance state detail
Battery Panel (/diagnostics):
- Voltage, SoC, estimated current, runtime estimate
- 120-point voltage history sparkline (2 min)
- Reads battery_voltage_v, battery_soc_pct KeyValues from DiagnosticArray
Motor Panel (/saltybot/balance_state, /saltybot/rover_pwm):
- Auto-detects balance vs rover mode
- Bidirectional duty bars for left/right motors
- Motor temp from /diagnostics, PID detail for balance bot
Map Viewer (/map, /odom, /outdoor/route):
- OccupancyGrid canvas renderer (unknown/free/occupied colour-coded)
- Robot position + heading arrow, Nav2/OSM path overlay (dashed)
- Pan (mouse/touch) + zoom, 2 m scale bar
Control Mode (/saltybot/control_mode):
- RC / RAMP_TO_AUTO / AUTO / RAMP_TO_RC state badge
- Blend alpha progress bar
- Safety flags: SLAM ok, RC link ok, stick override active
- State machine diagram
System Health (/diagnostics):
- CPU/GPU temperature gauges with colour-coded bars
- RAM, GPU memory, disk resource bars
- ROS2 node status list sorted by severity (ERROR → WARN → OK)
Also:
- Three.js vendor chunk split (471 kB separate lazy chunk)
- Updated App.jsx with grouped SOCIAL + TELEMETRY tab nav
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:18:39 -05:00
9341e9d986
feat(mapping): RTAB-Map persistence + multi-session + map management (Issue #123 )
...
- Add saltybot_mapping package: MapDatabase, MapExporter, MapManagerNode
- 6 ROS2 services: list/save_as/load/delete maps + export occupancy/pointcloud
- Auto-save current.db every 5 min; keep last 5 autosaves; warn at 2 GB
- Update rtabmap_params.yaml: database_path, Mem/InitWMWithAllNodes=true,
Rtabmap/StartNewMapOnLoopClosure=false (multi-session persistence by default)
- Update slam_rtabmap.launch.py: remove --delete_db_on_start, add fresh_start
arg (deletes DB before launch) and database_path arg (load named map)
- CLI tools: backup_map.py, export_map.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:17:54 -05:00
972bc8cb27
chore(bridge): update setup.py for stm32_cmd_node and battery_node entries
...
Add stm32_cmd_node and battery_node console_scripts, new launch/config files
to data_files list.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:17:13 -05:00
30be0d8cd3
feat(bridge): binary STM32 command protocol (Issue #119 )
...
Add stm32_protocol.py — pure-Python binary frame codec:
- Frame: STX(0x02)+TYPE+LEN+PAYLOAD+CRC16-CCITT(BE)+ETX(0x03)
- CRC covers TYPE+LEN+PAYLOAD; polynomial 0x1021, init 0xFFFF
- Encoders: HEARTBEAT, SPEED_STEER(-1000..+1000 int16), ARM, SET_MODE, PID_UPDATE
- Telemetry decoders: ImuFrame, BatteryFrame, MotorRpmFrame, ArmStateFrame, ErrorFrame
- FrameParser: streaming byte-by-byte state machine, resync on corrupt data
Add stm32_cmd_node.py — ROS2 bidirectional bridge node:
- /cmd_vel → SPEED_STEER at up to 50 Hz
- HEARTBEAT timer (default 200ms); STM32 watchdog fires at 500ms
- Jetson-side watchdog: no /cmd_vel for 500ms → send SPEED_STEER(0,0)
- /saltybot/arm service (SetBool) → ARM frame
- /saltybot/pid_update topic → PID_UPDATE frame
- Telemetry RX: IMU→/saltybot/imu, BATTERY→/saltybot/telemetry/battery,
MOTOR_RPM→/saltybot/telemetry/motor_rpm, ARM_STATE→/saltybot/arm_state,
ERROR→/saltybot/error
- Auto-reconnect on USB disconnect (serial.SerialException caught)
- Zero-speed + disarm sent on node shutdown
- /diagnostics with serial health, frame counts, last cmd age
Add test_stm32_protocol.py (60+ tests):
- CRC16-CCITT correctness, known test vectors
- All encoder output structures and payload values
- FrameParser: all telemetry types, bad CRC, bad ETX, resync, streaming,
oversized payload, frame counters, reset
Add test_stm32_cmd_node.py (30+ tests):
- MockSerial: TX/RX byte-level testing without hardware
- Speed/steer clamping, scaling, frame structure
- Watchdog fires/doesn't fire relative to timeout
- CRC error counted, resync after garbage
Add stm32_cmd_params.yaml, stm32_cmd.launch.py.
Update package.xml (add std_srvs, geometry_msgs deps).
Update setup.py (add stm32_cmd_node entry point + new config/launch).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:17:04 -05:00
413810e6ba
feat(tank): SaltyTank tracked-vehicle ESC driver (Issue #122 )
...
- kinematics.py: tank_speeds with slip compensation (angular_cmd scaled
by 1/(1-slip_factor)), skid_steer_speeds (no slip), speed_to_pwm,
compute_track_speeds (2wd|4wd|tracked modes, ±max clip), expand_to_4ch,
odometry_from_track_speeds (angular scaled by (1-slip_factor) — inverse
of command path, consistent dead-reckoning across all slip values)
- tank_driver_node.py: 50 Hz ROS2 node; serial P<ch1>,<ch2>,<ch3>,<ch4>\n;
H\n heartbeat; dual stop paths (watchdog 0.3s + /saltybot/e_stop topic,
latching); runtime drive_mode + slip_factor param switch; dead-reckoning
odometry with slip compensation; publishes /saltybot/tank_pwm (JSON) +
/saltybot/tank_odom
- config/tank_params.yaml, launch/tank_driver.launch.py, package.xml,
setup.py, setup.cfg
- test/test_tank_kinematics.py: 71 unit tests, all passing (includes
parametric round-trip over slip ∈ {0.0, 0.1, 0.3, 0.5})
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:14:37 -05:00
7de55accc3
Merge pull request 'feat(rover): SaltyRover 4-wheel ESC motor driver (Issue #110 )' ( #117 ) from sl-controls/issue-110-rover-driver into main
2026-03-02 09:04:01 -05:00
3c438595e8
feat(rover): SaltyRover 4-wheel ESC motor driver (Issue #110 )
...
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 2s
social-bot integration tests / Core integration tests (mock sensors, no GPU) (pull_request) Has been skipped
social-bot integration tests / Latency profiling (GPU, Orin) (pull_request) Has been cancelled
- kinematics.py: pure unicycle→differential/skid-steer kinematics,
speed_to_pwm (1000–2000µs), compute_wheel_speeds with ±max clip,
odometry_from_wheel_speeds inverse helper
- rover_driver_node.py: 50 Hz ROS2 node; serial P<ch1>,<ch2>,<ch3>,<ch4>\n
protocol; heartbeat H\n; deadman on /cmd_vel silence; runtime 2WD/4WD
variant switch via four_wheel param; dead-reckoning odometry;
publishes /saltybot/rover_pwm (JSON) + /saltybot/rover_odom
- config/rover_params.yaml, launch/rover_driver.launch.py, package.xml,
setup.py, setup.cfg
- test/test_rover_kinematics.py: 51 unit tests, all passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 09:03:28 -05:00
f278f0fd06
Merge pull request 'feat(tests): social-bot integration test suite (Issue #108 )' ( #118 ) from sl-jetson/issue-108-integration-tests into main
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 1m8s
social-bot integration tests / Core integration tests (mock sensors, no GPU) (push) Has been skipped
social-bot integration tests / Latency profiling (GPU, Orin) (push) Has been cancelled
2026-03-02 09:03:18 -05:00
5bb1ec6d3e
Merge pull request 'feat: SaltyRover chassis Rev 2 — 4-wheel rover with spring suspension ( #109 )' ( #116 ) from sl-mechanical/issue-109-rover-chassis into main
2026-03-02 09:03:12 -05:00
ee8438fd04
feat(tests): social-bot integration test suite (Issue #108 )
...
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 3m58s
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 3m3s
social-bot integration tests / Core integration tests (mock sensors, no GPU) (push) Has been skipped
social-bot integration tests / Core integration tests (mock sensors, no GPU) (pull_request) Has been skipped
social-bot integration tests / Latency profiling (GPU, Orin) (push) Has been cancelled
social-bot integration tests / Latency profiling (GPU, Orin) (pull_request) Has been cancelled
Add saltybot_social_tests package with full pytest + launch_testing harness:
- test_launch.py: start social_test.launch.py, verify all nodes up within 30s
- test_topic_rates.py: measure topic Hz over 3s window vs. minimum SLAs
- test_services.py: /social/enroll, /social/nav/set_mode, person CRUD, mood query
- test_gpu_memory.py: total allocation < 6 GB, no leak over 30s
- test_latency.py: inject→transcript→LLM→TTS state-machine SLA profiling
- test_shutdown.py: no zombies, GPU memory released, audio device freed
- test_helpers.py: TopicRateChecker, NodeChecker, ServiceChecker, GpuMemoryChecker
- mock_sensors.py: camera/faces/fused/persons/uwb publishers at correct rates
- social_test.launch.py: CI-mode launch (no mic/speaker, mock sensors auto-started)
- conftest.py + pytest.ini: gpu_required / full_stack / stack_running markers
- docker/Dockerfile.ci + docker-compose.ci.yml: CPU-only CI container
- docker/entrypoint-ci.sh: launch stack + wait 10s + run pytest
- bags/record_social_test.sh + bags/README.md: rosbag recording for replay
- .gitea/workflows/social-tests-ci.yml: lint + core-tests + latency-gpu jobs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 08:50:22 -05:00
2fa72e169e
feat: SaltyRover chassis Rev 2 — 4-wheel rover with spring suspension ( #109 )
...
New chassis design files for the SaltyRover rough-terrain variant:
• saltyrover_chassis_r2.scad — Deck plate (500×480×6mm Al, laser-cut DXF),
4× M3-adjustable pivot brackets, 4× CSI corner camera mounts (45° outward,
20° down), D435i front bracket (8° tilt), stem collar. All RENDER modes
for STL and DXF export included.
• rover_spring_arm.scad — Trailing-arm spring suspension (×4). Pivot on M8
bolt; captured 14mm OD compression spring (50mm free, ~5 N/mm); open-end
axle dropout slot with retainer cap. Provides 25mm bump + 15mm droop travel.
Bearing-seat recess for caliper-verified 37.8mm collar OD.
• rover_electronics_bay.scad — PETG electronics bay (240×200×80mm internal).
FC standoffs 30.5×30.5mm M3 and Jetson Orin 58×49mm M3 — shared SaltyLab
swappable pattern. Ventilation slots all 4 walls + lid. Lid integrates
100mm RPLIDAR A1M8 tower (58mm BC, matched to rplidar_mount.scad).
Split-print halves for 220mm beds included.
• rover_chassis_r2_BOM.md — Full BOM, mass estimate (frame ~2.15kg; reduce
to <2kg by setting DECK_T=5), assembly sequence, critical dimensions.
Sensor positions: RPLIDAR top-centre on bay lid, D435i front, 4× IMX219 at
deck corners. Shares 30.5mm FC + 58mm Jetson + Ø25mm stem patterns with
SaltyLab for swappable electronics.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 08:42:44 -05:00