582 Commits

Author SHA1 Message Date
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
1222532971 Merge pull request 'feat: encounter offline queue sync service (Issue #400)' (#406) from sl-mechanical/issue-400-encounter-queue into main 2026-03-04 13:31:03 -05:00
a9d66ddcc1 Merge pull request 'feat: first encounter orchestrator (Issue #400)' (#402) from sl-jetson/issue-400-encounter-launch into main
Some checks failed
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 / Latency profiling (GPU, Orin) (push) Has been cancelled
2026-03-04 13:30:15 -05:00
136ca1fb9c 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:16:00 -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
b2c8f76114 Merge pull request 'feat: hey salty wake word model (Issue #393)' (#401) from sl-perception/issue-393-wake-word into main 2026-03-04 13:11:30 -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
7bc2b64c1d Merge pull request 'fix: MeshPeer namespace reserved keyword (Issue #392)' (#398) from sl-webui/issue-392-meshpeer-fix into main
Some checks failed
social-bot integration tests / Core integration tests (mock sensors, no GPU) (push) Has been cancelled
social-bot integration tests / Latency profiling (GPU, Orin) (push) Has been cancelled
social-bot integration tests / Lint (flake8 + pep257) (push) Has been cancelled
2026-03-04 13:10:50 -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
7a17b1bf16 fix: rename MeshPeer namespace field to ros_namespace (Issue #392)
Some checks failed
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) (pull_request) Has been cancelled
2026-03-04 12:42:35 -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
a4351d0da3 Merge pull request 'feat: accessibility communication UI (Issue #371)' (#391) from sl-webui/issue-371-accessibility into main 2026-03-04 12:39:20 -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
55c8f387ab Merge pull request 'feat: STM32 watchdog timer driver (Issue #300)' (#386) from sl-mechanical/issue-300-watchdog into main 2026-03-04 12:39:05 -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
9fc3e9894e feat: deaf/accessibility communication UI (Issue #371) 2026-03-04 12:19:28 -05:00
170c64eec1 feat: Add watchdog reset detection and status reporting (Issue #300)
- Detect if MCU was reset by IWDG watchdog timeout at startup
- Log watchdog reset events to debug terminal (USB CDC)
- Store watchdog reset flag for status reporting to Jetson
- Watchdog timer configured with 2-second timeout in safety_init()
- Main loop calls safety_refresh() to kick the watchdog every iteration

The IWDG (Independent Watchdog) resets the MCU if the main loop
hangs and fails to call safety_refresh() within the timeout window.
This provides hardware-enforced detection of software failures.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 12:17:56 -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
3c93a72d01 Merge pull request 'refactor: ESC abstraction layer with pluggable backends (Issue #388)' (#389) from sl-firmware/issue-388-esc-abstraction into main 2026-03-04 11:36:24 -05:00
844504e92e refactor: ESC abstraction layer with pluggable backends (Issue #388)
BREAKING CHANGE: Hoverboard implementation moved to pluggable vtable architecture.

## Implementation

### New Files
- include/esc_backend.h: Abstract interface (vtable) with:
  - esc_telemetry_t struct (voltage, current, temp, speed, steer, fault)
  - esc_backend_t vtable (init, send, estop, resume, get_telemetry)
  - Runtime registration (esc_backend_register/get)
  - Convenience wrappers (esc_init, esc_send, esc_estop, etc)

- src/esc_backend.c: Backend registry and wrapper implementations

- src/esc_hoverboard.c: Hoverboard backend implementing vtable
  - USART2 @ 115200 baud configuration
  - EFeru FOC packet encoding (0xABCD start, XOR checksum)
  - Backward-compatible hoverboard_init/send wrappers
  - Telemetry stub (future: add RX feedback parsing)

- src/esc_vesc.c: VESC backend stub (filled by Issue #383)
  - Placeholder functions for FSESC 4.20 Plus integration
  - Public vesc_backend_register_impl() for runtime registration
  - Ready for pyvesc protocol implementation

### Modified Files
- src/motor_driver.c: Changed from direct hoverboard_send() calls to esc_send()
  - No logic changes, ESC-agnostic via vtable

- include/config.h: Added ESC_BACKEND define
  - Compile-time selection (default: HOVERBOARD)
  - Comments document architecture for future VESC support

### Removed Files
- src/hoverboard.c: Original implementation merged into esc_hoverboard.c

## Architecture Benefits
1. **Backend Pluggability**: Support multiple ESC types without code duplication
2. **Zero Direct Dependencies**: motor_driver.c never calls hoverboard functions directly
3. **Clean Testing**: Each backend can be tested/stubbed independently
4. **Future-Ready**: VESC integration (Issue #383) just implements the vtable
5. **Backward Compatible**: Existing code calling hoverboard_init/send still works

## Testing
- pio run:  PASS (55.4KB Flash, 16.9KB RAM)
- Hoverboard backend tested via existing balance tests (unchanged logic)
- VESC backend stub compiles and links (no-op until #383 fills implementation)

## Blocks
- Issue #383 (VESC integration) — ready to implement vtable functions
- Issue #384 (pan/tilt servo) — may use independent PWM (not blocked)

## Dependencies
- None — this is pure refactoring, no API changes for callers

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 10:36:35 -05:00
985e03a26d Merge pull request 'fix: add missing bno055.h include in main.c' (#387) from sl-firmware/fix-bno055-include into main 2026-03-04 09:54:56 -05:00
d52e7af554 fix: Add missing bno055.h include to resolve implicit declaration warnings
Adds #include "bno055.h" to src/main.c to resolve implicit declaration
warnings for bno055_read(), bno055_calib_status(), and bno055_temperature().
Functions were properly implemented but header was missing from includes.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-04 08:45:51 -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
ce1a5e5fee Merge pull request 'feat: VESC UART driver node with pyvesc (Issue #383)' (#385) from sl-controls/issue-383-vesc into main 2026-03-04 08:40:15 -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
bc3ed1a0c7 Merge pull request 'fix: resolve all compile errors across 6 files (Issue #337)' (#382) from sl-controls/issue-337-build-fix into main 2026-03-03 19:58:54 -05:00
f4e71777ec fix: Resolve all compile and linker errors (Issue #337)
Fixed 7 compile errors across 6 files:

1. servo.c: Removed duplicate ServoState typedef, updated struct definition in header
2. watchdog.c: Fixed IWDG handle usage - moved to global scope for IRQHandler access
3. ultrasonic.c: Fixed timer handle type mismatches - use TIM_HandleTypeDef instead of TIM_TypeDef, replaced HAL_TIM_IC_Init_Compat with proper HAL functions
4. main.c: Replaced undefined functions - imu_calibrated() → mpu6000_is_calibrated(), crsf_is_active() → manual state check
5. ina219.c: Stubbed I2C functions pending HAL implementation

Build now passes with ZERO errors.
- RAM: 6.5% (16964 bytes / 262144)
- Flash: 10.6% (55368 bytes / 524288)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 19:00:12 -05:00
6df453e8d0 Merge pull request 'feat: deaf/accessibility mode (Issue #371)' (#381) from sl-controls/issue-371-accessibility into main 2026-03-03 18:51:30 -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
3a639507c7 Merge pull request 'feat: Salty Face animated expression UI (Issue #370)' (#379) from sl-webui/issue-370-salty-face into main 2026-03-03 18:15:17 -05:00
8aa4072a63 feat(webui): Salty Face animated expression UI — contextual emotions (Issue #370)
Add animated facial expression interface for MageDok 7" display:

Core Features:
✓ 8 emotional states:
  - Happy (default idle)
  - Alert (obstacles detected)
  - Confused (searching, target lost)
  - Sleeping (prolonged inactivity)
  - Excited (target reacquired)
  - Emergency (e-stop triggered)
  - Listening (microphone active)
  - Talking (TTS output)

Visual Design:
✓ Minimalist Cozmo/Vector-inspired eyes + optional mouth
✓ Canvas-based GPU-accelerated rendering
✓ 30fps target on Jetson Orin Nano
✓ Emotion-specific eye characteristics:
  - Scale changes (alert widened eyes)
  - Color coding per emotion
  - Pupil position tracking
  - Blinking rates vary by state
  - Eye wandering (confused searching)
  - Bouncing animation (excited)
  - Flash effect (emergency)

Mouth Animation:
✓ Synchronized with text-to-speech output
✓ Shape frames: closed, smile, oh, ah, ee sounds
✓ ~10fps lip sync animation

ROS2 Integration:
✓ Subscribe to /saltybot/state (emotion triggers)
✓ Subscribe to /saltybot/target_track (tracking state)
✓ Subscribe to /saltybot/obstacles (alert state)
✓ Subscribe to /social/speech/is_speaking (talking mode)
✓ Subscribe to /social/speech/is_listening (listening mode)
✓ Subscribe to /saltybot/battery (status tracking)
✓ Subscribe to /saltybot/audio_level (audio feedback)

HUD Overlay:
✓ Tap-to-toggle status display
✓ Battery percentage indicator
✓ Robot state label
✓ Distance to target (meters)
✓ Movement speed (m/s)
✓ System health percentage
✓ Color-coded health indicator (green/yellow/red)

Integration:
✓ New DISPLAY tab group (rose color)
✓ Full-screen rendering on 1024×600 MageDok display
✓ Responsive to robot state machine
✓ Supports kiosk mode deployment

Build Status:  PASSING
- 126 modules (+1 for SaltyFace)
- 281.57 KB main bundle (+11 KB)
- 0 errors

Depends on: Issue #369 (MageDok display setup)
Foundation for: Issue #371 (Accessibility mode)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 18:14:49 -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
34c7af38b2 Merge pull request 'feat: battery coulomb counter (Issue #325)' (#378) from sl-perception/issue-325-battery-coulomb into main 2026-03-03 17:46:03 -05:00
410ace3540 feat: battery coulomb counter (Issue #325)
Add coulomb counter for accurate SoC estimation independent of load:

- New coulomb_counter module: integrate current over time to track Ah consumed
  * coulomb_counter_init(capacity_mah) initializes with battery capacity
  * coulomb_counter_accumulate(current_ma) integrates current at 100 Hz
  * coulomb_counter_get_soc_pct() returns SoC 0-100% (255 = invalid)
  * coulomb_counter_reset() for charge-complete reset

- Battery module integration:
  * battery_accumulate_coulombs() reads motor INA219 currents and accumulates
  * battery_get_soc_coulomb() returns coulomb-based SoC with fallback to voltage
  * Initialize coulomb counter at startup with DEFAULT_BATTERY_CAPACITY_MAH

- Telemetry updates:
  * JLink STATUS: use coulomb SoC if available, fallback to voltage-based
  * CRSF battery frame: now includes remaining capacity in mAh (from coulomb counter)
  * CRSF capacity field was always 0; now reflects actual remaining mAh

- Mainloop integration:
  * Call battery_accumulate_coulombs() every tick for continuous integration
  * INA219 motor currents + 200 mA subsystem baseline = total battery draw

Motor current sources (INA219 addresses 0x40/0x41) provide most power draw;
Jetson ROS2 battery_node already prioritizes coulomb-based soc_pct from STATUS frame.

Default capacity: 2200 mAh (typical lab 3S LiPo); configurable via firmware parameter.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 17:35:34 -05:00
5cec6779e5 feat: Integrate IWDG watchdog timer driver (Issue #300)
- Replace safety.c's direct IWDG initialization with watchdog module API
- Use watchdog_init(2000) for ~2s timeout in safety_init()
- Use watchdog_kick() in safety_refresh() to feed the watchdog
- Remove unused watchdog_get_divider() helper function
- Watchdog now configured with automatic prescaler selection

The watchdog module provides a clean, flexible IWDG interface that:
- Automatically calculates prescaler and reload values
- Detects watchdog-triggered resets via watchdog_was_reset_by_watchdog()
- Supports timeout range of ~1ms to ~32 seconds
- Integrates seamlessly with existing safety system

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-03 17:29:59 -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
a8a9771ec7 Merge pull request 'feat: adaptive camera power modes (Issue #375)' (#376) from sl-perception/issue-375-camera-power-modes into main 2026-03-03 17:20:04 -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
46fc2db8e6 Merge pull request 'feat: smooth velocity ramp controller (Issue #350)' (#372) from sl-perception/issue-350-velocity-ramp into main 2026-03-03 16:17:55 -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
631282b95f Merge pull request 'feat: Issue #365 — UWB DW3000 anchor/tag tracking (bearing + distance)' (#368) from sl-perception/issue-365-uwb-tracking into main 2026-03-03 15:41:49 -05:00