Implement Ziegler-Nichols relay feedback auto-tuning with flash persistence:
Firmware (STM32F722):
- pid_flash.c/h: erase+write Kp/Ki/Kd to flash sector 7 (0x0807FFC0),
magic-validated; load on boot to restore saved tune
- jlink.h: add JLINK_CMD_PID_SAVE (0x0A) and JLINK_TLM_PID_RESULT (0x83)
with jlink_tlm_pid_result_t struct and pid_save_req flag in JLinkState
- jlink.c: dispatch JLINK_CMD_PID_SAVE -> pid_save_req; add
jlink_send_pid_result() to confirm flash write outcome over USART1
- main.c: load saved PID from flash after balance_init(); handle
pid_save_req in main loop (disarmed-only, erase stalls CPU ~1s)
Jetson ROS2 (saltybot_pid_autotune):
- pid_autotune_node.py: add Ki to Ziegler-Nichols formula (ZN PID:
Kp=0.6Ku, Ki=1.2Ku/Tu, Kd=0.075KuTu); add JLink serial client that
sends JLINK_CMD_PID_SET + JLINK_CMD_PID_SAVE after tuning completes
- autotune_config.yaml: add jlink_serial_port and jlink_baud_rate params
Trigger: ros2 service call /saltybot/autotune_pid std_srvs/srv/Trigger
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add saltybot_depth_costmap — a Nav2 costmap2d plugin that converts
D435i depth images directly into obstacle markings on both local and
global costmaps.
Pipeline:
1. Subscribe to /camera/depth/image_rect_raw (16UC1 mm) + camera_info
2. Back-project depth pixels to 3D using pinhole camera intrinsics
3. Transform points to costmap global_frame via TF2
4. Apply configurable height filter (min_height..max_height above ground)
5. Mark obstacle cells as LETHAL_OBSTACLE
6. Inflate neighbours within inflation_radius as INSCRIBED_INFLATED_OBSTACLE
Parameters:
min_height: 0.05 m — floor clearance (ignores ground returns)
max_height: 0.80 m — ceiling cutoff (ignores lights/ceiling)
obstacle_range: 3.5 m — max marking distance from camera
clearing_range: 4.0 m — max distance processed at all
inflation_radius: 0.10 m — in-layer inflation (works before inflation_layer)
downsample_factor: 4 — process 1 of N rows+cols (~19k pts @ 640×480)
Integration (#478):
- Added depth_costmap_layer to local_costmap plugins list
- Added depth_costmap_layer to global_costmap plugins list
- Plugin registered via pluginlib (plugin.xml)
Files:
jetson/ros2_ws/src/saltybot_depth_costmap/
CMakeLists.txt, package.xml, plugin.xml
include/saltybot_depth_costmap/depth_costmap_layer.hpp
src/depth_costmap_layer.cpp
jetson/ros2_ws/src/saltybot_bringup/config/nav2_params.yaml (updated)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add saltybot_motor_daemon ROS2 package — Python daemon that subscribes
to /cmd_vel and drives the FC via W<speed>,<steer>\n over /dev/ttyTHS1
at 921600 baud.
- motor_daemon_node.py: 50 Hz fixed-rate TX, 200ms safety watchdog,
Twist→ESC conversion (±1000 range), FC ack parsing (W:<s>,<st>),
periodic ? status query, /diagnostics publisher, auto-reconnect
- config/motor_daemon_params.yaml: all tunable params with comments
- launch/motor_daemon.launch.py: parameterised launch file
- test/test_motor_daemon.py: 25 unit tests (all passing)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implement automatic mission logging with bag recorder:
- Auto-records to ~/.saltybot-data/bags/ with 30min rotation
- Records mission-critical topics: /scan, /cmd_vel, /odom, /tf, /camera/color/image_raw/compressed, /saltybot/diagnostics
- MCAP format (preferred) with fallback to sqlite3 with zstd compression
- Services: /saltybot/save_bag, /saltybot/start_recording, /saltybot/stop_recording
- FIFO 20GB disk limit with automatic cleanup of oldest bags
- Auto-starts on launch, auto-saves on graceful shutdown
Changes:
- Updated bag_recorder_node.py with new parameters and services
- Changed default bag_dir to ~/.saltybot-data/bags/
- Set max_storage_gb to 20 (FIFO limit)
- Changed storage_format to MCAP by default
- Added start/stop recording service callbacks
- Updated package.xml description for mission logging
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Integrate saltybot_docking package into full_stack.launch.py
- Auto-trigger docking when battery drops to 20% (configurable via battery_low_pct)
- Launch docking at t=7s (after sensors, before Nav2)
- Add /saltybot/docking_state publisher (std_msgs/String) for state monitoring
- Update docking_params.yaml:
- battery_low_pct: 15% → 20% per Issue #489
- Add references to Issue #475 for conservative FC+hoverboard speeds
- Docking behavior includes:
- ArUco marker or IR beacon detection for dock location
- Nav2-based approach to pre-dock pose (~1m away)
- Visual servoing final alignment with contact detection
- Auto-undocking on full charge (80%) or command
- Integration with power management for mission interruption/resumption
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Integrate saltybot_docking package into full_stack.launch.py
- Auto-trigger docking when battery drops to 20% (configurable via battery_low_pct)
- Launch docking at t=7s (after sensors, before Nav2)
- Add /saltybot/docking_state publisher (std_msgs/String) for state monitoring
- Update docking_params.yaml:
- battery_low_pct: 15% → 20% per Issue #489
- Add references to Issue #475 for conservative FC+hoverboard speeds
- Docking behavior includes:
- ArUco marker or IR beacon detection for dock location
- Nav2-based approach to pre-dock pose (~1m away)
- Visual servoing final alignment with contact detection
- Auto-undocking on full charge (80%) or command
- Integration with power management for mission interruption/resumption
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Natural language voice command routing with fuzzy matching for speech variations.
Supported Commands:
- Follow me / Come with me
- Stop / Halt / Freeze
- Go home / Return to dock / Charge
- Patrol / Autonomous mode
- Come here / Approach
- Sit / Sit down
- Spin / Rotate / Turn around
- Dance / Groove
- Take photo / Picture / Smile
- What's that / Identify / Recognize
- Battery status / Battery level
Features:
- Fuzzy matching (rapidfuzz token_set_ratio) with 75% threshold
- Multiple pattern support per command for natural variations
- Three routing types: velocity (/cmd_vel), actions (/saltybot/action_command), services
- Command monitoring via /saltybot/voice_command
- Graceful handling of unrecognized speech
Architecture:
- Input: /saltybot/speech/transcribed_text (lowercase text)
- Fuzzy match against 11 command groups with 40+ patterns
- Route to: /cmd_vel (velocity), /saltybot/action_command (actions), or services
Files:
- saltybot_voice_router_node.py: Main router with fuzzy matching
- launch/voice_router.launch.py: Launch configuration
- VOICE_ROUTER_README.md: Usage documentation
Dependencies:
- rapidfuzz: Fuzzy string matching for natural speech handling
- rclpy, std_msgs, geometry_msgs: ROS2 core
Performance: <100ms per command (fuzzy matching + routing)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Update max_vel_x to 0.3 m/s (conservative for FC + hoverboard ESC)
- Update max_vel_theta to 0.5 rad/s (conservative for FC + hoverboard ESC)
- Set robot_radius to 0.22 m for 0.4m x 0.4m footprint
- Configure velocity smoother with conservative limits
- Both DWB local planner and velocity smoother updated for consistency
- RPLIDAR (/scan) + depth_to_laserscan (/depth_scan) costmap layers enabled
- NavFn global planner, DWB local planner configured
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Implement automatic map serialization and persistence for slam_toolbox:
- New SlamToolboxPersistenceNode with auto-save every 5 minutes
- Auto-load most recent map on startup
- Services: /saltybot/save_map, /saltybot/load_map, /saltybot/list_maps
- Export to Nav2-compatible YAML + PGM format
- Stores maps in ~/.saltybot-data/maps/ with .posegraph format
- Integrates with slam_toolbox serialize/deserialize services
Changes:
- Created saltybot_mapping/slam_toolbox_persistence.py
- Added slam_toolbox_persistence.launch.py
- Updated slam.launch.py to include persistence service
- Updated CMakeLists.txt to install new executable
- Added slam_toolbox dependency to package.xml
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- 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>
- saltybot_object_detection_msgs: DetectedObject, DetectedObjectArray, QueryObjects.srv
- saltybot_object_detection: YOLOv8n TensorRT FP16 node with depth projection
- Message filters for RGB-depth sync, TF2 transform to base_link
- Configurable confidence and class filtering (COCO 80 classes)
- Query service for voice integration ("whats in front of you")
- TensorRT build script with ONNX fallback
- Launch file with parameter configuration
- Full stack integration at t=6s (30 FPS target alongside person tracker)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>