saltylab-firmware/.claude/plans/zany-hugging-oasis.md
sl-controls d7051fe854
Some checks failed
social-bot integration tests / Lint (flake8 + pep257) (push) Failing after 11s
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
feat: Add Issue #467 - Power management supervisor with battery protection
- New ROS2 node: power_supervisor_node for battery state monitoring
- Battery thresholds: 30% warning, 20% dock search, 10% graceful shutdown, 5% force kill
- Charge cycle tracking and battery health estimation
- CSV logging to battery_log.csv for external analysis
- Publishes /saltybot/power_state for MQTT relay
- Graceful shutdown cascade: save state, stop motors, disarm on critical low battery
- Replaces/extends Issue #125 battery_node with supervisor-level power management

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-03-05 14:34:37 -05:00

309 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Issue #469: Terrain Classification Implementation Plan
## Context
SaltyBot currently has good sensor infrastructure (IMU, cameras, RealSense) and a robust velocity control system with the `VelocityRamp` class. However, it lacks terrain awareness for surface type detection and speed adaptation. This feature will enable:
1. **Surface detection** via IMU vibration analysis and camera texture analysis
2. **Automatic speed adaptation** based on terrain type and roughness
3. **Terrain logging** for mapping and future learning
4. **Improved robot safety** by reducing speed on rough/unstable terrain
## Architecture Overview
The implementation follows the existing ROS2 patterns:
```
IMU/Camera Data
[terrain_classifier_node] ← New node
/saltybot/terrain_state (TerrainState.msg)
[terrain_speed_adapter_node] ← New node
Adjusted /cmd_vel_terrain → existing cmd_vel_bridge
Speed-adapted robot motion
Parallel: [terrain_mapper_node] logs data for mapping
```
## Implementation Components
### 1. **Message Definition: TerrainState.msg**
**File to create:** `jetson/ros2_ws/src/saltybot_social_msgs/msg/TerrainState.msg`
Fields:
- `std_msgs/Header header` — timestamp/frame_id
- `uint8 terrain_type` — enum (0=unknown, 1=pavement, 2=grass, 3=gravel, 4=sand, 5=indoor)
- `float32 roughness` — 0.0=smooth, 1.0=very rough
- `float32 confidence` — 0.0-1.0 classification confidence
- `float32 recommended_speed_ratio` — 0.1-1.0 (fraction of max speed)
- `string source` — "imu_vibration" or "camera_texture" or "fused"
**Update files:**
- `jetson/ros2_ws/src/saltybot_social_msgs/CMakeLists.txt` — add TerrainState.msg to rosidl_generate_interfaces()
- `jetson/ros2_ws/src/saltybot_social_msgs/package.xml` — no changes needed (std_msgs already a dependency)
### 2. **Terrain Classifier Node**
**File to create:** `jetson/ros2_ws/src/saltybot_bringup/saltybot_bringup/terrain_classifier_node.py`
**Purpose:** Analyze IMU and camera data to classify terrain type and estimate roughness.
**Subscribes to:**
- `/camera/imu` (sensor_msgs/Imu) — RealSense IMU at 200 Hz
- `/camera/color/image_raw` (sensor_msgs/Image) — camera RGB at 15 Hz
**Publishes:**
- `/saltybot/terrain_state` (TerrainState.msg) — at 5 Hz
**Key functions:**
- `_analyze_imu_vibration()` — FFT analysis on accel data (window: 200 samples = 1 sec)
- Compute power spectral density in 0-50 Hz band
- Extract features: peak frequency, energy distribution, RMS acceleration
- Roughness = normalized RMS of high-freq components (>10 Hz)
- `_analyze_camera_texture()` — CNN-based texture analysis
- Uses MobileNetV2 pre-trained on ImageNet as feature extractor
- Extracts high-level texture/surface features from camera image
- Lightweight model (~3.5M parameters, ~50-100ms inference on Jetson)
- Outputs feature vector fed to classification layer
- `_classify_terrain()` — decision logic
- Simple rule-based classifier (can be upgraded to CNN)
- Input: [imu_roughness, camera_texture_variance, accel_magnitude]
- Decision tree or thresholds to classify into 5 types
- Output: terrain_type, roughness, confidence
**Node Parameters:**
- `imu_window_size` (int, default 200) — samples for FFT window
- `publish_rate_hz` (float, default 5.0)
- `roughness_threshold` (float, default 0.3) — FFT roughness threshold
- `terrain_timeout_s` (float, default 5.0) — how long to keep previous estimate if no new data
### 3. **Speed Adapter Node**
**File to create:** `jetson/ros2_ws/src/saltybot_bringup/saltybot_bringup/terrain_speed_adapter_node.py`
**Purpose:** Adapt cmd_vel speed based on terrain state and integration with velocity ramp.
**Subscribes to:**
- `/cmd_vel` (geometry_msgs/Twist) — raw velocity commands
- `/saltybot/terrain_state` (TerrainState.msg) — terrain classification
**Publishes:**
- `/cmd_vel_terrain` (geometry_msgs/Twist) — terrain-adapted velocity
**Logic:**
- Extract target linear velocity from cmd_vel
- Apply terrain speed ratio: `adapted_speed = target_speed × recommended_speed_ratio`
- Preserve angular velocity (steering not affected by terrain)
- Publish adapted command
**Node Parameters:**
- `enable_terrain_adaptation` (bool, default true)
- `min_speed_ratio` (float, default 0.1) — never go below 10% of requested speed
- `debug_logging` (bool, default false)
**Note:** This is a lightweight adapter. The existing `velocity_ramp_node` handles acceleration/deceleration smoothing independently.
### 4. **Terrain Mapper Node** (Logging/Mapping)
**File to create:** `jetson/ros2_ws/src/saltybot_bringup/saltybot_bringup/terrain_mapper_node.py`
**Purpose:** Log terrain detections with robot pose for future mapping.
**Subscribes to:**
- `/saltybot/terrain_state` (TerrainState.msg)
- `/odom` (nav_msgs/Odometry) — robot pose
**Publishes:**
- `/saltybot/terrain_log` (std_msgs/String) — CSV formatted log messages (optional, mainly for logging)
**Logic:**
- Store terrain observations: (timestamp, pose_x, pose_y, terrain_type, roughness, confidence)
- Log to file: `~/.ros/terrain_map_<timestamp>.csv`
- Resample to 1 Hz to avoid spam
**Node Parameters:**
- `log_dir` (string, default "~/.ros/")
- `resample_rate_hz` (float, default 1.0)
### 5. **Launch Configuration**
**File to update:** `jetson/ros2_ws/src/saltybot_bringup/launch/full_stack.launch.py`
Add terrain nodes:
```python
terrain_classifier_node = Node(
package='saltybot_bringup',
executable='terrain_classifier',
name='terrain_classifier',
parameters=[{
'imu_window_size': 200,
'publish_rate_hz': 5.0,
}],
remappings=[
('/imu_in', '/camera/imu'),
('/camera_in', '/camera/color/image_raw'),
],
)
terrain_speed_adapter_node = Node(
package='saltybot_bringup',
executable='terrain_speed_adapter',
name='terrain_speed_adapter',
parameters=[{
'enable_terrain_adaptation': True,
'min_speed_ratio': 0.1,
}],
remappings=[
('/cmd_vel_in', '/cmd_vel'),
('/cmd_vel_out', '/cmd_vel_terrain'),
],
)
terrain_mapper_node = Node(
package='saltybot_bringup',
executable='terrain_mapper',
name='terrain_mapper',
)
```
**Update setup.py entry points:**
```python
'terrain_classifier = saltybot_bringup.terrain_classifier_node:main'
'terrain_speed_adapter = saltybot_bringup.terrain_speed_adapter_node:main'
'terrain_mapper = saltybot_bringup.terrain_mapper_node:main'
```
### 6. **Integration with Existing Stack**
- The existing velocity ramp (`velocity_ramp_node.py`) processes `/cmd_vel_smooth` or `/cmd_vel`
- Optionally, update cmd_vel_bridge to use `/cmd_vel_terrain` if available, else fall back to `/cmd_vel`
- Terrain classification runs independently at 5 Hz (much slower than velocity ramping at 50 Hz)
### 7. **Future CNN Enhancement**
The current implementation uses rule-based classification with IMU FFT and camera edge detection. A future enhancement could add a lightweight CNN for texture classification (e.g., MobileNet) by:
1. Creating a `terrain_classifier_cnn.py` with TensorFlow/ONNX model
2. Replacing decision logic in `terrain_classifier_node.py` with CNN inference
3. Maintaining same message interface
## Implementation Tasks
1.**Phase 1: Message Definition**
- Create `TerrainState.msg` in saltybot_social_msgs
- Update `CMakeLists.txt`
2.**Phase 2: Terrain Classifier Node**
- Implement `terrain_classifier_node.py` with IMU FFT analysis
- Implement camera texture analysis
- Decision logic for classification
3.**Phase 3: Speed Adapter Node**
- Implement `terrain_speed_adapter_node.py`
- Velocity command adaptation
4.**Phase 4: Terrain Mapper Node**
- Implement `terrain_mapper_node.py` for logging
5.**Phase 5: Integration**
- Update `full_stack.launch.py` with new nodes
- Update `setup.py` with entry points
- Test integration
## Testing & Verification
**Unit Tests:**
- Test IMU FFT feature extraction with synthetic vibration data
- Test terrain classification decision logic
- Test speed ratio application
- Test CSV logging format
**Integration Tests:**
- Run full stack with simulated IMU/camera data
- Verify terrain messages published at 5 Hz
- Verify cmd_vel_terrain adapts speeds correctly
- Check terrain log file is created and properly formatted
**Manual Testing:**
- Drive robot on different surfaces
- Verify terrain detection changes appropriately
- Verify speed adaptation is smooth (no jerks from ramping)
- Check terrain log CSV has correct format
## Critical Files Summary
**To Create:**
- `jetson/ros2_ws/src/saltybot_social_msgs/msg/TerrainState.msg`
- `jetson/ros2_ws/src/saltybot_bringup/saltybot_bringup/terrain_classifier_node.py`
- `jetson/ros2_ws/src/saltybot_bringup/saltybot_bringup/terrain_speed_adapter_node.py`
- `jetson/ros2_ws/src/saltybot_bringup/saltybot_bringup/terrain_mapper_node.py`
**To Modify:**
- `jetson/ros2_ws/src/saltybot_social_msgs/CMakeLists.txt` (add TerrainState.msg)
- `jetson/ros2_ws/src/saltybot_bringup/launch/full_stack.launch.py` (add nodes)
- `jetson/ros2_ws/src/saltybot_bringup/setup.py` (add entry points)
**Key Dependencies:**
- `numpy` — FFT analysis (already available in saltybot)
- `scipy.signal` — Butterworth filter (optional, for smoothing)
- `cv2` (OpenCV) — image processing (already available)
- `tensorflow` or `tf-lite` — MobileNetV2 pre-trained model for texture CNN
- `rclpy` — ROS2 Python client
**CNN Model Details:**
- Model: MobileNetV2 pre-trained on ImageNet
- Input: 224×224 RGB image (downsampled from camera)
- Output: 1280-dim feature vector from last conv layer before classification
- Strategy: Use pre-trained features directly (transfer learning) for quick MVP, no fine-tuning needed initially
- Alternative: Pre-trained weights can be fine-tuned on terrain image dataset in future iterations
- Inference: ~50-100ms on Jetson Xavier (acceptable at 5 Hz publish rate)
## Terrain Classification Logic (IMU + CNN Fusion)
**Features extracted:**
1. `imu_roughness` = normalized RMS of high-freq (>10 Hz) accel components (0-1)
- Computed from FFT power spectral density in 10-50 Hz band
- Reflects mechanical vibration from surface contact
2. `cnn_texture_features` = 1280-dim feature vector from MobileNetV2
- Pre-trained features capture texture, edge, and surface characteristics
- Reduced to 2-3 principal components via PCA or simple aggregation
3. `accel_magnitude` = RMS of total acceleration (m/s²)
- Helps distinguish stationary (9.81 m/s²) vs. moving
**Classification approach (Version 1):**
- Simple decision tree with IMU-dominant logic + CNN support:
```
if imu_roughness < 0.2 and accel_magnitude < 9.8:
terrain = PAVEMENT (confidence boosted if CNN agrees)
elif imu_roughness < 0.35 and cnn_grainy_score < 0.4:
terrain = GRASS
elif imu_roughness > 0.45 and cnn_granular_score > 0.5:
terrain = GRAVEL
elif cnn_sand_texture_score > 0.6 and imu_roughness > 0.3:
terrain = SAND
else:
terrain = INDOOR
```
- Confidence: weighted combination of IMU and CNN agreement
- Roughness metric: `0.0 = smooth, 1.0 = very rough` derived from IMU FFT energy ratio
**Speed recommendations:**
- Pavement: 1.0 (full speed)
- Grass: 0.8 (20% slower)
- Gravel: 0.5 (50% slower)
- Sand: 0.4 (60% slower)
- Indoor: 0.7 (30% slower by default)
**Future improvement:** Replace decision tree with trained classifier (Random Forest, SVM, or small Dense net) on labeled terrain dataset once collected.
---
This plan follows SaltyBot's established patterns:
- Pure Python libraries for core logic (_terrain_analysis.py)
- ROS2 node wrappers for integration
- Parameter-based configuration in YAML
- Message-based pub/sub architecture
- Integration with existing velocity control pipeline