9d12805843
feat(bringup): visual odometry drift detector (Issue #260 )
...
Adds sliding-window drift detector that compares cumulative path lengths
of visual odom and wheel odom over a configurable window (default 10 s).
Drift = |vo_path − wheel_path|; flagged when ≥ 0.5 m (configurable).
OdomBuffer handles per-source rolling storage with automatic age eviction.
Publishes Bool on /saltybot/vo_drift_detected and Float32 on
/saltybot/vo_drift_magnitude at 2 Hz. 27/27 pure-Python tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 13:26:07 -05:00
32857435a1
feat(bringup): floor surface type classifier on D435i RGB (Issue #249 )
...
Adds multi-feature nearest-centroid classifier for 6 surface types:
carpet, tile, wood, concrete, grass, gravel. Features: circular hue mean,
saturation mean/std, brightness, Laplacian texture variance, Sobel edge
density — all extracted from the bottom 40% of each frame (floor ROI).
Majority-vote temporal smoother (window=5) suppresses single-frame noise.
Publishes std_msgs/String on /saltybot/floor_type at 2 Hz.
34/34 pure-Python tests pass (no ROS2 required).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 12:51:14 -05:00
ff34f5ac43
feat(bringup): LIDAR Euclidean object clustering + RViz visualisation (Issue #239 )
...
Adds gap-based Euclidean distance clustering of /scan LaserScan points.
Each cluster is published as a labelled semi-transparent CUBE + TEXT marker
in /saltybot/lidar_clusters (MarkerArray), sorted nearest-first. Stale
markers from shrinking cluster counts are explicitly deleted each cycle.
22/22 pure-Python tests pass (no ROS2 required).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 12:21:35 -05:00
b722279739
feat(bringup): scan height filter with IMU pitch compensation (Issue #211 )
...
Two files added to saltybot_bringup:
- _scan_height_filter.py: pure-Python helpers (no rclpy) —
filter_scan_by_height() projects each LIDAR ray to world-frame height
using pitch/roll from the IMU and filters ground/ceiling returns;
pitch_roll_from_accel() uses convention-agnostic atan2 formula;
AttitudeEstimator low-pass filters the accelerometer attitude.
- scan_height_filter_node.py: subscribes /scan + /camera/imu, publishes
/scan_filtered (LaserScan) for Nav2 at source rate (up to 20 Hz).
setup.py: adds scan_height_filter entry point.
18/18 unit tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 11:50:56 -05:00
e6065e1531
feat(jetson): camera health watchdog node (issue #198 )
...
Adds camera_health_node.py + _camera_state.py to saltybot_bringup:
• _camera_state.py — pure-Python CameraState dataclass (no ROS2):
on_frame(), age_s, fps(window_s), status(),
should_reset() + mark_reset() with 30s cooldown
• camera_health_node.py — subscribes 6 image topics (D435i color/depth
+ 4× IMX219 CSI front/right/rear/left);
1 Hz tick: WARNING at >2s silence, ERROR at
>10s + v4l2 stream-off/on reset for CSI cams;
publishes /saltybot/camera_health JSON with
per-camera status, age_s, fps, total_frames
• test/test_camera_health.py — 15 unit tests (15/15 pass, no ROS2 needed)
• setup.py — adds camera_health_monitor console_scripts entry point
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 11:11:48 -05:00
c26293d000
feat(jetson): depth confidence filter node (issue #190 )
...
Adds depth_confidence_filter_node.py to saltybot_bringup:
- Synchronises /camera/depth/image_rect_raw + /camera/depth/confidence
via ApproximateTimeSynchronizer (10ms slop)
- Zeros pixels where confidence uint8 < threshold * 255 (default 0.5)
- Republishes filtered float32 depth on /camera/depth/filtered
- Registered as depth_confidence_filter console_scripts entry point
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 11:02:15 -05:00
57420807ca
feat(webui): live camera viewer — multi-stream + detection overlays (Issue #177 )
...
UI (src/hooks/useCamera.js, src/components/CameraViewer.jsx):
- 7 camera sources: front/left/rear/right CSI, D435i RGB/depth, panoramic
- Compressed image subscription via rosbridge (sensor_msgs/CompressedImage)
- Client-side 15fps gate (drops excess frames, reduces JS pressure)
- Per-camera FPS indicator with quality badge (FULL/GOOD/LOW/NO SIGNAL)
- Detection overlays: face boxes + names (/social/faces/detections),
gesture icons (/social/gestures), scene object labels + hazard colours
(/social/scene/objects); overlay mode selector (off/faces/gestures/objects/all)
- 360° panoramic equirect viewer with mouse/touch drag azimuth pan
- Picture-in-picture: up to 3 pinned cameras via ⊕ button
- One-click recording (MediaRecorder → MP4/WebM download)
- Snapshot to PNG with detection overlay composite + timestamp watermark
- Cameras tab added to TELEMETRY group in App.jsx
Jetson (rosbridge bringup):
- rosbridge_params.yaml: whitelist + /camera/depth/image_rect_raw/compressed,
/camera/panoramic/compressed, /social/faces/detections,
/social/gestures, /social/scene/objects
- rosbridge.launch.py: D435i colour republisher (JPEG 75%) +
depth republisher (compressedDepth/PNG16 preserving uint16 values)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:47:01 -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
039355d5bb
feat: full_stack.launch.py — one-command autonomous stack bringup
...
Adds saltybot_bringup/launch/full_stack.launch.py: a single launch file
that brings up the entire SaltyBot software stack in dependency order,
with mode selection (indoor / outdoor / follow).
Launch sequence (wall-clock delays):
t= 0s robot_description (URDF + TF)
t= 0s STM32 bidirectional serial bridge
t= 2s sensors (RPLIDAR A1M8 + RealSense D435i)
t= 2s cmd_vel safety bridge (deadman + ramp + AUTONOMOUS gate)
t= 4s UWB driver (MaUWB DW3000 anchors on USB)
t= 4s CSI cameras — 4x IMX219 (optional, enable_csi_cameras:=true)
t= 6s SLAM — RTAB-Map RGB-D+LIDAR (indoor only)
t= 6s Outdoor GPS nav (outdoor only)
t= 6s YOLOv8n person detection (TensorRT)
t= 9s Person follower (UWB primary + camera fusion)
t=14s Nav2 navigation stack (indoor only)
t=17s rosbridge WebSocket server (port 9090)
Modes:
indoor — SLAM + Nav2 + full sensor suite + follow + UWB (default)
outdoor — GPS nav + sensors + follow + UWB (no SLAM)
follow — sensors + UWB + perception + follower only
Launch arguments:
mode, use_sim_time, enable_csi_cameras, enable_uwb, enable_perception,
enable_follower, enable_bridge, enable_rosbridge, follow_distance,
max_linear_vel, uwb_port_a, uwb_port_b, stm32_port
Also updates saltybot_bringup/package.xml:
- Adds exec_depend for all saltybot_* packages included by full_stack
- Updates maintainer to sl-jetson
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 00:56:39 -05:00
6420e07487
feat: rosbridge WebSocket server for web UI (port 9090)
...
Adds rosbridge_suite to the Jetson stack so the browser dashboard can
subscribe to ROS2 topics via roslibjs over ws://jetson:9090.
docker-compose.yml
New service: saltybot-rosbridge
- Runs saltybot_bringup/launch/rosbridge.launch.py
- network_mode: host → port 9090 directly reachable on Jetson LAN
- Depends on saltybot-ros2, stm32-bridge, csi-cameras
saltybot_bringup/launch/rosbridge.launch.py
- rosbridge_websocket node (port 9090, params from rosbridge_params.yaml)
- 4× image_transport/republish nodes: compress CSI camera streams
/camera/<name>/image_raw → /camera/<name>/image_raw/compressed (JPEG 75%)
saltybot_bringup/config/rosbridge_params.yaml
Whitelisted topics:
/map /scan /tf /tf_static
/saltybot/imu /saltybot/balance_state
/cmd_vel
/person/*
/camera/*/image_raw/compressed
max_message_size: 10 MB (OccupancyGrid headroom)
saltybot_bringup/SENSORS.md
Added rosbridge connection section with roslibjs snippet,
topic reference table, bandwidth estimates, and throttle_rate tips.
saltybot_bringup/package.xml
Added exec_depend: rosbridge_server, image_transport,
image_transport_plugins (all already installed in Docker image).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 00:22:02 -05:00
772a70b545
feat: Nav2 path planning + obstacle avoidance (Phase 2b)
...
Integrates Nav2 autonomous navigation stack with RTAB-Map SLAM on Orin
Nano Super. No AMCL/map_server needed — RTAB-Map provides /map + TF.
New files:
- jetson/config/nav2_params.yaml DWB controller,
NavFn planner, RPLIDAR obstacle layer, RealSense voxel layer;
10Hz local / 5Hz global costmap; robot_radius 0.15m, max_vel 1.0 m/s
- jetson/ros2_ws/src/saltybot_bringup/launch/nav2.launch.py
wraps nav2_bringup navigation_launch with saltybot params + BT XML
- jetson/ros2_ws/src/saltybot_bringup/behavior_trees/
navigate_to_pose_with_recovery.xml BT: replan@1Hz, DWB follow,
recovery: clear maps → spin 90° → wait 5s → back up 0.30m
Updated:
- jetson/docker-compose.yml add saltybot-nav2 service
(depends_on: saltybot-ros2)
- jetson/ros2_ws/src/saltybot_bringup/setup.py install behavior_trees/*.xml
- jetson/ros2_ws/src/saltybot_bringup/package.xml add rtabmap_ros + nav2_bringup
- projects/saltybot/SLAM-SETUP-PLAN.md Phase 2b ✅ Done
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 22:54:24 -05:00
c5d6a72d39
feat: update SLAM stack for Jetson Orin Nano Super (67 TOPS, JetPack 6)
...
Platform upgrade: Jetson Nano 4GB → Orin Nano Super 8GB (March 1, 2026)
All Nano-era constraints removed — power/rate/resolution limits obsolete.
Dockerfile: l4t-jetpack:r36.2.0 (JetPack 6 / Ubuntu 22.04 / CUDA 12.x),
ROS2 Humble via native apt, added ros-humble-rtabmap-ros,
ros-humble-v4l2-camera for future IMX219 CSI (Phase 2c)
New: slam_rtabmap.launch.py — Orin primary SLAM entry point
RTAB-Map with subscribe_scan (RPLIDAR) + subscribe_rgbd (D435i)
Replaces slam_toolbox as docker-compose default
New: config/rtabmap_params.yaml — Orin-optimized
DetectionRate 10Hz, MaxFeatures 1000, Grid/3D true,
TimeThr 0 (no limit), Mem/STMSize 0 (unlimited)
Updated: config/realsense_d435i.yaml — 848x480x30, pointcloud enabled
Updated: config/slam_toolbox_params.yaml — 10Hz rate, 1s map interval
Updated: SLAM-SETUP-PLAN.md — full rewrite for Orin: arch diagram,
Phase 2c IMX219 plan (4x 160° CSI surround), 25W power budget
docker-compose.yml: image tag jetson-orin, default → slam_rtabmap.launch.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 21:46:27 -05:00
76067d6d89
feat(bd-a2j): RealSense D435i + RPLIDAR A1M8 ROS2 driver integration
...
Adds saltybot_bringup ROS2 package with four launch files:
- realsense.launch.py — D435i at 640x480x15fps, IMU unified topic
- rplidar.launch.py — RPLIDAR A1M8 via /dev/rplidar udev symlink
- sensors.launch.py — both sensors + static TF (base_link→laser/camera)
- slam.launch.py — sensors + slam_toolbox online_async (compose entry point)
Sensor config YAMLs (mounted at /config/ in container):
- realsense_d435i.yaml — Nano power-budget settings (15fps, no pointcloud)
- rplidar_a1m8.yaml — Standard scan mode, 115200 baud, laser frame
- slam_toolbox_params.yaml — Nano-tuned (2Hz processing, 5cm resolution)
Fixes docker-compose volume mount: ./ros2_ws/src:/ros2_ws/src
(was ./ros2_ws:/ros2_ws/src — would have double-nested the src directory)
Topic reference and verification commands in SENSORS.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 17:14:21 -05:00