240 Commits

Author SHA1 Message Date
0cdbe4b43e Merge pull request 'feat: INA219 dual motor current monitor driver (Issue #214)' (#218) from sl-firmware/issue-214-ina219 into main 2026-03-02 11:57:02 -05:00
3a34ec84e0 Merge pull request 'feat(bringup): obstacle height filter with IMU pitch compensation (Issue #211)' (#219) from sl-perception/issue-211-height-filter into main 2026-03-02 11:51:37 -05:00
f6ed0bd4ec Merge pull request 'feat(webui): joystick teleop widget (Issue #212)' (#220) from sl-webui/issue-212-joystick into main 2026-03-02 11:51:30 -05:00
eceda99bb5 feat: Add INA219 dual motor current monitor driver (Issue #214)
Implements complete I2C1 driver for TI INA219 power monitoring IC supporting:
- Dual sensors on I2C1 (left motor @ 0x40, right motor @ 0x41)
- Auto-calibration for 5A max current, 0.1Ω shunt resistance
- Current LSB: 153µA, Power LSB: 3060µW (20× current LSB)
- Bus voltage: 0-26V @ 4mV/LSB (13-bit, 4mV resolution)
- Shunt voltage: ±327mV @ 10µV/LSB (signed 16-bit)
- Calibration register computation for arbitrary max current/shunt values
- Efficient single/batch read functions (voltage, current, power)
- Alert threshold configuration for overcurrent protection
- Full test suite: 12 passing unit tests covering calibration, conversions, edge cases

Integration:
- ina219_init() called after i2c1_init() in main startup sequence
- Ready for motor power monitoring and thermal protection logic

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-02 11:51:26 -05:00
0940af56ef Merge pull request 'feat(controls): odometry fusion (Issue #216)' (#217) from sl-controls/issue-216-odom-fusion into main 2026-03-02 11:51:12 -05:00
b722279739 feat(bringup): scan height filter with IMU pitch compensation (Issue #211)
Two files added to saltybot_bringup:
- _scan_height_filter.py: pure-Python helpers (no rclpy) —
  filter_scan_by_height() projects each LIDAR ray to world-frame height
  using pitch/roll from the IMU and filters ground/ceiling returns;
  pitch_roll_from_accel() uses convention-agnostic atan2 formula;
  AttitudeEstimator low-pass filters the accelerometer attitude.
- scan_height_filter_node.py: subscribes /scan + /camera/imu, publishes
  /scan_filtered (LaserScan) for Nav2 at source rate (up to 20 Hz).

setup.py: adds scan_height_filter entry point.
18/18 unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 11:50:56 -05:00
938c2a33d4 feat: Add Issue #216 - Odometry fusion node (complementary filter)
Fuses wheel, visual, and IMU odometry using complementary filtering. Publishes
fused /odom (nav_msgs/Odometry) and broadcasts odom→base_link TF at 50Hz.

Sensor Fusion Strategy:
  - Wheel odometry: High-frequency accurate linear displacement (weight: 0.6)
  - Visual odometry: Loop closure and long-term drift correction (weight: 0.3)
  - IMU: High-frequency attitude and angular velocity (weight: 0.1)

Complementary Filter Architecture:
  - Fast loop (IMU): High-frequency attitude updates, angular velocity
  - Slow loop (Vision): Low-frequency position/orientation correction
  - Integration: Velocity-based position updates with covariance weighting
  - Dropout handling: Continues with available sources if sensors drop

Fusion Algorithm:
  1. Extract velocities from wheel odometry (most reliable linear)
  2. Apply IMU angular velocity (highest frequency rotation)
  3. Update orientation from IMU with blending
  4. Integrate velocities to position (wheel odometry frame)
  5. Apply visual odometry drift correction (low-frequency)
  6. Update covariances based on available measurements
  7. Publish fused odometry with full covariance matrices

Published Topics:
  - /odom (nav_msgs/Odometry) - Fused pose/twist with covariance
  - /saltybot/odom_fusion_info (std_msgs/String) - JSON debug info

TF Broadcasts:
  - odom→base_link - Position (x, y) and orientation (yaw)

Subscribed Topics:
  - /saltybot/wheel_odom (nav_msgs/Odometry) - Wheel encoder odometry
  - /rtab_map/odom (nav_msgs/Odometry) - Visual/SLAM odometry
  - /imu/data (sensor_msgs/Imu) - IMU data

Package: saltybot_odom_fusion
Entry point: odom_fusion_node
Frequency: 50Hz (20ms cycle)

Features:
  ✓ Multi-source odometry fusion
  ✓ Complementary filtering with configurable weights
  ✓ Full covariance matrices for uncertainty tracking
  ✓ TF2 transform broadcasting
  ✓ Sensor dropout handling
  ✓ JSON telemetry with fusion status
  ✓ Configurable normalization of weights

Tests: 20+ unit tests covering:
  - OdomState initialization and covariances
  - Subscription handling for all three sensors
  - Position integration from velocity
  - Angular velocity updates
  - Velocity blending from multiple sources
  - Drift correction from visual odometry
  - Covariance updates based on measurement availability
  - Quaternion to Euler angle conversion
  - Realistic fusion scenarios (straight line, circles, drift correction)
  - Sensor dropout and recovery

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-02 11:50:13 -05:00
a05c93669e Merge pull request 'feat(controls): PID auto-tuner (Issue #213)' (#215) from sl-controls/issue-213-autotune into main 2026-03-02 11:48:02 -05:00
fd7eddd44d feat: Add Issue #213 - PID auto-tuner (Astrom-Hagglund relay oscillation)
Implements PID auto-tuning ROS2 node using relay feedback (Astrom-Hagglund) method.
Service-triggered relay oscillation measures ultimate gain (Ku) and ultimate period
(Tu), then computes Ziegler-Nichols PD gains. Safety abort on >25deg tilt.

Features:
  - Service /saltybot/autotune_pid (std_srvs/Trigger) starts tuning
  - Relay oscillation method for accurate gain measurement
  - Measures Ku (ultimate gain) and Tu (ultimate period)
  - Computes Z-N PD gains: Kp=0.6*Ku, Kd=0.075*Ku*Tu
  - Real-time safety abort >25° tilt angle
  - JSON telemetry on /saltybot/autotune_info
  - Relay commands on /saltybot/autotune_cmd_vel

Tuning Process:
  1. Settle phase: zero command, allow oscillations to die
  2. Relay oscillation: apply +/-relay_magnitude commands
  3. Measure peaks: detect zero crossings, record extrema
  4. Analysis: calculate Ku from peak amplitude, Tu from period
  5. Gain computation: Ziegler-Nichols formulas
  6. Publish results: Ku, Tu, Kp, Kd

Safety Features:
  - IMU tilt monitoring (abort >25°)
  - Max tuning duration timeout
  - Configurable settle time and oscillation cycles

Published Topics:
  - /saltybot/autotune_info (std_msgs/String) - JSON with Ku, Tu, Kp, Kd
  - /saltybot/autotune_cmd_vel (geometry_msgs/Twist) - Relay control

Subscribed Topics:
  - /imu/data (sensor_msgs/Imu) - IMU tilt safety check
  - /saltybot/balance_feedback (std_msgs/Float32) - Balance feedback

Package: saltybot_pid_autotune
Entry point: pid_autotune_node
Service: /saltybot/autotune_pid

Tests: 20+ unit tests covering:
  - IMU tilt extraction
  - Relay oscillation analysis
  - Ku/Tu measurement
  - Ziegler-Nichols gain computation
  - Peak detection and averaging
  - Safety limits (tilt, timeout)
  - State machine transitions
  - JSON telemetry format

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-02 11:47:05 -05:00
4ffa36370d feat: Add Issue #213 - PID auto-tuner (Astrom-Hagglund relay oscillation)
Implements PID auto-tuning ROS2 node using relay feedback (Astrom-Hagglund) method.
Service-triggered relay oscillation measures ultimate gain (Ku) and ultimate period
(Tu), then computes Ziegler-Nichols PD gains. Safety abort on >25deg tilt.

Features:
  - Service /saltybot/autotune_pid (std_srvs/Trigger) starts tuning
  - Relay oscillation method for accurate gain measurement
  - Measures Ku (ultimate gain) and Tu (ultimate period)
  - Computes Z-N PD gains: Kp=0.6*Ku, Kd=0.075*Ku*Tu
  - Real-time safety abort >25° tilt angle
  - JSON telemetry on /saltybot/autotune_info
  - Relay commands on /saltybot/autotune_cmd_vel

Tuning Process:
  1. Settle phase: zero command, allow oscillations to die
  2. Relay oscillation: apply +/-relay_magnitude commands
  3. Measure peaks: detect zero crossings, record extrema
  4. Analysis: calculate Ku from peak amplitude, Tu from period
  5. Gain computation: Ziegler-Nichols formulas
  6. Publish results: Ku, Tu, Kp, Kd

Safety Features:
  - IMU tilt monitoring (abort >25°)
  - Max tuning duration timeout
  - Configurable settle time and oscillation cycles

Published Topics:
  - /saltybot/autotune_info (std_msgs/String) - JSON with Ku, Tu, Kp, Kd
  - /saltybot/autotune_cmd_vel (geometry_msgs/Twist) - Relay control

Subscribed Topics:
  - /imu/data (sensor_msgs/Imu) - IMU tilt safety check
  - /saltybot/balance_feedback (std_msgs/Float32) - Balance feedback

Package: saltybot_pid_autotune
Entry point: pid_autotune_node
Service: /saltybot/autotune_pid

Tests: 20+ unit tests covering:
  - IMU tilt extraction
  - Relay oscillation analysis
  - Ku/Tu measurement
  - Ziegler-Nichols gain computation
  - Peak detection and averaging
  - Safety limits (tilt, timeout)
  - State machine transitions
  - JSON telemetry format

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-02 11:46:59 -05:00
7cc4b6742e Merge pull request 'feat(firmware): pan-tilt servo driver (Issue #206)' (#210) from sl-firmware/issue-206-servo into main 2026-03-02 11:45:22 -05:00
532edb835b feat(firmware): Pan-tilt servo driver for camera head (Issue #206)
Implements TIM4 PWM driver for 2-servo camera mount with:
- 50 Hz PWM frequency (standard servo control)
- CH1 (PB6) pan servo, CH2 (PB7) tilt servo
- 0-180° angle range → 500-2500 µs pulse width mapping
- Non-blocking servo_set_angle() for immediate positioning
- servo_sweep() for smooth pan-tilt animation (linear interpolation)
- Independent sweep control per servo (pan and tilt move simultaneously)
- 15 comprehensive unit tests covering all scenarios

Integration:
- servo_init() called at startup after power_mgmt_init()
- servo_tick(now_ms) called every 1ms in main loop
- Ready for camera/gimbal control automation

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-02 11:44:56 -05:00
d1a4008451 Merge pull request 'feat(perception): person re-identification node (Issue #201)' (#208) from sl-perception/issue-201-person-reid into main 2026-03-02 11:44:31 -05:00
d143a6d156 chore: remove accidentally committed __pycache__ dirs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 11:39:21 -05:00
0d07b09949 feat(perception): add person re-ID node (Issue #201)
Two new packages:
- saltybot_person_reid_msgs: PersonAppearance + PersonAppearanceArray msgs
- saltybot_person_reid: MobileNetV2 torso-crop embedder (128-dim L2-norm)
  with 128-bin HSV histogram fallback, cosine-similarity gallery with EMA
  identity updates and configurable age-based pruning, ROS2 node publishing
  PersonAppearanceArray on /saltybot/person_reid at 5 Hz.

Pure-Python helpers (_embedding_model, _reid_gallery) importable without
rclpy — 18/18 unit tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 11:39:21 -05:00
03e7995e66 Merge pull request 'feat(jetson): CPU/GPU thermal monitor — sysfs + /saltybot/thermal JSON (Issue #205)' (#209) from sl-jetson/issue-205-thermal into main 2026-03-02 11:39:03 -05:00
1600691ec5 Merge pull request 'feat(controls): node watchdog monitor (Issue #203)' (#207) from sl-controls/issue-203-watchdog into main 2026-03-02 11:38:55 -05:00
58bb5ef18e feat(jetson): CPU/GPU thermal monitor — sysfs + /saltybot/thermal JSON (Issue #205)
New saltybot_thermal package with thermal_node: reads all
/sys/class/thermal/thermal_zone* sysfs entries (millidegrees→°C),
publishes /saltybot/thermal JSON at 1 Hz with zones[], max_temp_c,
warn, and throttled flags. Logs ROS2 WARN at ≥75°C, ERROR at ≥85°C.
thermal_root param allows sysfs override for offline testing.
50/50 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 11:21:18 -05:00
e247389b07 feat: Add Issue #203 - Node watchdog monitor (20Hz heartbeat detection)
Implements node watchdog ROS2 node that monitors heartbeats from critical
systems and triggers safety fallback when motor driver is lost >2s.

Features:
  - Monitor heartbeats from: balance, motor_driver, emergency, docking
  - Alert on /saltybot/node_watchdog (JSON) if heartbeat lost >1s
  - Safety fallback: zero cmd_vel if motor driver lost >2s
  - Republish cmd_vel on /saltybot/cmd_vel_safe with safety checks
  - 20Hz monitoring and publishing frequency
  - Configurable heartbeat timeout thresholds

Heartbeat Topics:
  - /saltybot/balance_heartbeat (std_msgs/UInt32)
  - /saltybot/motor_driver_heartbeat (std_msgs/UInt32)
  - /saltybot/emergency_heartbeat (std_msgs/UInt32)
  - /saltybot/docking_heartbeat (std_msgs/UInt32)
  - /cmd_vel (geometry_msgs/Twist)

Published Topics:
  - /saltybot/node_watchdog (std_msgs/String) - JSON status
  - /saltybot/cmd_vel_safe (geometry_msgs/Twist) - Safety-checked velocity

Package: saltybot_node_watchdog
Entry point: node_watchdog_node
Launch file: node_watchdog.launch.py

Tests: 20+ unit tests covering:
  - Heartbeat reception and timeout detection
  - Motor driver critical timeout (>2s)
  - Safety fallback logic
  - cmd_vel zeroing on motor driver loss
  - Health status JSON serialization
  - Multi-node failure scenarios

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-02 11:17:19 -05:00
8ff85601c3 Merge pull request 'feat(firmware): WS2812B LED status indicator (Issue #193)' (#204) from sl-firmware/issue-193-led-driver into main 2026-03-02 11:17:09 -05:00
bfd58fc98c Merge pull request 'feat(social): robot mesh comms — peer announce + person handoff (Issue #171)' (#202) from sl-jetson/issue-171-mesh-comms into main
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 / Latency profiling (GPU, Orin) (push) Has been cancelled
2026-03-02 11:17:03 -05:00
bb645c5e72 Merge pull request 'feat(controls): speed limiter node (Issue #194)' (#200) from sl-controls/issue-194-speed-limiter into main 2026-03-02 11:15:14 -05:00
bc45208768 feat: Add Issue #194 - Speed limiter node (50Hz safety constraints)
Implements speed limiter ROS2 node that enforces safety constraints from:
  - Terrain type (via /saltybot/terrain_speed_scale)
  - Battery level (via /saltybot/speed_limit)
  - Emergency system state (via /saltybot/emergency)

Node computes minimum scale factor and applies to /cmd_vel, forwarding
limited commands to /saltybot/cmd_vel_limited. Publishes JSON telemetry
on /saltybot/speed_limit_info at 50Hz (20ms cycle).

Features:
  - Subscription to terrain, battery, and emergency constraints
  - Twist velocity scaling with min-of-three constraint logic
  - JSON telemetry with timestamp and all scale factors
  - 50Hz timer-based publishing for real-time control
  - Emergency state mapping (NOMINAL/RECOVERING/STOPPING/ESCALATED)
  - Comprehensive unit tests (18 test cases)

Package: saltybot_speed_limiter
Entry point: speed_limiter_node
Launch file: speed_limiter.launch.py

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-02 11:14:10 -05:00
e5236f781b feat(social): robot mesh comms — peer announce + person handoff (Issue #171)
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 / 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
New MeshPeer.msg (1 Hz DDS heartbeat: robot_id, social_state, active persons,
greeted names) and MeshHandoff.msg (person context transfer on STATE_LEAVING).
mesh_comms_node subscribes to person_states and orchestrator/state, publishes
announce heartbeat, triggers handoff on LEAVING, tracks peers with timeout
cleanup, and propagates mesh-wide greeting deduplication via /social/mesh/greeted.
73/73 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 11:14:00 -05:00
b345128427 Merge pull request 'feat(jetson): camera health watchdog — 6 streams, WARNING/ERROR, v4l2 reset (issue #198)' (#199) from sl-perception/issue-198-camera-health into main 2026-03-02 11:12:32 -05:00
e6065e1531 feat(jetson): camera health watchdog node (issue #198)
Adds camera_health_node.py + _camera_state.py to saltybot_bringup:

• _camera_state.py  — pure-Python CameraState dataclass (no ROS2):
                       on_frame(), age_s, fps(window_s), status(),
                       should_reset() + mark_reset() with 30s cooldown

• camera_health_node.py — subscribes 6 image topics (D435i color/depth
                           + 4× IMX219 CSI front/right/rear/left);
                           1 Hz tick: WARNING at >2s silence, ERROR at
                           >10s + v4l2 stream-off/on reset for CSI cams;
                           publishes /saltybot/camera_health JSON with
                           per-camera status, age_s, fps, total_frames

• test/test_camera_health.py — 15 unit tests (15/15 pass, no ROS2 needed)
• setup.py — adds camera_health_monitor console_scripts entry point

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 11:11:48 -05:00
7678ddebe2 Merge pull request 'feat(jetson): AprilTag landmark detector — DICT_APRILTAG_36h11 10Hz 6-DOF (issue #191)' (#197) from sl-perception/issue-191-apriltag into main 2026-03-02 11:08:44 -05:00
d6d7e7b75a feat(jetson): AprilTag landmark detector — DICT_APRILTAG_36h11 (issue #191)
saltybot_apriltag_msgs (ament_cmake):
• DetectedTag.msg     — tag_id, family, decision_margin, corners[8],
                        center, 6-DOF pose + pose_valid flag
• DetectedTagArray.msg — DetectedTag[], count

saltybot_apriltag (ament_python, single node):
• _aruco_utils.py    — ROS2-free: ArucoBackend (cv2 4.6/4.7+ API shim),
                        rvec_tvec_to_pose (Rodrigues → Shepperd quaternion)
• apriltag_node.py   — 10 Hz timer; subscribes image + latched camera_info;
                        cv2.solvePnP IPPE_SQUARE for 6-DOF pose when K
                        available; publishes /saltybot/apriltags
• test/test_apriltag.py — 11 unit tests (11/11 pass, no ROS2 needed):
                          pose math + rendered tag detection + multi-tag

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 11:07:35 -05:00
fbca191bae feat(firmware): WS2812B NeoPixel LED status indicator driver (Issue #193)
Implements TIM3_CH1 PWM driver for 8-LED NeoPixel ring with:
- 6 state-based animations: boot (blue chase), armed (solid green),
  error (red blink), low battery (yellow pulse), charging (green breathe),
  e_stop (red strobe)
- Non-blocking via 1 ms tick callback
- GRB byte order encoding (WS2812B standard)
- PWM duty values for "0" (~40%) and "1" (~56%) bit encoding
- 10 unit tests covering state transitions, animations, color encoding

Driver integrated into main.c initialization and main loop tick.
Includes buzzer driver (Issue #189) integration.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-02 11:06:13 -05:00
aedb8771ad feat(webui): robot event log viewer — emergency/docking/diagnostics (Issue #192)
- Add EventLog component with real-time event streaming
- Color-coded events: red=emergency, blue=docking, cyan=diagnostics
- Filter by event type with toggle buttons
- Auto-scroll to latest event
- Timestamped event cards with details display
- Max 200 event history (FIFO)
- Add MONITORING tab group with Events tab to App.jsx
- Supports /saltybot/emergency, /saltybot/docking_status, /diagnostics topics

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-02 11:04:53 -05:00
6dbbbb9adc Merge pull request 'feat(jetson): depth confidence filter node (issue #190)' (#196) from sl-perception/issue-190-depth-filter into main 2026-03-02 11:02:53 -05:00
c26293d000 feat(jetson): depth confidence filter node (issue #190)
Adds depth_confidence_filter_node.py to saltybot_bringup:
- Synchronises /camera/depth/image_rect_raw + /camera/depth/confidence
  via ApproximateTimeSynchronizer (10ms slop)
- Zeros pixels where confidence uint8 < threshold * 255 (default 0.5)
- Republishes filtered float32 depth on /camera/depth/filtered
- Registered as depth_confidence_filter console_scripts entry point

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 11:02:15 -05:00
e3e4bd70a4 Merge pull request 'feat(social): multi-language support - Whisper LID + per-lang Piper TTS (Issue #167)' (#187) from sl-jetson/issue-167-multilang into main
Some checks failed
social-bot integration tests / Lint (flake8 + pep257) (pull_request) Failing after 2s
social-bot integration tests / Core integration tests (mock sensors, no GPU) (pull_request) Has been skipped
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 8s
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
social-bot integration tests / Latency profiling (GPU, Orin) (pull_request) Has been cancelled
2026-03-02 10:58:16 -05:00
90c8b427fc feat(social): multi-language support — Whisper LID + per-lang Piper TTS (Issue #167)
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 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
- Add SpeechTranscript.language (BCP-47), ConversationResponse.language fields
- speech_pipeline_node: whisper_language param (""=auto-detect via Whisper LID);
  detected language published in every transcript
- conversation_node: track per-speaker language; inject "[Please respond in X.]"
  hint for non-English speakers; propagate language to ConversationResponse.
  _LANG_NAMES: 24 BCP-47 codes -> English names. Also adds Issue #161 emotion
  context plumbing (co-located in same branch for clean merge)
- tts_node: voice_map_json param (JSON BCP-47->ONNX path); lazy voice loading
  per language; playback queue now carries (text, lang) tuples for voice routing
- speech_params.yaml, tts_params.yaml: new language params with docs
- 47/47 tests pass (test_multilang.py)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:57:34 -05:00
077f26d9d6 Merge pull request 'feat(power): STOP-mode sleep/wake power manager — Issue #178' (#186) from sl-firmware/issue-178-power-mgmt into main 2026-03-02 10:56:52 -05:00
f446e5766e feat(power): STOP-mode sleep/wake power manager — Issue #178
Adds STM32F7 STOP-mode power management with <10ms wake latency:

- power_mgmt.c: state machine (ACTIVE→SLEEP_PENDING→SLEEPING→WAKING),
  30s idle timeout (PM_IDLE_TIMEOUT_MS), 3s LED fade before STOP,
  gate SPI3/I2S3+SPI2+USART6+UART5 on sleep (clock-only, state preserved),
  EXTI1(PA1/CRSF)+EXTI7(PB7/JLink)+EXTI4(PC4/IMU) wake sources,
  PLL restore after STOP (PLLM=8/N=216/P=2 → 216MHz), uwTick save/restore
- Peripheral gating: I2S3, SPI2(OSD), USART6, UART5 disabled during STOP;
  SPI1(IMU), UART4(CRSF), USART1(JLink), I2C1 remain active as wake sources
- Sleep LED: triangle-wave pulse (2s period) on LED1 during SLEEP_PENDING,
  software PWM in main loop (1-bit, pm_pwm_phase vs brightness)
- IWDG: fed just before WFI; <10ms wake << 50ms WATCHDOG_TIMEOUT_MS
- JLink: JLINK_CMD_SLEEP=0x09, JLINK_TLM_POWER=0x81 (11-byte power frame
  at 1Hz: power_state, est_total_ma, est_audio_ma, est_osd_ma, idle_ms)
- main.c: power_mgmt_init(), activity() on CRSF/JLink/armed, tick() when
  disarmed, sleep_req handler, LED PWM, JLINK_TLM_POWER telemetry
- config.h: PM_* constants, PM_CURRENT_*_MA estimates, PM_TLM_HZ
- test_power_mgmt.py: 72 tests passing (state machine, LED, gating,
  current estimates, JLink protocol, wake latency, hardware constants)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:53:02 -05:00
728d1b0c0e Merge pull request 'feat(webui): live camera viewer — multi-stream + detection overlays (Issue #177)' (#182) from sl-webui/issue-177-camera-viewer into main 2026-03-02 10:48:50 -05:00
57420807ca feat(webui): live camera viewer — multi-stream + detection overlays (Issue #177)
UI (src/hooks/useCamera.js, src/components/CameraViewer.jsx):
  - 7 camera sources: front/left/rear/right CSI, D435i RGB/depth, panoramic
  - Compressed image subscription via rosbridge (sensor_msgs/CompressedImage)
  - Client-side 15fps gate (drops excess frames, reduces JS pressure)
  - Per-camera FPS indicator with quality badge (FULL/GOOD/LOW/NO SIGNAL)
  - Detection overlays: face boxes + names (/social/faces/detections),
    gesture icons (/social/gestures), scene object labels + hazard colours
    (/social/scene/objects); overlay mode selector (off/faces/gestures/objects/all)
  - 360° panoramic equirect viewer with mouse/touch drag azimuth pan
  - Picture-in-picture: up to 3 pinned cameras via ⊕ button
  - One-click recording (MediaRecorder → MP4/WebM download)
  - Snapshot to PNG with detection overlay composite + timestamp watermark
  - Cameras tab added to TELEMETRY group in App.jsx

Jetson (rosbridge bringup):
  - rosbridge_params.yaml: whitelist + /camera/depth/image_rect_raw/compressed,
    /camera/panoramic/compressed, /social/faces/detections,
    /social/gestures, /social/scene/objects
  - rosbridge.launch.py: D435i colour republisher (JPEG 75%) +
    depth republisher (compressedDepth/PNG16 preserving uint16 values)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:47:01 -05:00
9ca0e0844c Merge pull request 'feat(social): facial expression recognition — TRT FP16 emotion CNN (Issue #161)' (#180) from sl-jetson/issue-161-emotion into main
Some checks failed
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 10:46:21 -05:00
54668536c1 Merge pull request 'feat(jetson): dynamic obstacle tracking — LIDAR motion detection, Kalman tracking, trajectory prediction, Nav2 costmap (#176)' (#181) from sl-perception/issue-176-dynamic-obstacles into main 2026-03-02 10:45:22 -05:00
c4bf8c371f Merge pull request 'feat(#169): emergency behavior system — obstacle stop, fall prevention, stuck detection, recovery FSM' (#179) from sl-controls/issue-169-emergency into main 2026-03-02 10:44:49 -05:00
2f4540f1d3 feat(jetson): add dynamic obstacle tracking package (issue #176)
Implements real-time moving obstacle detection, Kalman tracking, trajectory
prediction, and Nav2 costmap integration at 10 Hz / <50ms latency:

saltybot_dynamic_obs_msgs (ament_cmake):
• TrackedObject.msg      — id, PoseWithCovariance, velocity, predicted_path,
                           predicted_times, speed, confidence, age, hits
• MovingObjectArray.msg  — TrackedObject[], active_count, tentative_count,
                           detector_latency_ms

saltybot_dynamic_obstacles (ament_python):
• object_detector.py    — LIDAR background subtraction (EMA occupancy grid),
                           foreground dilation + scipy connected-component
                           clustering → Detection list
• kalman_tracker.py     — CV Kalman filter, state [px,py,vx,vy], Joseph-form
                           covariance update, predict_horizon() (non-mutating)
• tracker_manager.py    — up to 20 tracks, Hungarian assignment
                           (scipy.optimize.linear_sum_assignment), TENTATIVE→
                           CONFIRMED lifecycle, miss-prune
• dynamic_obs_node.py   — 10 Hz timer: detect→track→publish
                           /saltybot/moving_objects + MarkerArray viz
• costmap_layer_node.py — predicted paths → PointCloud2 inflation smear
                           → /saltybot/dynamic_obs_cloud for Nav2 ObstacleLayer
• launch/dynamic_obstacles.launch.py + config/dynamic_obstacles_params.yaml
• test/test_dynamic_obstacles.py — 27 unit tests (27/27 pass, no ROS2 needed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:44:32 -05:00
50971c0946 feat(social): facial expression recognition — TRT FP16 emotion CNN (Issue #161)
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 2s
social-bot integration tests / Core integration tests (mock sensors, no GPU) (pull_request) Has been skipped
social-bot integration tests / Latency profiling (GPU, Orin) (push) Has been cancelled
social-bot integration tests / Latency profiling (GPU, Orin) (pull_request) Has been cancelled
- Add Expression.msg / ExpressionArray.msg ROS2 message definitions
- Add emotion_classifier.py: 7-class CNN (happy/sad/angry/surprised/fearful/disgusted/neutral)
  via TensorRT FP16 with landmark-geometry fallback; EMA per-person smoothing; opt-out registry
- Add emotion_node.py: subscribes /social/faces/detections, runs TRT crop inference (<5ms),
  publishes /social/faces/expressions and /social/emotion/context JSON for LLM
- Wire emotion context into conversation_node.py: emotion hint injected into LLM prompt
  when speaker shows non-neutral affect; subscribes /social/emotion/context
- Add emotion_params.yaml config and emotion.launch.py launch file
- Add 67-test suite (test_emotion_classifier.py): classifier, tracker, opt-out, heuristic

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:40:54 -05:00
3b2f219d66 feat(#169): emergency behavior system — obstacle stop, fall prevention, stuck detection, recovery FSM
Two new packages:
- saltybot_emergency_msgs: EmergencyEvent.msg, RecoveryAction.msg
- saltybot_emergency: threat_detector, alert_manager, recovery_sequencer, emergency_fsm, emergency_node

Implements:
- ObstacleDetector: MAJOR <30cm, CRITICAL <10cm; suppressed when not moving
- FallDetector: MINOR/MAJOR/CRITICAL tilt thresholds; floor-drop edge detection
- StuckDetector: MAJOR after 3s wheel stall (cmd>threshold, actual~0)
- BumpDetector: jerk = |Δ|a||/dt with gravity removal; MAJOR/CRITICAL thresholds
- AlertManager: per-(type,level) suppression; MAJOR×N within window → CRITICAL escalation
- RecoverySequencer: REVERSING→TURNING→RETRYING FSM; max_retries before GAVE_UP
- EmergencyFSM: NOMINAL→STOPPING→RECOVERING→ESCALATED; acknowledge to clear
- EmergencyNode: 20Hz ROS2 node; /saltybot/emergency, /saltybot/e_stop, cmd_vel mux

59/59 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:39:37 -05:00
7c62f9bf88 Merge pull request 'feat(mechanical): modular payload bay system (Issue #170)' (#174) from sl-mechanical/issue-170-payload-bay into main 2026-03-02 10:39:35 -05:00
796e343b78 feat(mechanical): modular payload bay system (Issue #170)
Dovetail rail + tool-free swappable payload modules for all variants:
- payload_bay_rail.scad: 50×12 mm 60° dovetail rail (DXF for CNC Al bar),
  spring ball detent (Ø6 mm, 50 mm pitch), continuous safety-lock groove
  (M4 thumbscrew), 4-pin pogo connector housing (GND/5V/12V/UART),
  lab/rover/tank deck adapter plates
- payload_bay_modules.scad: universal _module_base() (male tongue, detent
  bore, 4× Ø4 mm target pads, lock bore) + 3 example modules: cargo tray
  (200×100 mm, Velcro slots, bungee cord slots), camera boom (120 mm mast +
  80 mm arm, 2020-rail-compatible head, 3-position tilt), cup holder
  (Ø80 mm tapered, 8-slot flex grip). Includes copy-paste module template.
- payload_bay_BOM.md: hardware list, CNC spec (dovetail dimensions, surface
  finish, connector pocket), load analysis (2 kg rated with Al rail + lock),
  module developer guide with constraints table

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:38:07 -05:00
d2cada00a0 Merge pull request 'feat(jetson): night vision mode — IR emitter, headlight, ambient-light FSM, IR SLAM bridge (#168)' (#175) from sl-perception/issue-168-night-vision into main 2026-03-02 10:36:55 -05:00
f2b24e29d8 Merge pull request 'feat(audio): I2S3 audio amplifier driver — Issue #143' (#173) from sl-firmware/issue-143-audio-amp into main 2026-03-02 10:36:36 -05:00
7dcdd2088a feat(jetson): add saltybot_night_vision package (issue #168)
Implements ambient-light-aware night vision mode for the D435i + IMX219
stack on the Jetson Orin Nano Super:

• light_analyser.py       — histogram-based intensity FSM with hysteresis:
                            day → twilight → night → ir_only
• camera_controller.py   — D435i IR emitter via realsense2_camera param
                            service + IMX219 gain/exposure via v4l2-ctl
• gpio_headlight.py      — physical pin 33 headlight; Jetson.GPIO PWM
                            primary, sysfs on/off fallback, sim mode
• light_monitor_node.py  — subscribes IMX219/IR1, publishes
                            /saltybot/ambient_light + /saltybot/vision_mode
• night_vision_controller_node.py — reacts to mode changes; drives
                            D435i emitter, IMX219 gain, headlight
• ir_slam_bridge_node.py — mono8 IR1 → rgb8 republish so RTAB-Map
                            keeps loop-closing in darkness
• launch/night_vision.launch.py + config/night_vision_params.yaml
• test/test_night_vision.py — 18 unit tests (18/18 pass, no ROS2 needed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:36:21 -05:00
c3ada4a156 feat(audio): I2S3 audio amplifier driver — Issue #143
Add I2S3/DMA audio output driver for MAX98357A/PCM5102A class-D amps:

- audio_init(): PLLI2S N=192/R=2 → 96 MHz → FS≈22058 Hz (<0.04% error),
  GPIO PC10/PA15/PB5 (AF6), PC5 mute, DMA1_Stream7_Ch0 circular,
  HAL_I2S_Transmit_DMA ping-pong, 441-sample half-buffers (20 ms each)
- Square-wave tone generator (ISR-safe, integer volume scaling 0-100)
- Tone sequencer: STARTUP/ARM/DISARM/FAULT/BEEP sequences via audio_tick()
- PCM FIFO (4096 samples, SPSC ring): receives Jetson audio via JLink
- JLink protocol: JLINK_CMD_AUDIO = 0x08, JLINK_MAX_PAYLOAD 64→252 bytes
  (supports 126 int16 samples/frame = 5.7 ms @22050 Hz)
- main.c: audio_init(), STARTUP tone on boot, ARM/FAULT tones, audio_tick()
- config.h: AUDIO_BCLK/LRCK/DOUT/MUTE pin defines + PLLI2S constants
- test_audio.py: 45 tests, all passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 10:34:35 -05:00