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

12 KiB
Raw Permalink Blame History

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:

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:

'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