e35bd949c0
feat: Integrate 360° LIDAR obstacle avoidance into full_stack (Issue #364 )
...
Adds saltybot_lidar_avoidance node to the launch sequence at t=3s.
The RPLIDAR A1M8 node was already fully implemented with:
- Emergency stop if obstacle detected within 0.5m
- Speed-dependent safety zones (0.6m @ 0 m/s → 3.0m @ 5.56 m/s)
- Forward scanning window (±30° cone)
- Debounced obstacle detection (2-frame debounce)
- Publishes /cmd_vel_safe with filtered velocity commands
Integrates seamlessly with Nav2 stack and cmd_vel_mux multiplexer.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 12:23:43 -05:00
93756f5248
feat: Add FC↔Orin UART verification (Issue #362 )
...
Implements UART bridge verification between Flight Controller (STM32F722)
and Jetson Orin.
Changes:
1. jetson/scripts/uart_test.py (12.7 KB)
- Opens /dev/ttyTHS1 at 921600 baud
- Sends jlink binary test frames (PING, VERSION, ECHO)
- Verifies CRC16-CCITT frame integrity
- Logs transactions with timestamps
- JSON result export and optional MQTT publishing
2. jetson/ros2_ws/src/saltybot_bridge/launch/uart_bridge.launch.py
- ROS2 launch file for serial_bridge_node on UART port
- Configurable port (default /dev/ttyTHS1), baud rate (921600)
- Bridges FC telemetry to /saltybot/imu, /saltybot/balance_state
- Publishes diagnostics to /diagnostics
Usage:
Test: sudo python3 jetson/scripts/uart_test.py
Launch: ros2 launch saltybot_bridge uart_bridge.launch.py
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 12:17:56 -05:00
7cfb98aec1
feat: Add pan/tilt camera head servo control (Issue #384 )
...
Implement SCS/STS serial protocol driver for Waveshare ST3215 servos
at 1Mbps daisy-chain configuration. Pan and tilt servos on single UART.
Features:
- SCSServoBus class: Low-level protocol handler with packet construction
- Position write commands with configurable speed (0-1000)
- Position and temperature readback from servos
- PanTiltNode: ROS2 node with target tracking control loop
- Subscribes to /saltybot/target_track for centroid position
- Proportional control to keep target centered in D435i FOV
- Publishes /pan_tilt/state with angles and temperatures
- Publishes /pan_tilt/command for servo position monitoring
- 30 Hz control loop, 1 Hz telemetry loop
- Configurable servo limits and speeds
Servos: 0.24° resolution, 0-4095 position range
Camera: 87° × 58° field of view
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 08:44:51 -05:00
a11722e872
feat: Implement VESC UART driver node (Issue #383 )
...
ROS2 driver for Flipsky FSESC 4.20 Plus (VESC dual ESC) motor control.
Replaces hoverboard ESC communication with pyvesc library.
Features:
- UART serial communication (configurable port/baud)
- Dual command modes: duty_cycle (-100 to 100) and RPM setpoint
- Telemetry publishing: voltage, current, RPM, temperature, fault codes
- Command timeout: auto-zero throttle if no cmd_vel received
- Heartbeat-based connection management
- Comprehensive error handling and logging
Topics:
- Subscribe: /cmd_vel (geometry_msgs/Twist)
- Publish: /vesc/state (JSON telemetry)
- Publish: /vesc/raw_telemetry (debug)
Launch: ros2 launch saltybot_vesc_driver vesc_driver.launch.py
Config: config/vesc_params.yaml
Next phase: Integrate with cmd_vel_mux + safety layer.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 07:05:46 -05:00
5604670646
feat: Implement deaf/accessibility mode with STT, touch keyboard, TTS (Issue #371 )
...
Accessibility mode for hearing-impaired users:
- Speech-to-text display: Integrates with saltybot_social speech_pipeline_node
- Touch keyboard overlay: 1024x600 optimized for MageDok 7in display
- TTS output: Routes to MageDok speakers via PulseAudio
- Web UI server: Responsive keyboard interface with real-time display updates
- Auto-confirm: Optional TTS feedback for spoken input
- Physical keyboard support: Both touch and physical input methods
Features:
- Keyboard buffer with backspace/clear/send controls
- Transcript history display (max 10 entries)
- Status indicators for STT/TTS ready state
- Number/symbol support (1-5, punctuation)
- HTML/CSS responsive design optimized for touch
- ROS2 integration via /social/speech/transcript and /social/conversation/request
Launch: ros2 launch saltybot_accessibility_mode accessibility_mode.launch.py
UI Port: 8080 (MageDok display access)
Config: config/accessibility_params.yaml
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 18:17:41 -05:00
b942bb549a
Merge pull request 'feat: 360° LIDAR obstacle avoidance (Issue #364 )' ( #380 ) from sl-controls/issue-364-lidar-avoidance into main
2026-03-03 18:15:28 -05:00
cfa8ee111d
Merge pull request 'feat: Replace GNOME with Cage+Chromium kiosk (Issue #374 )' ( #377 ) from sl-webui/issue-374-cage-kiosk into main
2026-03-03 17:46:14 -05:00
aeb90efa61
feat: Implement 360° LIDAR obstacle avoidance (Issue #364 )
...
Implements ROS2 node for RPLIDAR A1M8 obstacle detection with:
- Emergency stop at 0.5m
- Speed-dependent safety zone (3m @ 20km/h, scales linearly)
- Forward-facing 60° obstacle cone scanning
- Publishes /saltybot/obstacle_alert and /cmd_vel_safe
- Debounced obstacle detection (2 frames)
- JSON status reporting
Launch: ros2 launch saltybot_lidar_avoidance lidar_avoidance.launch.py
Config: config/lidar_avoidance_params.yaml
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 17:29:14 -05:00
b04fd916ff
Merge pull request 'feat: MageDok 7in display setup for Orin (Issue #369 )' ( #373 ) from sl-webui/issue-369-display-setup into main
2026-03-03 17:20:15 -05:00
042c0529a1
feat: Add Issue #375 — adaptive camera power mode manager
...
Implements a 5-mode FSM for dynamic sensor activation based on speed,
scenario, and battery level — avoids running all 4 CSI cameras + full
sensor suite when unnecessary, saving ~1 GB RAM and significant compute.
Five modes (sensor sets):
SLEEP — no sensors (~150 MB RAM)
SOCIAL — webcam only (~400 MB RAM, parked/socialising)
AWARE — front CSI + RealSense + LIDAR (~850 MB RAM, indoor/<5km/h)
ACTIVE — front+rear CSI + RealSense + LIDAR + UWB (~1.15 GB, 5-15km/h)
FULL — all 4 CSI + RealSense + LIDAR + UWB (~1.55 GB, >15km/h)
Core library — _camera_power_manager.py (pure Python, no ROS2 deps)
- CameraPowerFSM.update(speed_mps, scenario, battery_pct) → ModeDecision
- Speed-driven upgrades: instant (safety-first)
- Speed-driven downgrades: held for downgrade_hold_s (default 5s, anti-flap)
- Scenario overrides (instant, bypass hysteresis):
· CROSSING / EMERGENCY → FULL always
· PARKED → SOCIAL immediately
· INDOOR → cap at AWARE (never ACTIVE/FULL indoors)
- Battery low cap: battery_pct < threshold → cap at AWARE
- Idle timer: near-zero speed holds at AWARE for idle_to_social_s (30s)
before dropping to SOCIAL (avoids cycling at traffic lights)
ROS2 node — camera_power_node.py
- Subscribes: /saltybot/speed, /saltybot/scenario, /saltybot/battery_pct
- Publishes: /saltybot/camera_mode (CameraPowerMode, latched, 2 Hz)
- Publishes: /saltybot/camera_cmd/{front,rear,left,right,realsense,lidar,uwb,webcam}
(std_msgs/Bool, TRANSIENT_LOCAL so late subscribers get last state)
- Logs mode transitions with speed/scenario/battery context
Tests — test/test_camera_power_manager.py: 64/64 passing
- Sensor configs: counts, correct flags per mode, safety invariants
- Speed upgrades: instantaneous at all thresholds, no hold required
- Downgrade hysteresis: hold timer, cancellation on speed spike, hold=0 instant
- Scenario overrides: CROSSING/EMERGENCY/PARKED/INDOOR, all CSIs on crossing
- Battery low: cap at AWARE, threshold boundary
- Idle timer: delay AWARE→SOCIAL, motion resets timer
- Reset, labels, ModeDecision fields
- Integration: full ride scenario (walk→jog→sprint→crossing→indoor→park→low bat)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 16:48:17 -05:00
e2587b60fb
feat: SaltyFace web app UI for Chromium kiosk (Issue #370 )
...
Animated robot expression interface as lightweight web application:
**Architecture:**
- HTML5 Canvas rendering engine
- Node.js HTTP server (localhost:3000)
- ROSLIB WebSocket bridge for ROS2 topics
- Fullscreen responsive design (1024×600)
**Features:**
- 8 emotional states (happy, alert, confused, sleeping, excited, emergency, listening, talking)
- Real-time ROS2 subscriptions:
- /saltybot/state (emotion triggers)
- /saltybot/battery (status display)
- /saltybot/target_track (EXCITED emotion)
- /saltybot/obstacles (ALERT emotion)
- /social/speech/is_speaking (TALKING emotion)
- /social/speech/is_listening (LISTENING emotion)
- Tap-to-toggle status overlay
- 60fps Canvas animation on Wayland
- ~80MB total memory (Node.js + browser)
**Files:**
- public/index.html — Main page (1024×600 fullscreen)
- public/salty-face.js — Canvas rendering + ROS2 integration
- server.js — Node.js HTTP server with CORS support
- systemd/salty-face-server.service — Auto-start systemd service
- docs/SALTY_FACE_WEB_APP.md — Complete setup & API documentation
**Integration:**
- Runs in Chromium kiosk (Issue #374 )
- Depends on rosbridge_server for WebSocket bridge
- Serves on localhost:3000 (configurable)
**Next:** Issue #371 (Accessibility enhancements)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 16:42:41 -05:00
82b8f40b39
feat: Replace GNOME with Cage + Chromium kiosk (Issue #374 )
...
Lightweight fullscreen kiosk for MageDok 7" display:
**Architecture:**
- Cage: Minimal Wayland compositor (replaces GNOME)
- Chromium: Fullscreen kiosk browser for SaltyFace web UI
- PulseAudio: HDMI audio routing (from Issue #369 )
- Touch: HID input from MageDok USB device
**Memory Savings:**
- GNOME desktop: ~650MB RAM
- Cage + Chromium: ~200MB RAM
- Net gain: ~450MB for ROS2 workloads
**Files:**
- config/cage-magedok.ini — Cage display settings (1024×600@60Hz)
- config/wayland-magedok.conf — Wayland output configuration
- scripts/chromium_kiosk.sh — Cage + Chromium launcher
- systemd/chromium-kiosk.service — Auto-start systemd service
- launch/cage_display.launch.py — ROS2 launch configuration
- docs/CAGE_CHROMIUM_KIOSK.md — Complete setup & troubleshooting guide
**Next:** Issue #370 (Salty Face as web app in Chromium kiosk)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 16:41:00 -05:00
6592b58f65
feat: Add Issue #350 — smooth velocity ramp controller
...
Adds a rate-limiting shim between raw /cmd_vel and the drive stack to
prevent wheel slip, tipping, and jerky motion from step velocity inputs.
Core library — _velocity_ramp.py (pure Python, no ROS2 deps)
- VelocityRamp: applies independent accel/decel limits to linear-x and
angular-z with configurable max_lin_accel, max_lin_decel,
max_ang_accel, max_ang_decel
- _ramp_axis(): per-axis rate limiter with correct accel/decel selection
(decel when |target| < |current| or sign reversal; accel otherwise)
- Emergency stop: step(0.0, 0.0) bypasses ramp → immediate zero output
- Asymmetric limits supported (e.g. faster decel than accel)
ROS2 node — velocity_ramp_node.py
- Subscribes /cmd_vel, publishes /cmd_vel_smooth at configurable rate_hz
- Parameters: max_lin_accel (0.5 m/s²), max_lin_decel (0.5 m/s²),
max_ang_accel (1.0 rad/s²), max_ang_decel (1.0 rad/s²), rate_hz (50)
Tests — test/test_velocity_ramp.py: 50/50 passing
- _ramp_axis: accel/decel selection, sign reversal, overshoot prevention
- Construction: invalid params raise ValueError, defaults verified
- Linear/angular ramp-up: step size, target reached, no overshoot
- Deceleration: asymmetric limits, partial decel (non-zero target)
- Emergency stop: immediate zero, state cleared, resume from zero
- Sign reversal: passes through zero without jumping
- Reset: state cleared, next ramp starts from zero
- Monotonicity: linear and angular outputs are monotone toward target
- Rate accuracy: 50Hz/10Hz step sizes, 100-step convergence verified
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 15:45:05 -05:00
45d456049a
feat: MageDok 7in display setup for Jetson Orin (Issue #369 )
...
Add complete display integration for MageDok 7" IPS touchscreen:
Configuration Files:
- X11 display config (xorg-magedok.conf) — 1024×600 @ 60Hz
- PulseAudio routing (pulseaudio-magedok.conf) — HDMI audio to speakers
- Udev rules (90-magedok-touch.rules) — USB touch device permissions
- Systemd service (magedok-display.service) — auto-start on boot
ROS2 Launch:
- magedok_display.launch.py — coordinate display/touch/audio setup
Helper Scripts:
- verify_display.py — validate 1024×600 resolution via xrandr
- touch_monitor.py — detect MageDok USB touch, publish status
- audio_router.py — configure PulseAudio HDMI sink routing
Documentation:
- MAGEDOK_DISPLAY_SETUP.md — complete installation and troubleshooting guide
Features:
✓ DisplayPort → HDMI video from Orin DP connector
✓ USB touch input as HID device (driver-free)
✓ HDMI audio routing to built-in speakers
✓ 1024×600 native resolution verification
✓ Systemd auto-launch on boot (no login prompt)
✓ Headless fallback when display disconnected
✓ ROS2 status monitoring (touch/audio/resolution)
Supports Salty Face UI (Issue #370 ) and accessibility features (Issue #371 )
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 15:44:03 -05:00
0ecf341c57
feat: Add Issue #365 — UWB DW3000 anchor/tag tracking (bearing + distance)
...
Software-complete implementation of the two-anchor UWB ranging stack.
All ROS2 / serial code written against an abstract interface so tests run
without physical hardware (anchors on order).
New message
- UwbTarget.msg: valid, bearing_deg, distance_m, confidence,
anchor0/1_dist_m, baseline_m, fix_quality (0=none 1=single 2=dual)
Core library — _uwb_tracker.py (pure Python, no ROS2/runtime deps)
- parse_frame(): ASCII RANGE,<id>,<tag>,<mm> protocol decoder
- bearing_from_ranges(): law-of-cosines 2-anchor bearing with confidence
(penalises extreme angles + close-range geometry)
- bearing_single_anchor(): fallback bearing=0, conf≤0.3
- BearingKalman: 1-D constant-velocity Kalman filter [bearing, rate]
- UwbRangingState: thread-safe per-anchor state + stale timeout + Kalman
- AnchorSerialReader: background thread, readline() interface (real or mock)
ROS2 node — uwb_node.py
- Opens /dev/ttyUSB0 + /dev/ttyUSB1 (configurable)
- Non-fatal serial open failure (will publish FIX_NONE until plugged in)
- Publishes /saltybot/uwb_target at 10 Hz (configurable)
- Graceful shutdown: stops reader threads
Tests — test/test_uwb_tracker.py: 64/64 passing
- Frame parsing: valid, malformed, STATUS, CR/LF, mm→m conversion
- Bearing geometry: straight-ahead, ±45°, ±30°, symmetry, confidence
- Kalman: seeding, smoothing, convergence, rate tracking
- UwbRangingState: single/dual fix, stale timeout, thread safety
- AnchorSerialReader: mock serial, bytes decode, stop()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 15:25:23 -05:00
c620dc51a7
feat: Add Issue #363 — P0 person tracking for follow-me mode
...
Implements real-time person detection + tracking pipeline for the
follow-me motion controller on Jetson Orin Nano Super (D435i).
Core components
- TargetTrack.msg: bearing_deg, distance_m, confidence, bbox, vel_bearing_dps,
vel_dist_mps, depth_quality (0-3)
- _person_tracker.py (pure-Python, no ROS2/runtime deps):
· 8-state constant-velocity Kalman filter [cx,cy,w,h,vcx,vcy,vw,vh]
· Greedy IoU data association
· HSV torso colour histogram re-ID (16H×8S, Bhattacharyya similarity)
with fixed saturation clamping (s = (cmax−cmin)/cmax, clipped to [0,1])
· FollowTargetSelector: nearest person auto-lock, hold_frames hysteresis
· TENTATIVE→ACTIVE after min_hits; LOST track removal after max_lost_frames
with per-frame lost_age increment across all LOST tracks
· bearing_from_pixel, depth_at_bbox (median, quality flags)
- person_tracking_node.py:
· YOLOv8n via ultralytics (TRT FP16 on first run) → HOG+SVM fallback
· Subscribes colour + depth + camera_info + follow_start/stop
· Publishes /saltybot/target_track at ≤30 fps
- test/test_person_tracker.py: 59/59 tests passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 15:19:02 -05:00
672120bb50
feat(perception): geometric face emotion classifier (Issue #359 )
...
Classifies facial expressions into neutral/happy/surprised/angry/sad
using geometric rules over MediaPipe Face Mesh landmarks — no ML model
required at runtime.
Rules
-----
surprised: brow_raise > 0.12 AND eye_open > 0.07 AND mouth_open > 0.07
happy: smile > 0.025 (lip corners above lip midpoint)
angry: brow_furl > 0.02 AND smile < 0.01
sad: smile < -0.025 AND brow_furl < 0.015
neutral: default
Changes
-------
- saltybot_scene_msgs/msg/FaceEmotion.msg — per-face emotion + features
- saltybot_scene_msgs/msg/FaceEmotionArray.msg
- saltybot_scene_msgs/CMakeLists.txt — register new msgs
- _face_emotion.py — pure-Python: FaceLandmarks, compute_features,
classify_emotion, detect_emotion, from_mediapipe
- face_emotion_node.py — subscribes /camera/color/image_raw,
publishes /saltybot/face_emotions (≤15 fps)
- test/test_face_emotion.py — 48 tests, all passing
- setup.py — add face_emotion entry point
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 14:39:49 -05:00
f7f89403d5
Merge pull request 'feat(social): system resource monitor for Jetson Orin (Issue #355 )' ( #357 ) from sl-jetson/issue-355-sysmon into main
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 / Latency profiling (GPU, Orin) (push) Has been cancelled
2026-03-03 14:32:49 -05:00
677e6eb75e
feat(perception): MFCC nearest-centroid audio scene classifier (Issue #353 )
...
Classifies ambient audio into indoor/outdoor/traffic/park at 1 Hz using
a 16-d feature vector (13 MFCC + spectral centroid + rolloff + ZCR) with
a normalised nearest-centroid classifier. Centroids are computed at import
time from seeded synthetic prototypes, ensuring deterministic behaviour.
Changes
-------
- saltybot_scene_msgs/msg/AudioScene.msg — label + confidence + features[16]
- saltybot_scene_msgs/CMakeLists.txt — register AudioScene.msg
- _audio_scene.py — pure-numpy feature extraction + NearestCentroidClassifier
- audio_scene_node.py — subscribes /audio/audio, publishes /saltybot/audio_scene
- test/test_audio_scene.py — 53 tests (all passing) with synthetic audio
- setup.py — add audio_scene entry point
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 14:03:11 -05:00
0af4441120
feat(social): system resource monitor for Jetson Orin (Issue #355 )
...
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 9s
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
Polls /proc/stat (CPU delta), /proc/meminfo (RAM), os.statvfs (disk),
/sys/devices/gpu.0/load (GPU), and thermal zone sysfs paths; publishes
JSON payload on /saltybot/system_resources at 1 Hz.
Pure helpers (parse_proc_stat, cpu_percent_from_stats, parse_meminfo,
compute_ram_stats, read_disk_usage, read_gpu_load, read_thermal_zones)
are all unit-tested offline. Injectable I/O on SysmonNode allows full
node tick tests without /proc or /sys. 67/67 tests passing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 13:54:31 -05:00
2a9b03dd76
feat(perception): depth-based obstacle size estimator (Issue #348 )
...
Projects LIDAR clusters into the D435i depth image to estimate 3-D
obstacle width and height in metres.
- saltybot_scene_msgs/msg/ObstacleSize.msg — new message
- saltybot_scene_msgs/msg/ObstacleSizeArray.msg — array wrapper
- saltybot_scene_msgs/CMakeLists.txt — register new msgs
- saltybot_bringup/_obstacle_size.py — pure-Python helper:
CameraParams (intrinsics + LIDAR→camera extrinsics)
ObstacleSizeEstimate (NamedTuple)
lidar_to_camera() LIDAR frame → camera frame transform
project_to_pixel() pinhole projection + bounds check
sample_depth_median() uint16 depth image window → median metres
estimate_height() vertical strip scan for row extent → height_m
estimate_cluster_size() full pipeline: cluster → size estimate
- saltybot_bringup/obstacle_size_node.py — ROS2 node
sub: /scan, /camera/depth/image_rect_raw, /camera/depth/camera_info
pub: /saltybot/obstacle_sizes (ObstacleSizeArray)
width from LIDAR bbox; height from depth strip back-projection;
graceful fallback (LIDAR-only) when depth image unavailable;
intrinsics latched from CameraInfo on first arrival
- test/test_obstacle_size.py — 33 tests, 33 passing
- setup.py — add obstacle_size entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 13:32:41 -05:00
813d6f2529
feat(social): trigger-based ROS2 bag recorder (Issue #332 )
...
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 12s
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
BagRecorderNode: subscribes /saltybot/record_trigger (Bool), spawns
ros2 bag record subprocess, drives idle/recording/stopping/error
state machine; auto-stop timeout, SIGINT graceful shutdown with
SIGKILL fallback. Publishes /saltybot/recording_status (String).
Configurable topics (csv), bag_dir, prefix, compression, size limit.
Subprocess injectable for offline testing. 101/101 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 13:19:49 -05:00
a0f3677732
Merge pull request 'feat: Add pure pursuit path follower for Nav2 (Issue #333 )' ( #334 ) from sl-controls/issue-333-pure-pursuit into main
2026-03-03 13:19:29 -05:00
1729e43964
feat(perception): MediaPipe hand tracking — Leap Motion pivot (Issue #342 )
...
PART 1 AUDIT: Zero Leap Motion / UltraLeap references found in any
saltybot_* package. Existing gesture_node.py (saltybot_social) already
uses MediaPipe — no cleanup required.
PART 2 NEW PACKAGES:
saltybot_hand_tracking_msgs (ament_cmake)
- HandLandmarks.msg — 21 landmarks (float32[63]), handedness,
gesture label + direction, wrist position
- HandLandmarksArray.msg
saltybot_hand_tracking (ament_python)
- _hand_gestures.py — pure-Python gesture classifier (no ROS2/MP deps)
Vocabulary: stop (open palm) → pause/stop,
point (index up) → direction command + 8-compass,
disarm (fist) → emergency-off,
confirm (thumbs-up) → confirm action,
follow_me (peace sign) → follow mode,
greeting (wrist oscillation) → greeting response
WaveDetector: sliding-window lateral wrist tracking
- hand_tracking_node.py — ROS2 node
sub: /camera/color/image_raw (BEST_EFFORT)
pub: /saltybot/hands (HandLandmarksArray)
/saltybot/hand_gesture (std_msgs/String)
MediaPipe model_complexity=0 (lite) for 20+ FPS
on Orin Nano Super; background MP init thread;
per-hand WaveDetector instances
- test/test_hand_gestures.py — 35 tests, 35 passing
Covers: Landmark, HandGestureResult, WaveDetector, all 6 gesture
classifiers, priority ordering, direction vectors, confidence bounds
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 12:47:22 -05:00
bd9cb6da35
feat(perception): lane/path edge detector (Issue #339 )
...
Adds Canny+Hough+bird-eye perspective pipeline for detecting left/right
path edges from the forward camera. Pure-Python helper (_path_edges.py)
is fully tested; ROS2 node publishes PathEdges on /saltybot/path_edges.
- saltybot_scene_msgs/msg/PathEdges.msg — new message
- saltybot_scene_msgs/CMakeLists.txt — register PathEdges.msg
- saltybot_bringup/_path_edges.py — PathEdgeConfig, PathEdgesResult,
build/apply_homography, canny_edges,
hough_lines, classify_lines,
average_line, warp_segments,
process_frame
- saltybot_bringup/path_edges_node.py — ROS2 node (sensor_msgs/Image →
PathEdges, parameters for all
tunable Canny/Hough/birdseye params)
- test/test_path_edges.py — 38 tests, 38 passing
- setup.py — add path_edges console_script
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 11:33:22 -05:00
eb61207532
feat(perception): dynamic obstacle velocity estimator (Issue #326 )
...
Adds ObstacleVelocity/ObstacleVelocityArray msgs and an
ObstacleVelocityNode that clusters /scan points, tracks each centroid
with a constant-velocity Kalman filter, and publishes velocity vectors
on /saltybot/obstacle_velocities.
New messages (saltybot_scene_msgs):
msg/ObstacleVelocity.msg — obstacle_id, centroid, velocity,
speed_mps, width_m, depth_m,
point_count, confidence, is_static
msg/ObstacleVelocityArray.msg — array wrapper with header
New files (saltybot_bringup):
saltybot_bringup/_obstacle_velocity.py — pure helpers (no ROS2 deps)
KalmanTrack constant-velocity 2-D KF: predict(dt) / update(centroid)
coasting counter → alive flag; confidence = age/n_init
associate() greedy nearest-centroid matching (O(N·M), strict <)
ObstacleTracker predict-all → associate → update/spawn → prune cycle
saltybot_bringup/obstacle_velocity_node.py
Subscribes /scan (BEST_EFFORT); reuses _lidar_clustering helpers;
publishes ObstacleVelocityArray on /saltybot/obstacle_velocities
Parameters: distance_threshold_m=0.20, min_points=3, range 0.05–12m,
max_association_dist_m=0.50, max_coasting_frames=5,
n_init_frames=3, q_pos=0.05, q_vel=0.50, r_pos=0.10,
static_speed_threshold=0.10
test/test_obstacle_velocity.py — 48 tests, all passing
Modified:
saltybot_scene_msgs/CMakeLists.txt — register new msgs
saltybot_bringup/setup.py — add obstacle_velocity console_script
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 06:53:04 -05:00
1c8430e68a
feat: Add pure pursuit path follower for Nav2 (Issue #333 )
...
Implements saltybot_pure_pursuit package:
- Pure pursuit algorithm for path following with configurable parameters
- Lookahead distance (0.5m default) for target point on path
- Goal tolerance (0.1m) for goal detection
- Heading error correction to reduce speed when misaligned with path
- Publishes Twist commands on /cmd_vel_tracked for Nav2 integration
- Subscribes to /odom (odometry) and /path (Path trajectory)
- Tracks and publishes cross-track error for monitoring
Pure pursuit geometry:
- Finds closest point on path to robot current position
- Looks ahead specified distance along path from closest point
- Computes steering angle to follow circular arc to lookahead point
- Reduces linear velocity when heading error is large (with correction enabled)
- Clamps velocities to configurable maximums
Configuration parameters:
- lookahead_distance: 0.5m (typical range: 0.1-1.0m)
- goal_tolerance: 0.1m (distance to goal before stopping)
- heading_tolerance: 0.1 rad (unused but can support in future)
- max_linear_velocity: 1.0 m/s
- max_angular_velocity: 1.57 rad/s
- use_heading_correction: true (reduces speed on large heading errors)
Comprehensive test suite: 20+ tests covering:
- Geometric calculations (distance, quaternion conversions)
- Path following logic (empty path, straight/curved/spiral paths)
- Steering calculations (heading errors, velocity limits)
- Edge cases and realistic scenarios
- Control loop integration
- Parameter variations
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 06:47:45 -05:00
4dbb4c6f0d
feat(perception): appearance-based person re-identification (Issue #322 )
...
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 12s
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
Adds PersonTrack/PersonTrackArray msgs and a PersonReidNode that matches
individuals across camera views using HSV colour histogram appearance
features and cosine similarity, with EMA gallery update and 30s stale timeout.
New messages (saltybot_scene_msgs):
msg/PersonTrack.msg — track_id, camera_id, bbox, confidence,
first_seen, last_seen, is_stale
msg/PersonTrackArray.msg — array wrapper with header
New files (saltybot_bringup):
saltybot_bringup/_person_reid.py — pure kinematics (no ROS2 deps)
extract_hsv_histogram() 2-D HS histogram (H=16, S=8 → 128-dim, L2-norm)
cosine_similarity() handles zero/non-unit vectors
match_track() best gallery match above threshold (strict >)
TrackGallery add/update/match/mark_stale/prune_stale
TrackEntry mutable dataclass; EMA feature blend (α=0.3)
saltybot_bringup/person_reid_node.py
Subscribes /camera/color/image_raw + /saltybot/scene/objects (BEST_EFFORT)
Crops COCO person (class_id=0) ROIs; extracts features; matches gallery
Publishes PersonTrackArray on /saltybot/person_tracks at 5 Hz
Parameters: camera_id, similarity_threshold=0.75, stale_timeout_s=30,
max_tracks=20, publish_hz=5.0
test/test_person_reid.py — 50 tests, all passing
Modified:
saltybot_scene_msgs/CMakeLists.txt — register PersonTrack/Array msgs
saltybot_bringup/setup.py — add person_reid console_script
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 06:45:43 -05:00
f506c89960
Merge pull request 'feat(social): USB camera hot-plug monitor (Issue #320 )' ( #328 ) from sl-jetson/issue-320-camera-hotplug into main
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 / Latency profiling (GPU, Orin) (push) Has been cancelled
2026-03-03 06:45:23 -05:00
a3386e1694
feat(social): USB camera hot-plug monitor (Issue #320 )
...
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 2s
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 5s
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
Polls /dev/video* at 2 Hz, drives a three-state machine
(connected/disconnected/restarting) and publishes to
/saltybot/camera_status (std_msgs/String). Reconnects within
restart_grace_s (5 s) → 'restarting' held for restart_hold_s (2 s)
to signal downstream capture pipelines to restart. Scan function
is injected for offline testing. 82/82 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 00:47:27 -05:00
45828b1cc2
feat: Add battery-aware speed limiter ROS2 node (Issue #321 )
...
Implements saltybot_battery_speed_limiter package:
- Subscribes to /saltybot/battery_state, publishes speed limit factor
- Battery percentage thresholds: 100-50%=1.0, 50-25%=0.7, 25-15%=0.4, <15%=0.0
- Reduces speed to preserve battery when running low
- Automatically stops movement below 15% to prevent deep discharge
- Configurable thresholds and speed factors
- Comprehensive test suite: 30+ tests covering all threshold transitions
- Handles both direct percentage and voltage-based fallback calculation
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 00:46:12 -05:00
067a871103
feat(perception): wheel encoder differential drive odometry (Issue #184 )
...
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 7s
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
Adds saltybot_bridge_msgs package with WheelTicks.msg (int32 left/right
encoder counts) and a WheelOdomNode that subscribes to
/saltybot/wheel_ticks, integrates midpoint-Euler differential drive
kinematics (handling int32 counter rollover), and publishes
nav_msgs/Odometry on /odom_wheel at 50 Hz with optional TF broadcast.
New files:
jetson/ros2_ws/src/saltybot_bridge_msgs/
msg/WheelTicks.msg
CMakeLists.txt, package.xml
jetson/ros2_ws/src/saltybot_bringup/
saltybot_bringup/_wheel_odom.py — pure kinematics (no ROS2 deps)
saltybot_bringup/wheel_odom_node.py — 50 Hz timer node + TF broadcast
test/test_wheel_odom.py — 42 tests, all passing
Modified:
saltybot_bringup/package.xml — add saltybot_bridge_msgs, nav_msgs deps
saltybot_bringup/setup.py — add wheel_odom console_script entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 00:41:39 -05:00
b96c6b96d0
Merge pull request 'feat(social): audio wake-word detector 'hey salty' (Issue #320 )' ( #317 ) from sl-jetson/wake-word-detect into main
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 10s
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 10s
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
2026-03-03 00:41:22 -05:00
d6553ce3d6
feat(social): audio wake-word detector 'hey salty' (Issue #320 )
...
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 2s
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 10s
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
Energy-gated log-mel + cosine-similarity wake-word node. Subscribes to
/social/speech/audio_raw (PCM-16 UInt8MultiArray), maintains a 1.5 s
sliding ring buffer, runs detection every 100 ms; fires Bool(True) on
/saltybot/wake_word_detected with 2 s cooldown. Template loaded from
.npy file; passive (no detections) when template_path is empty.
91/91 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 00:26:59 -05:00
2919e629e9
feat: Add velocity smoothing filter with Butterworth low-pass filtering
...
Implements saltybot_velocity_smoother package:
- Subscribes to /odom, applies digital Butterworth low-pass filter
- Filters linear (x,y,z) and angular (x,y,z) velocity components independently
- Publishes smoothed odometry on /odom_smooth
- Reduces encoder jitter and improves state estimation stability
- Configurable filter order (1-4), cutoff frequency (Hz), publish rate
- Can be enabled/disabled via enable_smoothing parameter
- Comprehensive test suite: 18+ tests covering filter behavior, edge cases, scenarios
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 00:23:53 -05:00
d468cb515e
Merge pull request 'feat(social): personal space respector node (Issue #310 )' ( #314 ) from sl-jetson/issue-310-personal-space into main
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 5s
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 15s
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
2026-03-03 00:20:10 -05:00
0b35a61217
Merge pull request 'feat(perception): sky detector for outdoor navigation (Issue #307 )' ( #313 ) from sl-perception/issue-307-sky-detect into main
2026-03-03 00:20:03 -05:00
b8a14e2bfc
feat(social): personal space respector node (Issue #310 )
...
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 5s
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 9s
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
Subscribes /social/faces/detected + /social/person_states; backs robot
away (cmd_vel linear.x = -backup_speed) when closest person <= 0.8 m,
latches /saltybot/too_close Bool; hysteresis band prevents chatter.
92/92 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 21:42:33 -05:00
e24c0b2e26
feat(perception): sky detector for outdoor navigation — Issue #307
...
- Add _sky_detector.py: SkyResult NamedTuple; detect_sky() with dual HSV
band masking (blue sky H∈[90,130]/S∈[40,255]/V∈[80,255] OR overcast
S∈[0,50]/V∈[185,255]), cv2.bitwise_or combined mask; sky_fraction over
configurable top scan_frac region; horizon_y = bottommost row where
per-row sky fraction ≥ row_threshold (−1 when no sky detected)
- Add sky_detect_node.py: subscribes /camera/color/image_raw (BEST_EFFORT),
publishes Float32 /saltybot/sky_fraction and Int32 /saltybot/horizon_y
per frame; scan_frac (default 0.60) and row_threshold (default 0.30) params
- Register sky_detector console script in setup.py
- 33/33 unit tests pass (no ROS2 required)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 21:39:28 -05:00
fc87862603
feat(controls): Adaptive PID gain scheduler (Issue #309 )
...
Implements ROS2 adaptive PID gain scheduler for SaltyBot with:
- Subscribes to /saltybot/speed_scale for speed conditions
- Subscribes to /saltybot/terrain_roughness for surface conditions
- Adjusts PID gains dynamically:
* P gain increases with terrain roughness (better response on rough)
* D gain decreases at low speed (prevent oscillation when slow)
* I gain scales with both conditions for stability
- Publishes Float32MultiArray [Kp, Ki, Kd] on /saltybot/pid_gains
- Configurable scaling factors for each gain modulation
- Includes 18+ unit tests for gain scheduling logic
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-02 21:37:08 -05:00
becd0bc717
Merge pull request 'feat(perception): terrain roughness estimator via Gabor + LBP (Issue #296 )' ( #305 ) from sl-perception/issue-296-terrain-rough into main
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 9s
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
2026-03-02 21:35:40 -05:00
a8838cfbbd
Merge pull request 'feat(social): conversation topic memory (Issue #299 )' ( #304 ) from sl-jetson/issue-299-topic-memory 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 21:35:35 -05:00
3bf603f685
feat(perception): terrain roughness estimator via Gabor + LBP — Issue #296
...
- Add _terrain_roughness.py: RoughnessResult NamedTuple; gabor_energy() with
4-orientation × 2-wavelength (5px, 10px) quadrature Gabor bank, DC removal
via image mean subtraction (prevents false high energy on uniform surfaces);
lbp_variance() using 8-point radius-1 LBP in vectorised numpy slice
comparisons (no sklearn); estimate_roughness() with bottom roi_frac crop,
normalised blend roughness = 0.5*(gabor/500) + 0.5*(lbp/5000) clipped [0,1]
- Add terrain_rough_node.py: subscribes /camera/color/image_raw (BEST_EFFORT),
publishes Float32 /saltybot/terrain_roughness at 2Hz (configurable via
publish_hz param); roi_frac param default 0.40 (bottom 40% = floor region)
- Register terrain_roughness console script in setup.py
- 37/37 unit tests pass (no ROS2 required)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 21:12:51 -05:00
797ed711b9
feat(social): conversation topic memory — Issue #299
...
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 9s
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
Adds topic_memory_node to saltybot_social:
- Subscribes /social/conversation_text (String, JSON {person_id, text})
- Extracts keywords via stop-word filtered pipeline:
lowercase -> strip punctuation -> filter length/alpha -> remove
stop words -> deduplicate -> cap at max_keywords_per_msg
- Maintains per-person rolling topic history (PersonTopicMemory):
ordered list, dedup with recency promotion, capped at
max_topics_per_person, evicts oldest when full
- Prunes persons idle for > prune_after_s (default 30 min; 0=disabled)
- Publishes JSON on /saltybot/conversation_topics on each utterance:
{person_id, recent_topics (most-recent first), new_topics, ts}
- Enables recall like 'last time you mentioned X'
- 71/71 tests passing
Closes #299
2026-03-02 21:11:14 -05:00
bfd291cbdd
feat(controls): Geofence boundary enforcer (Issue #298 )
...
Implements ROS2 geofence enforcer for SaltyBot with:
- Loads polygon geofence from params (list of x/y vertices)
- Subscribes to /odom for real-time robot position
- Point-in-polygon ray casting algorithm for boundary checking
- Publishes Bool on /saltybot/geofence_breach on boundary violation
- Optional enforcement flag for cmd_vel zeroing
- Configurable safety margin
- Includes 20+ unit tests for geometry and breach detection
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-02 21:06:58 -05:00
b087af4b94
Merge pull request 'feat(social): speech volume auto-adjuster (Issue #289 )' ( #293 ) from sl-jetson/issue-289-volume-adjust into main
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 10s
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 21:05:28 -05:00
1a47abf5ae
Merge pull request 'feat(perception): motion blur detector via Laplacian variance (Issue #286 )' ( #292 ) from sl-perception/issue-286-blur-detect into main
2026-03-02 21:05:23 -05:00
e8b787a987
feat(social): speech volume auto-adjuster — Issue #289
...
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 8s
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
Adds volume_adjust_node to saltybot_social:
- Subscribes /social/speech/energy (Float32 linear RMS [0..1] from vad_node)
- Maps energy to TTS volume via:
1. Normalise into [noise_floor, noise_ceil]
2. Power-curve shaping (curve_gamma; <1 concave, 1 linear, >1 convex)
3. Lerp to [min_volume, max_volume]
4. Exponential moving-average smoothing (smoothing_alpha)
- Publishes Float32 on /saltybot/tts_volume at publish_rate Hz (default 5 Hz)
- Holds last value when energy is stale (> stale_timeout_s)
- All params exposed: min/max_volume, noise_floor/ceil, curve_gamma,
smoothing_alpha, publish_rate, stale_timeout_s
- 74/74 tests passing
Closes #289
2026-03-02 20:47:53 -05:00
c5f3a5b2ce
feat(perception): motion blur detector via Laplacian variance — Issue #286
...
- Add _blur_detector.py: BlurResult NamedTuple, laplacian_variance() (ksize=3
Laplacian on greyscale, with optional ROI crop), detect_blur() returning
variance + is_blurred flag + threshold; handles greyscale and BGR inputs,
empty ROI returns 0.0
- Add blur_detect_node.py: subscribes /camera/color/image_raw (BEST_EFFORT),
publishes Bool /saltybot/image_blurred and Float32 /saltybot/blur_score per
frame; threshold and roi_frac ROS params
- Register blur_detector console script in setup.py
- 25/25 unit tests pass (no ROS2 required)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 20:46:37 -05:00
dab0f96399
feat(controls): Cliff and drop-off detection safety node (Issue #288 )
...
Implements ROS2 cliff detector for SaltyBot with:
- Subscribes to /saltybot/cliff_sensors (IR range array)
- Threshold-based detection (default 0.5m)
- Debouncing (3 consecutive frames) for robustness
- Majority voting (min 2 sensors) for safety
- Publishes Bool on /saltybot/cliff_detected
- Emergency stop trigger on cliff/drop-off detection
- Includes 15+ unit tests for detection logic
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-02 20:45:21 -05:00