sl-controls created pull request seb/saltylab-firmware#154

sl-controls
Mon, 02 Mar 2026 10:04:59 -0500

154#feat(#142): terrain adaptation — surface detection + dynamic speed/PID/bias#

Summary

Implements Issue #142 — surface detection and dynamic terrain adaptation.

Two new ROS2 packages:

saltybot_terrain_msgs (ament_cmake)

Terrain.msg: stamp, surface_type, roughness, incline_rad, grip, slip_ratio, speed_scale

saltybot_terrain_adaptation (ament_python, 50 Hz)

terrain_analyzer.py: IMU vibration RMS → normalised roughness + surface classification (smooth/tile/carpet/grass/gravel) with hysteresis-protected switching (5-sample gate)

incline_detector.py: quaternion pitch → first-order LPF (τ=1.5s) → incline_rad; motor_bias() = k_bias × sin(incline)

slip_detector.py: |v_cmd − v_actual| / |v_cmd| with EMA smoothing; gated below min_speed_ms

terrain_adapter.py: TerrainState, TerrainAdapter (speed_scale = roughness_scale × incline_scale × slip_scale; pid_scales from grip; motor_bias), TerrainHistory (rolling deque + JSON type-change log)

terrain_adaptation_node.py: 50 Hz; IMU watchdog (0.5s timeout → safe defaults); publishes /saltybot/terrain JSON, /saltybot/terrain_speed_scale, /saltybot/terrain_pid_scales, /saltybot/terrain_motor_bias, /saltybot/terrain_history; optional typed /saltybot/terrain_typed (TerrainMsg) if saltybot_terrain_msgs is built

config/terrain_params.yaml, launch/terrain_adaptation.launch.py

Test plan

60/60 pytest passing: TerrainAnalyzer (buffer, roughness, classification), SlipDetector (ratio, gate, EMA, reset), InclineDetector (LPF, seeding, clamp, motor_bias), TerrainAdapter (speed_scale, pid_scales, motor_bias, apply), TerrainHistory (change detection, JSON log)