saltylab-firmware/.claude/plans/floofy-forging-hellman.md
sl-webui 9c517c468f
Some checks failed
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) (pull_request) Has been cancelled
feat(webui): waypoint editor with click-to-navigate (Issue #261)
2026-03-02 17:28:26 -05:00

209 lines
7.1 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 #194 — Speed Limiter Node Implementation Plan
## Context
The SaltyBot control stack currently lacks a dedicated speed limiting aggregator. Individual subsystems publish speed scale factors:
- **TerrainAdaptationNode** → `/saltybot/terrain_speed_scale` (Float32, 0.151.0)
- **EmergencyNode** → `/saltybot/emergency` (EmergencyEvent with severity level)
- Hypothetical external source → `/saltybot/speed_limit` (to be determined)
These must be unified into a single authoritative speed scale and applied to `/cmd_vel` before forwarding to the drive system. Currently, each subsystem is independent, creating potential conflicts and inconsistent behavior.
## Goal
Create `saltybot_speed_limiter` ROS2 package that:
1. Subscribes to terrain_speed_scale, speed_limit, and emergency signals
2. Computes effective minimum speed scale from all three inputs
3. Applies scale to /cmd_vel messages → /saltybot/cmd_vel_limited
4. Publishes diagnostic JSON on /saltybot/speed_limit_info
5. Operates at 50 Hz with proper message synchronization
## Architecture
### Subscriptions
| Topic | Type | Source | Frequency | Use |
|-------|------|--------|-----------|-----|
| `/saltybot/terrain_speed_scale` | Float32 | TerrainAdaptationNode | 50Hz | Terrain-based scaling (0.151.0) |
| `/saltybot/speed_limit` | Float32 | External (TBD) | ≥10Hz | Global speed limit (0.01.0) |
| `/saltybot/emergency` | EmergencyEvent | EmergencyNode | 20Hz | Emergency severity level |
| `/cmd_vel` | Twist | SpeedControllerNode | ≥20Hz | Raw commanded velocity |
### Outputs
| Topic | Type | Frequency | Content |
|-------|------|-----------|---------|
| `/saltybot/cmd_vel_limited` | Twist | 50Hz | Scaled cmd_vel |
| `/saltybot/speed_limit_info` | String (JSON) | 50Hz | Diagnostic state |
### Message Types
**EmergencyEvent** (from saltybot_emergency_msgs):
```
string severity # "CLEAR"|"MINOR"|"MAJOR"|"CRITICAL"
```
**speed_limit_info JSON**:
```json
{
"terrain_scale": 0.638,
"speed_limit": 1.0,
"emergency_scale": 1.0,
"effective_scale": 0.638,
"cmd_vel_input": {"linear": 1.0, "angular": 0.5},
"cmd_vel_limited": {"linear": 0.638, "angular": 0.319},
"timestamp": 1740000000.123456
}
```
### Control Logic
```python
# Emergency severity → speed scale mapping
emergency_scale = {
"CLEAR": 1.0,
"MINOR": 0.95, # Yellow alert: slight caution
"MAJOR": 0.7, # Orange alert: significant slowdown
"CRITICAL": 0.3, # Red alert: severe limitation
}
# Effective scale = minimum of all sources
effective_scale = min(
terrain_scale, # From /saltybot/terrain_speed_scale
speed_limit, # From /saltybot/speed_limit
emergency_scale, # From /saltybot/emergency severity
)
# Apply to all cmd_vel components
cmd_vel_limited.linear.x *= effective_scale
cmd_vel_limited.linear.y *= effective_scale
cmd_vel_limited.linear.z *= effective_scale
cmd_vel_limited.angular.x *= effective_scale
cmd_vel_limited.angular.y *= effective_scale
cmd_vel_limited.angular.z *= effective_scale
```
## Implementation Details
### Package Structure
```
saltybot_speed_limiter/
├── CMakeLists.txt
├── package.xml
├── setup.py
├── README.md
├── launch/
│ └── speed_limiter.launch.py
├── resource/
│ └── saltybot_speed_limiter
├── saltybot_speed_limiter/
│ ├── __init__.py
│ └── speed_limiter_node.py
├── config/
│ └── speed_limiter_params.yaml
└── test/
├── __init__.py
└── test_speed_limiter.py
```
### Key Implementation Points
1. **Message Synchronization**: Use `message_filters.ApproximateTimeSynchronizer` to align terrain_scale, speed_limit, emergency, and cmd_vel with small slop tolerance (50100ms @ 50Hz)
2. **State Management**:
- Store latest values for each input (timeout handling if stale)
- Fallback to safe defaults if messages stop arriving:
- terrain_scale → 1.0 (no scaling)
- speed_limit → 1.0 (no limit)
- emergency → "CLEAR" (no threat)
3. **Parameters**:
- `publish_hz`: 50.0 (fixed, not tunable)
- `sync_slop_ms`: 100 (ApproximateTimeSynchronizer slop)
- `timeout_s`: 2.0 (signal timeout)
- `frame_id`: 'base_link'
- Emergency scale map (tunable per severity)
4. **Performance**:
- Small package, minimal computation (3 float comparisons + geometry scaling)
- No heavy libraries (just numpy for Twist scaling)
### Unit Tests
Test file: `test/test_speed_limiter.py`
Coverage:
- Min-of-three logic (all combinations)
- Twist scaling on all 6 components
- Emergency severity mapping (CLEAR, MINOR, MAJOR, CRITICAL)
- Timeout/stale handling (graceful fallback)
- JSON output structure and values
- Message synchronization edge cases
### Integration Points
**Upstream** (inputs):
- SpeedControllerNode → `/cmd_vel`
- TerrainAdaptationNode → `/saltybot/terrain_speed_scale`
- EmergencyNode → `/saltybot/emergency`
**Downstream** (outputs):
- CmdVelBridgeNode ← `/saltybot/cmd_vel_limited` (replaces `/cmd_vel`)
**Migration Path**:
1. CmdVelBridgeNode currently reads `/cmd_vel`
2. After speed_limiter is ready: modify CmdVelBridgeNode to read `/saltybot/cmd_vel_limited` instead
3. Or: use ROS2 topic remapping in launch files during transition
## Files to Create
1. **saltybot_speed_limiter/speed_limiter_node.py** (~200 lines)
- SpeedLimiterNode class
- Message synchronization with fallback logic
- Scale computation and Twist application
- JSON diagnostic publishing
2. **test/test_speed_limiter.py** (~150 lines)
- Min-of-three logic tests
- Twist scaling verification
- Emergency map tests
- Timeout/stale handling tests
3. **launch/speed_limiter.launch.py** (~40 lines)
- Configurable parameters
- QoS setup
4. **config/speed_limiter_params.yaml** (~20 lines)
- Emergency severity scales
- Timeout and sync parameters
5. **package.xml, setup.py, CMakeLists.txt, README.md, resource/** (~150 lines total)
- Standard ROS2 package boilerplate
## Verification Plan
1. **Unit Tests**: Run pytest on test_speed_limiter.py (expect 15+ tests, all pass)
2. **Package Build**: `colcon build --packages-select saltybot_speed_limiter`
3. **Node Launch**: `ros2 launch saltybot_speed_limiter speed_limiter.launch.py`
4. **Manual Testing** (hardware):
- Subscribe to /saltybot/cmd_vel_limited and verify scaling
- Inject different terrain_scale, speed_limit, emergency values
- Confirm effective_scale = min(all three)
- Verify JSON diagnostic content
## Branch & Commit Strategy
1. Branch: `sl-controls/issue-194-speed-limiter` from main
2. Commit: Single commit with all 10 files
3. PR: To main branch with detailed description
4. MQTT Report: Send status to max after merge
## Key Decisions
- **Min-of-three logic** (not weighted average): Ensures safety — most conservative limit wins
- **Separate topic output** (`/saltybot/cmd_vel_limited`): Preserves original /cmd_vel for debugging
- **Emergency severity levels** (CLEAR/MINOR/MAJOR/CRITICAL): Map to scales 1.0/0.95/0.7/0.3 (can tune)
- **ApproximateTimeSynchronizer**: Handles async inputs at different rates
- **JSON string output** (not typed message): Easy to parse in logging/MQTT system