210 Commits

Author SHA1 Message Date
sl-android
bd46aa5bc3 feat: Patrol mode - autonomous waypoint circuit (Issue #446)
Add autonomous patrol mode with idle behaviors and social integration:

Patrol Node:
- Nav2 /navigate_to_pose integration for autonomous navigation
- Idle behaviors at each waypoint (10-30s pan-tilt sweep + greetings)
- Person detection → pause patrol → social mode → resume
- Battery monitoring: automatic return to dock at <25%
- Teleop override: can be interrupted by joystick control
- Voice commands: 'patrol start/stop' via speech
- Randomized waypoint order for variety
- State publishing to /saltybot/patrol_state

Features:
- Waypoint loading from patrol_params.yaml (x, y, yaw coordinates)
- Random idle durations (configurable 10-30s)
- Pan-tilt sweep and greeting at each waypoint
- Person detection pause with timeout
- Automatic dock return when battery low
- Polling loop for state management
- Voice control integration

State Machine:
  IDLE ↔ NAVIGATE → IDLE_BEHAVIOR → (next waypoint)
           ↓
    [Person Detected] → PAUSE_PERSON
           ↓
    [Battery Low] → RETURN_TO_DOCK
           ↓
    [Teleop/Voice] → IDLE

Configuration:
- Waypoints with name, x, y, yaw
- Idle time range (min/max)
- Battery dock threshold (default 25%)
- Person detection pause timeout

Topics:
- /saltybot/patrol_state (String)
- /saltybot/speech_text (String)
- /saltybot/pan_tilt_cmd (String)
- /saltybot/person_detections (Detection2DArray)
- /saltybot/teleop_cmd (String)
- /saltybot/voice_cmd (String)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-05 09:04:10 -05:00
d657696840 Merge pull request 'feat: system health monitor (Issue #408)' (#439) from sl-firmware/issue-408-health-monitor into main 2026-03-05 09:00:34 -05:00
7c287e6f37 Merge pull request 'feat: LED strip controller (Issue #432)' (#436) from sl-mechanical/issue-432-led-controller into main 2026-03-05 08:59:19 -05:00
2203773377 Merge pull request 'feat: emotion engine (Issue #429)' (#435) from sl-webui/issue-429-emotion-engine into main 2026-03-05 08:59:14 -05:00
sl-android
df49eec21f feat: Android/Termux OpenClaw node (Issue #420)
Add complete phone node infrastructure:

ROS2 Package (saltybot_phone):
- camera_node: /phone/camera/image_raw (320x240, 15 FPS)
- gps_node: /phone/gps (NavSatFix via termux-api)
- imu_node: /phone/imu (accelerometer data)
- openclaw_chat_node: Local LLM inference
  Subscribes /saltybot/speech_text
  Publishes /saltybot/chat_response
- ws_bridge: WebSocket bridge to Jetson Orin (:9090)

Phone Scripts:
- termux-bootstrap.sh: Complete Termux setup
- power-management.sh: Battery/thermal monitoring
- README.md: Setup documentation

Architecture:
- Termux on Android phone
- WiFi/USB tether to Jetson Orin
- ROS2 topic bridge for sensors
- Local LLM for on-device chat (Phi-2)
- Termux:Boot auto-start
- Power management for extended runtime

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-05 08:55:47 -05:00
54d4bd97f5 feat: LED strip controller (Issue #432)
NeoPixel status indicators with animated patterns:
- Breathing blue (idle state)
- Green pulse (follow mode)
- Red flash (error state)
- Rainbow animation (celebrate)
- Amber pulse (low battery <20%)
- White chase (search mode)

Features:
- Smooth transitions between states
- Configurable LED count (default 30) and GPIO pin (default GPIO18)
- Auto-dim brightness control
- Subscribes to battery, balance, social, emotion, health topics
- Publishes LED state JSON to /saltybot/led_state
- 30Hz update frequency with multiple animation patterns

Configuration:
- YAML-based hardware and pattern settings
- Per-pattern speed and color customization
- State priority system for concurrent status indicators

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-05 08:55:41 -05:00
7000cbfbd3 feat: Add Issue #429 - Emotion engine with context-aware face expression selection
- ROS2 node subscribing to orchestrator state, battery, balance, person tracker, voice commands, health
- State-to-emotion mapping: navigation → excited, social → happy/curious, low battery → concerned, etc.
- Smooth emotion transitions (0.3–1.2s) with confidence tracking
- Idle behaviors: blink (~3s), look-around (~8s), breathing (sine wave)
- Social memory: familiarity-based warmth modifier (0.3–1.0) for known people
- Personality-aware responses: extroversion, playfulness, responsiveness, anxiety (0.0–1.0 configurable)
- Publishes /saltybot/emotion_state (JSON): emotion, intensity, confidence, expression name, context, idle_flags
- Configurable via emotion_engine.yaml: personality traits, battery thresholds, update rate
- Launch file: emotion_engine.launch.py

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-05 08:53:56 -05:00
b8b29125d6 feat: trick routines action server (Issue #431)
Implement ROS2 node for fun behavior routines:
- spin: 360° rotation with audio
- dance: sway side-to-side + bob
- nod: head yes/no patterns
- celebrate: spin + look up + smile
- shy: back away + head down + bashful

Features:
- Voice command integration (/saltybot/voice_command)
- Timed sequences: /cmd_vel + pan_tilt controls
- Obstacle safety abort on /scan near-field detection
- 10s cooldown between tricks to prevent repetition
- Trick state publishing (/saltybot/trick_state)
- Background execution thread for non-blocking operation

Package structure:
- saltybot_tricks/trick_routines_node.py (main node)
- launch/tricks.launch.py (configurable launch)
- config/tricks_params.yaml (tuning parameters)
- test/test_tricks.py (module structure tests)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-05 08:53:55 -05:00
d7b1366d6c feat: Add audio direction estimator (Issue #430)
Implements GCC-PHAT beamforming for sound source localization via Jabra mic.
- GCC-PHAT cross-correlation for direction of arrival (DoA) estimation
- Voice activity detection (VAD) using RMS energy + smoothing
- Stereo/quadrophonic channel support (left/right/front/rear estimation)
- ROS2 publishers: /saltybot/audio_direction (Float32 bearing), /saltybot/audio_activity (Bool VAD)
- Configurable parameters: sample_rate, chunk_size, publish_hz, vad_threshold, gcc_phat_max_lag
- Integration-ready for multi-person tracker speaker tracking

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-05 08:53:43 -05:00
9683fd3685 feat: Add ROS2 system health monitor (Issue #408)
Implement centralized health monitoring node that:
- Subscribes to /saltybot/<node>/heartbeat from all tracked nodes
- Tracks expected nodes from YAML configuration
- Marks nodes DEAD if silent >5 seconds
- Triggers auto-restart via ros2 launch when nodes fail
- Publishes /saltybot/system_health JSON with full status
- Alerts face display on critical node failures

Features:
- Configurable heartbeat timeout (default 5s)
- Automatic dead node detection and restart
- System health JSON publishing (timestamp, uptime, node status, critical alerts)
- Face alert system for critical failures
- Rate-limited alerting to avoid spam
- Comprehensive monitoring config with critical/important node tiers

Package structure:
- saltybot_health_monitor: Main health monitoring node
- health_config.yaml: Configurable list of monitored nodes
- health_monitor.launch.py: Launch file with parameters
- Unit tests for heartbeat parsing and health status generation

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-05 08:52:52 -05:00
c1b3a4368d Merge pull request 'feat: Nav2 SLAM integration (Issue #422)' (#428) from sl-controls/issue-422-nav2-slam into main 2026-03-04 23:59:32 -05:00
73263c1faa Merge pull request 'feat: remote monitoring relay (Issue #424)' (#427) from sl-jetson/issue-424-remote-monitor into main 2026-03-04 23:59:25 -05:00
524d2545ed Merge pull request 'feat: multi-person tracker (Issue #423)' (#426) from sl-perception/issue-423-multi-person into main 2026-03-04 23:59:17 -05:00
ca95489b1d feat: Nav2 SLAM integration with RPLIDAR + RealSense (Issue #422)
Complete autonomous navigation stack for SaltyBot:
- SLAM Toolbox: online_async 2D LIDAR SLAM from RPLIDAR A1M8
- RealSense D435i: depth → pointcloud → costmap obstacle layer
- Nav2 stack: controllers, planners, behavior server, lifecycle management
- DWB planner: tuned for 20km/h (5.5 m/s) max velocity operation
- VESC odometry bridge: motor telemetry → nav_msgs/Odometry
- Costmap integration: LIDAR + depth for global + local costmaps
- TF tree: complete setup with base_link→laser, camera_link, odom
- Goal interface: /navigate_to_pose action for autonomous goals

Configuration:
- slam_toolbox_params: loop closure, scan matching, fine/coarse search
- nav2_params: AMCL, controllers, planners, behavior trees, lifecycle
- Global costmap: static layer + LIDAR obstacle layer + inflation
- Local costmap: rolling window + LIDAR + RealSense depth + inflation
- DWB planner: 20 vx samples, 40 theta samples, 1.7s horizon

Nodes and launch files:
- vesc_odometry_bridge: integrates motor RPM to wheel odometry
- nav2_slam_bringup: main integrated launch entry point
- depth_to_costmap: RealSense depth processing pipeline
- odometry_bridge: VESC telemetry bridge

Hardware support:
- RPLIDAR A1M8: 5.5 Hz, 12m range, 360° omnidirectional
- RealSense D435i: 15 Hz RGB-D, 200 Hz IMU, depth range 5m
- VESC Flipsky FSESC 4.20: dual motor control via UART
- SaltyBot 2-wheel balancer: 0.35m radius, hoverboard motors

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 23:35:15 -05:00
16068aa3e4 feat: remote monitoring WebSocket relay (Issue #424)
Implement WebSocket telemetry relay with:
- 2Hz JSON aggregation (battery, motors, IMU, GPS, health, social)
- Port 9091 with token authentication
- msgpack compression option
- 5-minute circular history buffer
- Mobile-friendly responsive HTML UI
- Auto-reconnect WebSocket with fallback
- Critical alerts: low battery (< 15%), high temps, node crash
- Real-time dashboard with telemetry gauges

Features:
- Battery monitoring with SOC/voltage/current
- Motor command visualization (L/R duty)
- IMU attitude display (roll/pitch/yaw)
- CPU/GPU temperature with thresholds
- RAM/Disk usage progress bars
- GPS coordinates (lat/lon/alt)
- Social state (speaking, face tracking)
- Alert history with severity levels

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 23:33:55 -05:00
33fcc9d935 feat: remote monitoring WebSocket relay (Issue #424)
Implement WebSocket telemetry relay with:
- 2Hz JSON aggregation (battery, motors, IMU, GPS, health, social)
- Port 9091 with token authentication
- msgpack compression option
- 5-minute circular history buffer
- Mobile-friendly responsive HTML UI
- Auto-reconnect WebSocket with fallback
- Critical alerts: low battery (< 15%), high temps, node crash
- Real-time dashboard with telemetry gauges

Features:
- Battery monitoring with SOC/voltage/current
- Motor command visualization (L/R duty)
- IMU attitude display (roll/pitch/yaw)
- CPU/GPU temperature with thresholds
- RAM/Disk usage progress bars
- GPS coordinates (lat/lon/alt)
- Social state (speaking, face tracking)
- Alert history with severity levels

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 23:33:31 -05:00
31cfb9dcb9 feat: Add Issue #423 - Multi-person tracker with group handling + target priority
Implement multi-person tracking with:
- Track up to 10 people with persistent unique IDs
- Target priority: wake-word speaker > closest known > largest bbox
- Occlusion handoff with 3-second grace period
- Re-ID via face embedding (cosine similarity) + HSV color histogram
- Group detection and centroid calculation
- Lost target behavior: stop + rotate + SEARCHING state
- 15+ fps on Jetson Orin Nano Super
- PersonArray message publishing with active target tracking
- Configurable similarity thresholds and grace periods
- Unit tests for tracking, matching, priority, and re-ID

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 23:33:18 -05:00
9e3e586fca feat: Issue #421 - Piper TTS service with queue + priority + Jabra output
- Central ROS2 TTS node using Piper (offline ONNX speech synthesis)
- Subscribe to /saltybot/tts_request (String messages) for TTS requests
- Priority queue management with interrupt capability
- Audio output to Jabra device via ALSA/PulseAudio
- Configurable voice, speed, pitch, and volume parameters
- Publish /saltybot/tts_state (idle/synthesizing/playing) for status tracking
- Preload Piper model on startup for faster synthesis
- Queue management with configurable max size (default 16)
- Non-blocking async playback via worker thread
- Complete ROS2 package with launch file and tests
2026-03-04 23:32:21 -05:00
6aca138af1 Merge pull request 'feat: auto-dock (Issue #410)' (#417) from sl-mechanical/issue-410-auto-dock into main 2026-03-04 22:46:50 -05:00
8070dcd4b1 Merge pull request 'feat: VESC balance PID controller with tilt safety (Issue #407)' (#415) from sl-controls/issue-407-vesc-balance into main 2026-03-04 22:46:40 -05:00
fdc44f7025 feat: Issue #410 - Auto-dock charging with 20% SoC trigger and charging animation
- Update battery_low_pct from 15% to 20% for auto-dock trigger (Issue #410 requirement)
- Add social mood publisher for charging animation display
- Trigger face charging animation (happy mood) when entering CHARGING state
- ArUco marker ID 42 detection via RealSense D435i (already configured)
- Approach sequence: detect → align → slow → contact → verify charging
- 360° search then expand capability (DETECTING state handles search)
- Safety abort timeouts already implemented in FSM state machine
- IR beacon fallback detection also available
2026-03-04 22:44:15 -05:00
c86abdd1b8 feat: Add VESC balance PID controller with tilt safety (Issue #407)
- ROS2 node for balance mode PID parameter management via pyvesc UART
- Tilt safety kill switch: ±45° pitch > 500ms triggers motor cutoff
- Startup ramp: gradual acceleration from 0 to full output over configurable duration
- IMU integration: subscribe to /imu/data for pitch/roll angle computation
- State publishing: /saltybot/balance_state with tilt angles, PID values, motor telemetry
- Data logging: /saltybot/balance_log publishes CSV-formatted IMU + motor data
- Configurable parameters: PID gains, tilt thresholds, ramp duration, control frequency
- Test suite: quaternion to Euler conversion, tilt safety checks, startup ramp

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 22:40:40 -05:00
f69c02880e feat: Add VESC balance PID controller with tilt safety (Issue #407)
- ROS2 node for balance mode PID parameter management via pyvesc UART
- Tilt safety kill switch: ±45° pitch > 500ms triggers motor cutoff
- Startup ramp: gradual acceleration from 0 to full output over configurable duration
- IMU integration: subscribe to /imu/data for pitch/roll angle computation
- State publishing: /saltybot/balance_state with tilt angles, PID values, motor telemetry
- Data logging: /saltybot/balance_log publishes CSV-formatted IMU + motor data
- Configurable parameters: PID gains, tilt thresholds, ramp duration, control frequency
- Test suite: quaternion to Euler conversion, tilt safety checks, startup ramp

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 22:40:26 -05:00
88deacb337 feat: ROS2 bag recording service (Issue #411)
Implement circular buffer bag recorder with:
- Configurable topics recording
- Last N minutes circular buffer (default 30min)
- Manual save trigger via /saltybot/save_bag service
- Auto-save on crash with signal handlers
- Storage management (7-day TTL, 50GB quota)
- Compression via zstd
- Optional rsync to NAS for backup
- Periodic maintenance (cleanup expired, enforce quota)

Saves to /home/seb/rosbags/ by default.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 22:40:19 -05:00
9257f4c7de Merge branch 'origin/sl-firmware/issue-400-encounter-enrollment' into main
Resolved conflicts in social_enrollment_node.py by keeping comprehensive
docstring and imports from both versions.
2026-03-04 13:32:20 -05:00
020adf1b94 feat: Add Issue #400 - Encounter offline queue sync service
Some checks failed
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 4s
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
Implement EncounterSyncService ROS2 node for managing offline-first
encounter data syncing. Features:
- Monitors /home/seb/encounter-queue/ for JSON files
- Uploads to configurable cloud API when connectivity detected
- Exponential backoff retry with max 5 attempts
- Moves synced files to /home/seb/encounter-queue/synced/
- Publishes status on /social/encounter_sync_status topic
- Connectivity check via HTTP ping (configurable URL)
- Handles offline operation gracefully

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 13:14:44 -05:00
86c60f48e6 feat: First Encounter social interaction launch (Issue #400)
Some checks failed
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 19s
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 17s
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 encounter.launch.py orchestrating all First Encounter nodes:
- encounter_sync_service (offline queue backend)
- social_enrollment_node (face/voice enrollment)
- first_encounter_node (interaction orchestrator)
- wake_word_node (speech detection)
- face_display_bridge_node (UI frontend)

Include in full_stack.launch.py at t=9s with enable_encounter flag.
Add encounter_params.yaml with configurable greeting, TTS voice,
enrollment thresholds, database paths, and cloud sync settings.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 13:13:22 -05:00
c85619b8da feat: first encounter orchestrator state machine (Issue #400)
Some checks failed
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 13s
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
Implement state machine for detecting and enrolling unknown persons.
Manages workflow: DETECT → GREET → ASK_NAME → SMALL_TALK → ENROLL → FAREWELL

Features:
- Subscribes to /saltybot/person_tracker for unknown face detection
- Unknown person threshold configurable (default: 30% confidence)
- State machine with Piper TTS triggers for each state
- Captures STT responses for name and conversation context
- Publishes /social/orchestrator/state for coordination with other nodes
- Handles person interruptions gracefully (walks away)
- Auto-enrolls person to face gallery (configurable)
- Stores encounter data as JSON in /home/seb/encounter-queue/
- Tracks duration, responses, interests, and enrollment success

Encounter data structure:
{
  person_id, timestamp, state, name, context, greeting_response,
  interests[], enrollment_success, duration_sec, notes
}

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 13:12:47 -05:00
65ec1151f8 Merge pull request 'feat: face display bridge (Issue #394)' (#399) from sl-controls/issue-394-face-bridge into main
Some checks failed
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-04 13:11:01 -05:00
39258f465b feat: hey salty wake word template (Issue #393)
Some checks failed
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 24s
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
Creates log-mel spectrogram template for 'hey salty' wake word detection
using synthetic speech generation. Template generated from 5 synthetic
audio samples with varying pitch to improve robustness.

- generate_wake_word_template.py: Script to synthesize and generate template
- hey_salty.npy: 40-band log-mel template (40, 61) shape
- wake_word_params.yaml: Updated template_path
- README.md: Documentation for template usage and retraining procedures

The template is used by wake_word_node.py via cosine similarity matching
against incoming audio. Configurable sensitivity via match_threshold.

Future work: Collect real training recordings to improve accuracy.
2026-03-04 12:44:36 -05:00
82cb2bde79 feat: face display bridge node (Issue #394)
Implement ROS2 node bridging orchestrator state to face display expressions.
Maps /social/orchestrator/state and /saltybot/wake_word_detected to HTTP API.

Features:
- Subscribes to /social/orchestrator/state (JSON: IDLE, LISTENING, THINKING, SPEAKING, THROTTLED)
- Subscribes to /saltybot/wake_word_detected for immediate Alert response
- HTTP GET requests to face display server (configurable localhost:3000/face/{id})
- State to expression mapping:
  * IDLE → 0 (Tracking)
  * LISTENING → 1 (Alert)
  * THINKING → 3 (Searching)
  * SPEAKING → 4 (Social)
  * Wake word → 1 (Alert, immediate override)
- Publishes /face/state with JSON: {face_id, orchestrator_state, timestamp}
- Configurable face_server_url parameter
- Fallback to urllib if requests library unavailable

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 12:44:33 -05:00
fc6151ae16 fix: rename MeshPeer namespace field to ros_namespace (Issue #392)
Some checks failed
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
2026-03-04 12:43:00 -05:00
858ae1e7b9 Merge pull request 'feat: 360 obstacle avoidance with RPLIDAR (Issue #364)' (#396) from sl-firmware/issue-364-obstacle-avoidance into main 2026-03-04 12:39:46 -05:00
19017eca37 Merge pull request 'feat: pan/tilt camera head (Issue #384)' (#390) from sl-controls/issue-384-pan-tilt into main 2026-03-04 12:39:07 -05:00
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