feat: adaptive camera power modes (Issue #375) #376

Merged
sl-jetson merged 1 commits from sl-perception/issue-375-camera-power-modes into main 2026-03-03 17:20:04 -05:00
Collaborator

Summary

5-mode FSM for dynamic sensor activation. Avoids running all 4 CSI cameras when unnecessary, saving ~1 GB RAM and significant Orin compute.

Modes

  • SLEEP (0): no sensors, ~150 MB
  • SOCIAL (1): webcam only, ~400 MB — parked/socialising
  • AWARE (2): front CSI + RealSense + LIDAR, ~850 MB — indoor/<5 km/h
  • ACTIVE (3): front+rear CSI + RealSense + LIDAR + UWB, ~1.15 GB — 5-15 km/h
  • FULL (4): all 4 CSI + RealSense + LIDAR + UWB, ~1.55 GB — >15 km/h / crossing

New message type

CameraPowerMode.msg: mode (0-4), mode_name, 8 sensor flags, trigger_speed_mps, trigger_scenario, scenario_override

Core library: _camera_power_manager.py (pure Python)

  • CameraPowerFSM.update(speed_mps, scenario, battery_pct) returns ModeDecision
  • Speed upgrades: instant (safety-first, never delayed)
  • Speed downgrades: held for downgrade_hold_s (default 5 s) to prevent flapping
  • Scenario overrides (bypass hysteresis, instant):
    • CROSSING / EMERGENCY: force FULL (all CSIs mandatory at street crossings)
    • PARKED: force SOCIAL immediately
    • INDOOR: cap at AWARE
  • Battery low: cap at AWARE when battery_pct < threshold
  • Idle timer: holds at AWARE for idle_to_social_s (30 s) when near-stopped

ROS2 node: camera_power_node.py

  • Subscribes: /saltybot/speed, /saltybot/scenario, /saltybot/battery_pct
  • Publishes: /saltybot/camera_mode (TRANSIENT_LOCAL, 2 Hz)
  • Publishes: /saltybot/camera_cmd/{front,rear,left,right,realsense,lidar,uwb,webcam} (Bool)
  • Params: rate_hz, downgrade_hold_s, idle_to_social_s, battery_low_pct, initial_mode

Tests: 64/64 passing

Covers: sensor configs, speed upgrades, hysteresis, scenario overrides, battery cap, idle timer, reset, full ride scenario.

Safety invariants

  • Rear CSI always active in ACTIVE and FULL (approaching traffic)
  • CROSSING forces FULL with no hold delay

Test plan

  • 64/64 unit tests pass
  • ros2 run saltybot_bringup camera_power
  • Verify transitions at 1/5/15 km/h
  • Verify CROSSING forces FULL instantly
  • Verify /saltybot/camera_cmd/rear True in ACTIVE/FULL

Closes #375

Generated with Claude Code

## Summary 5-mode FSM for dynamic sensor activation. Avoids running all 4 CSI cameras when unnecessary, saving ~1 GB RAM and significant Orin compute. ### Modes - SLEEP (0): no sensors, ~150 MB - SOCIAL (1): webcam only, ~400 MB — parked/socialising - AWARE (2): front CSI + RealSense + LIDAR, ~850 MB — indoor/<5 km/h - ACTIVE (3): front+rear CSI + RealSense + LIDAR + UWB, ~1.15 GB — 5-15 km/h - FULL (4): all 4 CSI + RealSense + LIDAR + UWB, ~1.55 GB — >15 km/h / crossing ### New message type `CameraPowerMode.msg`: mode (0-4), mode_name, 8 sensor flags, trigger_speed_mps, trigger_scenario, scenario_override ### Core library: _camera_power_manager.py (pure Python) - `CameraPowerFSM.update(speed_mps, scenario, battery_pct)` returns `ModeDecision` - Speed upgrades: instant (safety-first, never delayed) - Speed downgrades: held for `downgrade_hold_s` (default 5 s) to prevent flapping - Scenario overrides (bypass hysteresis, instant): - CROSSING / EMERGENCY: force FULL (all CSIs mandatory at street crossings) - PARKED: force SOCIAL immediately - INDOOR: cap at AWARE - Battery low: cap at AWARE when `battery_pct < threshold` - Idle timer: holds at AWARE for `idle_to_social_s` (30 s) when near-stopped ### ROS2 node: camera_power_node.py - Subscribes: `/saltybot/speed`, `/saltybot/scenario`, `/saltybot/battery_pct` - Publishes: `/saltybot/camera_mode` (TRANSIENT_LOCAL, 2 Hz) - Publishes: `/saltybot/camera_cmd/{front,rear,left,right,realsense,lidar,uwb,webcam}` (Bool) - Params: rate_hz, downgrade_hold_s, idle_to_social_s, battery_low_pct, initial_mode ### Tests: 64/64 passing Covers: sensor configs, speed upgrades, hysteresis, scenario overrides, battery cap, idle timer, reset, full ride scenario. ## Safety invariants - Rear CSI always active in ACTIVE and FULL (approaching traffic) - CROSSING forces FULL with no hold delay ## Test plan - [x] 64/64 unit tests pass - [ ] `ros2 run saltybot_bringup camera_power` - [ ] Verify transitions at 1/5/15 km/h - [ ] Verify CROSSING forces FULL instantly - [ ] Verify `/saltybot/camera_cmd/rear` True in ACTIVE/FULL Closes #375 Generated with [Claude Code](https://claude.com/claude-code)
sl-perception added 1 commit 2026-03-03 16:49:56 -05:00
Implements a 5-mode FSM for dynamic sensor activation based on speed,
scenario, and battery level — avoids running all 4 CSI cameras + full
sensor suite when unnecessary, saving ~1 GB RAM and significant compute.

Five modes (sensor sets):
  SLEEP  — no sensors       (~150 MB RAM)
  SOCIAL — webcam only      (~400 MB RAM, parked/socialising)
  AWARE  — front CSI + RealSense + LIDAR   (~850 MB RAM, indoor/<5km/h)
  ACTIVE — front+rear CSI + RealSense + LIDAR + UWB  (~1.15 GB, 5-15km/h)
  FULL   — all 4 CSI + RealSense + LIDAR + UWB       (~1.55 GB, >15km/h)

Core library — _camera_power_manager.py (pure Python, no ROS2 deps)
- CameraPowerFSM.update(speed_mps, scenario, battery_pct) → ModeDecision
- Speed-driven upgrades: instant (safety-first)
- Speed-driven downgrades: held for downgrade_hold_s (default 5s, anti-flap)
- Scenario overrides (instant, bypass hysteresis):
  · CROSSING / EMERGENCY → FULL always
  · PARKED → SOCIAL immediately
  · INDOOR → cap at AWARE (never ACTIVE/FULL indoors)
- Battery low cap: battery_pct < threshold → cap at AWARE
- Idle timer: near-zero speed holds at AWARE for idle_to_social_s (30s)
  before dropping to SOCIAL (avoids cycling at traffic lights)

ROS2 node — camera_power_node.py
- Subscribes: /saltybot/speed, /saltybot/scenario, /saltybot/battery_pct
- Publishes: /saltybot/camera_mode (CameraPowerMode, latched, 2 Hz)
- Publishes: /saltybot/camera_cmd/{front,rear,left,right,realsense,lidar,uwb,webcam}
  (std_msgs/Bool, TRANSIENT_LOCAL so late subscribers get last state)
- Logs mode transitions with speed/scenario/battery context

Tests — test/test_camera_power_manager.py: 64/64 passing
- Sensor configs: counts, correct flags per mode, safety invariants
- Speed upgrades: instantaneous at all thresholds, no hold required
- Downgrade hysteresis: hold timer, cancellation on speed spike, hold=0 instant
- Scenario overrides: CROSSING/EMERGENCY/PARKED/INDOOR, all CSIs on crossing
- Battery low: cap at AWARE, threshold boundary
- Idle timer: delay AWARE→SOCIAL, motion resets timer
- Reset, labels, ModeDecision fields
- Integration: full ride scenario (walk→jog→sprint→crossing→indoor→park→low bat)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
sl-jetson merged commit a8a9771ec7 into main 2026-03-03 17:20:04 -05:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: seb/saltylab-firmware#376
No description provided.