CuRobo ROS Manager Architecture¶
Last Updated: December 2025 Introduced: December 10, 2025 refactoring
Overview¶
The curobo_ros system uses a modular, composition-based architecture with five specialized manager classes, each handling distinct responsibilities. This design follows the Single Responsibility Principle and enables clean separation of concerns for motion planning, collision management, and ROS integration.
Architecture Benefits¶
Modularity: Each manager handles one specific domain
Testability: Managers can be tested independently
Maintainability: Changes to one manager don’t affect others
Scalability: New managers can be added without modifying existing code
Single Source of Truth: ObstacleManager is the authoritative source for world configuration
The Five Core Managers¶
ConfigManager - Configuration loading and parameter management
RobotModelManager - Kinematics and collision sphere computation
ObstacleManager - Obstacle management and world state (SINGLE SOURCE OF TRUTH)
CameraSystemManager - Camera configuration and point cloud integration
RosServiceManager - ROS service interface and publishers
Architecture Diagram¶
classDiagram
class UnifiedPlannerNode {
+tensor_args
+robot_context
+config_wrapper_motion
+planner_manager
+ik_services
+fk_services
+motion_gen
+generate_trajectory()
+set_planner()
+execute_trajectory()
}
class ConfigWrapperMotion {
+config_manager
+robot_model_manager
+obstacle_manager
+camera_system_manager
+ros_service_manager
+update_world_config()
+set_motion_gen_config()
}
class ConfigManager {
-node
-robot_cfg
-world_cfg (initial)
-base_link
+get_robot_config()
+get_world_config()
+get_base_link()
}
class RobotModelManager {
-robot_cfg
-robot
-base_link
-kin_model
+get_kinematics_state()
+get_collision_spheres()
+get_joint_state()
}
class ObstacleManager {
-node
-world_cfg (AUTHORITATIVE)
-obstacle_names
-mesh_cuboid_mapping
-collision_cache
+add_object()
+remove_object()
+get_obstacles()
+get_world_cfg()
+set_collision_cache()
+update_voxel_size()
}
class CameraSystemManager {
-node
-camera_context
+get_camera_context()
}
class RosServiceManager {
-node
-obstacle_manager
-robot_model_manager
-config_manager
-config_wrapper
+init_services()
+publish_collision_spheres()
}
class PlannerManager {
-planners
-current_planner
+set_current_planner()
+get_current_planner()
}
class RobotContext {
-strategy
-strategy_lock
+select_strategy()
+set_command()
+get_joint_pose()
}
class CameraContext {
-cameras
+add_camera()
+get_camera_observation()
+get_all_camera_observations()
}
class MotionGen {
+plan()
+update_world()
}
class TrajectoryPlanner {
<<abstract>>
+plan()
+execute()
}
class IKServices {
+warmup_ik()
+ik()
+ik_batch()
+update_world()
}
class FKServices {
+warmup_fk()
+fk()
}
class JointCommandStrategy {
<<abstract>>
+send_trajectory()
+get_joint_pose()
+stop_robot()
}
class CameraStrategy {
<<abstract>>
+get_camera_observation()
+is_ready()
}
class ClassicPlanner {
+plan()
+execute()
}
class MPCPlanner {
+plan()
+execute()
}
class MultiPointPlanner {
+plan()
+execute()
}
class JointSpacePlanner {
+plan()
+execute()
}
class DoosanControl {
+send_trajectory()
+get_joint_pose()
}
class EmulatorStrategy {
+send_trajectory()
+get_joint_pose()
}
class PointCloudCameraStrategy {
+get_camera_observation()
+is_ready()
}
class DepthMapCameraStrategy {
+get_camera_observation()
+is_ready()
}
UnifiedPlannerNode --> ConfigWrapperMotion : contains
UnifiedPlannerNode --> PlannerManager : manages
UnifiedPlannerNode --> RobotContext : uses
UnifiedPlannerNode --> MotionGen : uses
ConfigWrapperMotion --> ConfigManager : Phase 1
ConfigWrapperMotion --> RobotModelManager : Phase 2
ConfigWrapperMotion --> ObstacleManager : Phase 4
ConfigWrapperMotion --> CameraSystemManager : Phase 5
ConfigWrapperMotion --> RosServiceManager : Phase 3
RobotModelManager --> ConfigManager : uses robot_cfg
ObstacleManager --> ConfigManager : takes initial world_cfg
RosServiceManager --> ObstacleManager : delegates
RosServiceManager --> RobotModelManager : delegates
RosServiceManager --> ConfigManager : uses
RosServiceManager --> ConfigWrapperMotion : updates
MotionGen --> ObstacleManager : reads world_cfg
PlannerManager --> ObstacleManager : reads world_cfg
UnifiedPlannerNode --> IKServices : contains
UnifiedPlannerNode --> FKServices : contains
PlannerManager --> TrajectoryPlanner : creates
TrajectoryPlanner <|-- ClassicPlanner : implements
TrajectoryPlanner <|-- MPCPlanner : implements
TrajectoryPlanner <|-- MultiPointPlanner : implements
TrajectoryPlanner <|-- JointSpacePlanner : implements
IKServices --> ObstacleManager : shares world
RobotContext --> JointCommandStrategy : uses
JointCommandStrategy <|-- DoosanControl : implements
JointCommandStrategy <|-- EmulatorStrategy : implements
CameraSystemManager --> CameraContext : manages
CameraContext --> CameraStrategy : uses
CameraStrategy <|-- PointCloudCameraStrategy : implements
CameraStrategy <|-- DepthMapCameraStrategy : implements
Initialization Order: Phase 1 (ConfigManager) → Phase 2 (RobotModelManager) → Phase 3 (RosServiceManager) → Phase 4 (ObstacleManager) → Phase 5 (CameraSystemManager)
Strategy Patterns:
Planner Strategy: Dynamic algorithm selection (Classic, MPC, MultiPoint, JointSpace)
Robot Strategy: Hardware abstraction (Doosan, UR5e, Emulator, Ghost)
Camera Strategy: Sensor abstraction (PointCloud, DepthMap)
The Five Managers¶
1. ConfigManager¶
File: /home/ros2_ws/src/curobo_ros/curobo_ros/core/config_manager.py
Purpose: Centralized configuration loading and parameter management.
Responsibilities:
Load robot configuration from YAML files
Load initial world configuration from files or defaults
Manage ROS parameters (base_link, world_file, etc.)
Set up CUDA device configuration for tensor operations
Key Methods:
Method |
Returns |
Description |
|---|---|---|
|
|
Returns robot configuration with kinematics |
|
|
Returns initial world config (before obstacles) |
|
|
Returns robot base frame name |
Important Notes:
ConfigManager loads the initial world configuration
After initialization, ObstacleManager becomes the single source of truth for world_cfg
For runtime world configuration with obstacles, use
obstacle_manager.get_world_cfg()
Configuration Flow:
ROS Parameters (base_link, world_file)
↓
ConfigManager loads YAML files
↓
Initial world_cfg passed to ObstacleManager
↓
ObstacleManager becomes authoritative source
2. RobotModelManager¶
File: /home/ros2_ws/src/curobo_ros/curobo_ros/core/robot_model_manager.py
Purpose: Manage GPU-accelerated robot kinematics and collision geometry.
Responsibilities:
Create and manage CudaRobotModel for forward/inverse kinematics
Compute collision spheres for robot visualization
Provide current joint state access
Handle CUDA device/dtype configuration
Key Methods:
Method |
Returns |
Description |
|---|---|---|
|
|
Compute FK from joint positions |
|
|
Get robot collision spheres [x,y,z,radius] |
|
|
Get current joint state from robot interface |
Use Cases:
Computing forward kinematics for visualization
Getting collision spheres for RViz visualization
Accessing current robot state for planning
Example: Visualize Robot Collision Spheres
The collision spheres are automatically published to RViz:
# View collision spheres in RViz
# Add MarkerArray display, topic: /unified_planner/collision_spheres
ros2 topic echo /unified_planner/collision_spheres
The RosServiceManager publishes collision spheres at 0.5s intervals using the RobotModelManager.
3. ObstacleManager ⭐¶
File: /home/ros2_ws/src/curobo_ros/curobo_ros/core/obstacle_manager.py
Purpose: SINGLE SOURCE OF TRUTH for world configuration and obstacle management.
Responsibilities:
Maintain authoritative world_cfg with all obstacles
Add/remove obstacles with validation
Convert primitives (cuboids, capsules, cylinders, spheres) to collision geometry
Handle mesh voxelization using MeshBloxilization pipeline
Manage collision cache (OBB, mesh, blox)
Compute voxel grids for collision checking
Key Methods:
Method |
Service/Topic |
Description |
|---|---|---|
|
|
Add cuboid, capsule, cylinder, sphere, or mesh |
|
|
Remove obstacle and cleanup derived cuboids |
|
|
Clear all obstacles |
|
|
List all obstacle names |
|
|
Compute voxel grid (1.52m cube) |
|
|
Update collision cache parameters |
|
N/A (internal) |
Return authoritative world configuration |
|
N/A (internal) |
Update voxel size for mesh voxelization |
Critical Concept: Single Source of Truth
After initialization:
ConfigManager.world_cfg → Initial template (used once)
ObstacleManager.world_cfg → Authoritative runtime state ⭐
All planning, collision checking, and visualization must use obstacle_manager.get_world_cfg().
Mesh Handling Strategy
Meshes are stored in two lists for seamless collision checker switching:
world_cfg.mesh - High-fidelity mesh geometry (for MESH collision checker)
world_cfg.cuboid - Voxelized cuboid approximations (for BLOX collision checker)
Created by MeshBloxilization pipeline
Named as
{mesh_name}_cuboid_{idx}Tracked in
mesh_cuboid_mappingfor cleanup
When you add a mesh object:
# Add mesh (stored in both mesh and cuboid lists)
ros2 service call /unified_planner/add_object curobo_msgs/srv/AddObject \
"{name: 'part', type: 4, mesh_file_path: '/path/to/mesh.stl', \
pose: {position: {x: 0.5, y: 0.0, z: 0.3}, orientation: {w: 1.0}}}"
# Response message shows world state counts:
# "Object 'part' added successfully (N cuboids, 1 mesh in world)"
# Mesh internally stored as:
# - world_cfg.mesh: ["part"] (original mesh)
# - world_cfg.cuboid: ["part_cuboid_0", "part_cuboid_1", ...] (voxelized)
When you remove the mesh:
ros2 service call /unified_planner/remove_object curobo_msgs/srv/RemoveObject \
"{name: 'part'}"
# Automatically removes:
# - Original mesh from world_cfg.mesh
# - ALL derived cuboids from world_cfg.cuboid
Example: Add Obstacle and Update Planners
# 1. Add obstacle
ros2 service call /unified_planner/add_object curobo_msgs/srv/AddObject \
"{name: 'table', type: 0, dimensions: {x: 1.0, y: 0.8, z: 0.05}, \
pose: {position: {x: 0.5, y: 0.0, z: 0.0}, orientation: {w: 1.0}}}"
# 2. Verify obstacle added
ros2 service call /unified_planner/get_obstacles std_srvs/srv/Trigger
# 3. All planners automatically see the new obstacle (shared reference)
# No manual update needed - world_cfg is shared across all planners
4. CameraSystemManager¶
File: /home/ros2_ws/src/curobo_ros/curobo_ros/core/camera_system_manager.py
Purpose: Load and manage camera configurations for point cloud processing.
Responsibilities:
Load camera configuration from YAML files
Create and configure CameraContext for multiple cameras
Support different camera types (point cloud, depth, RGB-D)
Handle camera intrinsics and extrinsics
Key Methods:
Method |
Returns |
Description |
|---|---|---|
|
|
Returns camera context with all configured cameras |
Camera Configuration YAML Structure:
cameras:
- name: "camera_1"
type: "point_cloud" # or "depth", "rgbd"
topic: "/camera/points"
frame_id: "camera_link"
intrinsics:
fx: 525.0
fy: 525.0
cx: 319.5
cy: 239.5
extrinsics:
# Camera pose relative to robot base
position: {x: 0.5, y: 0.0, z: 0.8}
orientation: {x: 0.0, y: 0.0, z: 0.0, w: 1.0}
Use Cases:
Dynamic obstacle detection from point clouds
Real-time collision avoidance with camera input
Multi-camera fusion for comprehensive workspace coverage
5. RosServiceManager¶
File: /home/ros2_ws/src/curobo_ros/curobo_ros/core/ros_service_manager.py
Purpose: Bridge between ROS interface and internal manager logic.
Responsibilities:
Create and manage all ROS services for collision/obstacle operations
Publish visualization markers (collision spheres)
Delegate service callbacks to appropriate managers
Update planners when world configuration changes
Manage periodic timers for publishing
ROS Services Created:
Service |
Type |
Delegates To |
|---|---|---|
|
|
ObstacleManager |
|
|
ObstacleManager |
|
|
ObstacleManager |
|
|
ObstacleManager |
|
|
ObstacleManager |
|
|
ObstacleManager + RobotModelManager |
|
|
ObstacleManager |
|
|
Self |
ROS Topics Published:
Topic |
Type |
Rate |
Description |
|---|---|---|---|
|
|
0.5 Hz |
Robot collision sphere visualization |
Service Delegation Pattern:
When a ROS service is called:
ROS Client Request
↓
RosServiceManager._callback_*()
↓
Appropriate Manager (ObstacleManager, etc.)
↓
If world changed: ConfigWrapper.update_world_config()
↓
Response returned to client
Example: Trace an Add Object Call
ros2 service call /unified_planner/add_object curobo_msgs/srv/AddObject \
"{name: 'box', type: 0, dimensions: {x: 0.2, y: 0.2, z: 0.2}, \
pose: {position: {x: 0.5, y: 0.3, z: 0.4}, orientation: {w: 1.0}}}"
Internal Flow:
RosServiceManager receives request
Validates request parameters
Calls
obstacle_manager.add_object(request, response)ObstacleManager validates name uniqueness and dimensions
ObstacleManager adds cuboid to
world_cfg.cuboidRosServiceManager calls
config_wrapper.update_world_config()ConfigWrapper clears collision cache
ConfigWrapper calls
motion_gen.update_world(world_cfg)All planners now see the new obstacle (shared reference)
Response returned to client with success status
Initialization Sequence¶
The managers are initialized in a specific order to satisfy dependencies:
Phase 1: ConfigManager¶
ConfigManager(node)
├── Load ROS parameters (base_link, world_file)
├── Load robot configuration from YAML
└── Load initial world configuration from file or default
ROS Parameters:
# View configuration parameters
ros2 param list /unified_planner | grep -E "(base_link|world_file|robot_file)"
ros2 param get /unified_planner base_link
ros2 param get /unified_planner world_file
Phase 2: RobotModelManager¶
RobotModelManager(robot_cfg, robot, base_link)
└── Create CudaRobotModel for GPU kinematics
Dependencies: ConfigManager (needs robot_cfg)
Phase 4: ObstacleManager¶
ObstacleManager(node, config_manager, initial_world_cfg)
├── Initialize world_cfg from ConfigManager
├── Initialize obstacle tracking lists
└── Setup collision cache and checker type (BLOX by default)
Dependencies: ConfigManager (needs initial world_cfg)
Phase 5: CameraSystemManager¶
CameraSystemManager(node, cameras_config_file)
└── Load YAML and create CameraContext
Dependencies: None (standalone)
Phase 3: RosServiceManager¶
RosServiceManager(node, obstacle_mgr, robot_mgr, config_mgr, config_wrapper)
├── Create all ROS services
├── Create collision spheres publisher
└── Create periodic timer (0.5s interval)
Dependencies: All other managers (needs references to delegate to)
Complete Initialization Timeline¶
1. UnifiedPlannerNode created
2. ConfigWrapperMotion created
└── ConfigManager initialized (Phase 1)
└── RobotModelManager initialized (Phase 2)
└── ObstacleManager initialized (Phase 4)
└── CameraSystemManager initialized (Phase 5)
3. RosServiceManager initialized (Phase 3)
4. PlannerManager created
5. MotionGen created and warmed up
6. Node ready - services available
Verify Initialization:
# Check if node is ready
ros2 service call /unified_planner/is_available std_srvs/srv/Trigger
# Expected response:
# success: true
# message: 'Node is available and ready'
Design Patterns¶
1. Composition Over Inheritance¶
Instead of a monolithic class with all functionality, ConfigWrapperMotion uses composition to delegate to specialized managers:
ConfigWrapperMotion (Orchestrator)
├── config_manager (Configuration)
├── robot_model_manager (Kinematics)
├── obstacle_manager (World State)
├── camera_system_manager (Sensors)
└── ros_service_manager (ROS Interface)
Benefits:
Each manager can be tested independently
Changes to one manager don’t affect others
New managers can be added without modifying existing code
2. Single Source of Truth¶
ObstacleManager.world_cfg is the authoritative world configuration after initialization.
ConfigManager.world_cfg (initial template)
↓ (passed once during initialization)
ObstacleManager.world_cfg (SINGLE SOURCE OF TRUTH)
↑ (all access goes through this)
All Planners (MotionGen, MPC, etc.)
Anti-Pattern to Avoid:
# ❌ WRONG - Uses stale initial config
world = config_manager.get_world_config()
# ✅ CORRECT - Uses authoritative runtime config
world = obstacle_manager.get_world_cfg()
3. Lazy Loading¶
Expensive components are created on-first-use to reduce startup time:
MotionGen: Created during initialization (required for most operations)
MPC Solver: Created on-first-use when MPC planner is requested
Planners: Cached in PlannerManager, retrieved instantly on subsequent use
Example:
# First call to MPC planner: ~2-3 seconds (creates MPC solver)
ros2 service call /unified_planner/set_planner curobo_msgs/srv/SetPlanner "{planner_type: 1}"
# Subsequent calls to MPC planner: <10ms (retrieves cached planner)
ros2 service call /unified_planner/set_planner curobo_msgs/srv/SetPlanner "{planner_type: 1}"
4. Reference Sharing¶
All planners share the same reference to obstacle_manager.world_cfg:
# In UnifiedPlannerNode initialization:
shared_world_cfg = obstacle_manager.world_cfg # Not a copy!
# In MotionGen:
motion_gen.update_world(shared_world_cfg)
# In MPC:
mpc_solver.world = shared_world_cfg
Benefit: When obstacles change, all planners automatically see updates without manual propagation.
5. Service Delegation Pattern¶
RosServiceManager acts as a facade that delegates to appropriate managers:
ROS Service Request
↓
RosServiceManager._callback_*()
↓
├─ ObstacleManager (add/remove objects)
├─ RobotModelManager (get collision spheres)
├─ ConfigManager (get parameters)
└─ ConfigWrapper (update planners)
↓
Response + Side Effects
Example Implementation:
def _callback_add_object(self, request, response):
# 1. Delegate to ObstacleManager
self.obstacle_manager.add_object(self.node, request, response)
# 2. Update all planners if successful
if response.success:
self.config_wrapper.update_world_config()
return response
6. Strategy Pattern (Planners, Robots, Cameras)¶
The system uses the Strategy Pattern in three key areas to enable runtime flexibility and extensibility.
6.1 Planner Strategy¶
Purpose: Switch between different planning algorithms at runtime without modifying code.
Strategy Interface: TrajectoryPlanner (abstract base class)
plan()- Generate trajectoryexecute()- Execute trajectoryget_planner_name()- Return planner identifier
Concrete Implementations:
TrajectoryPlanner (Abstract)
├── ClassicPlanner - MotionGen-based (open-loop)
├── MPCPlanner - Model Predictive Control (closed-loop)
├── MultiPointPlanner - Multiple waypoint planning
└── JointSpacePlanner - Joint-space interpolation
Factory & Manager: PlannerFactory creates planners, PlannerManager caches and manages them.
Dynamic Switching:
# Switch to classic planner (0)
ros2 service call /unified_planner/set_planner curobo_msgs/srv/SetPlanner "{planner_type: 0}"
# Switch to MPC planner (1)
ros2 service call /unified_planner/set_planner curobo_msgs/srv/SetPlanner "{planner_type: 1}"
Use Cases:
Static environments → Use ClassicPlanner (faster)
Dynamic obstacles → Use MPCPlanner (reactive)
Multiple waypoints → Use MultiPointPlanner
6.2 Robot Strategy¶
Purpose: Abstract hardware-specific robot control implementations for different robot models.
Strategy Interface: JointCommandStrategy (abstract base class)
send_trajectory()- Send trajectory to robot controllerget_joint_pose()- Get current joint positionsstop_robot()- Emergency stopget_progression()- Get execution progress (0.0 to 1.0)
Concrete Implementations:
JointCommandStrategy (Abstract)
├── DoosanControl - Doosan M1013 robot control
├── EmulatorStrategy - Software emulator for testing
├── GhostStrategy - RViz visualization (always active)
└── UR5eControl - Universal Robots UR5e (placeholder)
Context Manager: RobotContext manages strategy selection and provides thread-safe switching.
Dynamic Switching:
# Switch robot strategy via ROS parameter
ros2 param set /unified_planner robot_type "emulator"
# Or via service (if implemented)
ros2 service call /unified_planner/set_robot_strategy ...
Configuration-Driven Selection:
# In launch file or config
robot_type: "doosan_m1013" # Automatically selects DoosanControl strategy
Use Cases:
Real hardware → Use DoosanControl or UR5eControl
Testing without hardware → Use EmulatorStrategy
Visualization → GhostStrategy (always runs alongside real strategy)
Benefits:
Hardware Independence: Same planning code works with different robots
Testing: Use emulator for development, switch to real hardware for deployment
Safety: Test with emulator before risking real hardware
Visualization: Ghost strategy provides real-time RViz feedback
6.3 Camera Strategy¶
Purpose: Support different sensor types for dynamic obstacle detection.
Strategy Interface: CameraStrategy (abstract base class)
get_camera_observation()- Return CameraObservation for collision checkingis_ready()- Check if camera data is availableset_update_callback()- Register callback for new data
Concrete Implementations:
CameraStrategy (Abstract)
├── PointCloudCameraStrategy - 3D point cloud sensors (RealSense, Kinect)
└── DepthMapCameraStrategy - Depth cameras with intrinsics
Context Manager: CameraContext manages multiple cameras and aggregates observations.
Configuration-Driven Setup:
# In camera config YAML
cameras:
- name: "overhead_camera"
type: "point_cloud"
topic: "/camera/points"
frame_id: "camera_link"
- name: "side_camera"
type: "depth_camera"
topic: "/camera/depth/image_raw"
camera_info_topic: "/camera/depth/camera_info"
Multi-Camera Support:
Add multiple cameras via configuration
CameraContext aggregates observations from all ready cameras
Each camera runs independently with its own strategy
Failed cameras don’t block other cameras
Use Cases:
Point cloud sensors → Use PointCloudCameraStrategy
Depth cameras → Use DepthMapCameraStrategy
Multiple viewpoints → Configure multiple cameras in CameraContext
Benefits:
Sensor Flexibility: Support different camera types without code changes
Multi-Camera Fusion: Combine observations from multiple sensors
Extensibility: Add new camera types by implementing CameraStrategy interface
Runtime Management: Dynamically add/remove cameras
Key Data Flows¶
Flow 1: Adding an Obstacle¶
ros2 service call /unified_planner/add_object
↓
RosServiceManager._callback_add_object()
↓
ObstacleManager.add_object()
├─ Validate name uniqueness
├─ Validate dimensions
├─ Convert primitive to collision geometry
└─ Add to world_cfg (cuboid/mesh/capsule/cylinder/sphere)
↓
ConfigWrapper.update_world_config()
├─ Clear collision cache
└─ MotionGen.update_world(world_cfg)
↓
Response: success=true
CLI Example:
# Add table
ros2 service call /unified_planner/add_object curobo_msgs/srv/AddObject \
"{name: 'table', type: 0, dimensions: {x: 1.2, y: 0.8, z: 0.05}, \
pose: {position: {x: 0.6, y: 0.0, z: 0.0}, orientation: {w: 1.0}}}"
# Plan trajectory (automatically avoids table)
ros2 service call /unified_planner/generate_trajectory curobo_msgs/srv/TrajectoryGeneration \
"{goal_pose: {position: {x: 0.5, y: 0.3, z: 0.4}, orientation: {w: 1.0}}}"
Flow 2: Planning a Trajectory¶
ros2 service call /unified_planner/generate_trajectory
↓
UnifiedPlannerNode.generate_trajectory_callback()
↓
PlannerManager.get_current_planner()
↓
Planner.plan(start_state, request, config, robot_context)
├─ Read obstacle_manager.world_cfg (shared reference)
├─ Perform collision checking
└─ Generate trajectory waypoints
↓
Response: trajectory waypoints + success status
CLI Example:
# Plan trajectory to goal pose
ros2 service call /unified_planner/generate_trajectory curobo_msgs/srv/TrajectoryGeneration \
"{goal_pose: {position: {x: 0.5, y: 0.3, z: 0.4}, orientation: {w: 1.0}}}"
Flow 3: Collision Sphere Visualization¶
Timer (0.5s interval)
↓
RosServiceManager.publish_collision_spheres()
↓
RobotModelManager.get_collision_spheres()
├─ RobotModelManager.get_joint_state() (current robot state)
└─ CudaRobotModel.get_state(joint_positions)
↓
Create MarkerArray with sphere markers
↓
Publish to /unified_planner/collision_spheres
CLI Example:
# View collision spheres
ros2 topic echo /unified_planner/collision_spheres
# Each sphere has [x, y, z, radius] in base frame
Flow 4: Updating Collision Cache¶
ros2 service call /unified_planner/set_collision_cache
↓
RosServiceManager._callback_set_collision_cache()
↓
ObstacleManager.set_collision_cache()
├─ Validate cache parameters (obb, mesh, blox)
└─ Update collision_cache attribute
↓
ConfigWrapper.update_world_config() (if needed)
↓
ros2 service call /unified_planner/update_motion_gen_config
↓
Response: success=true
CLI Example:
# Increase OBB cache for more cuboids
ros2 service call /unified_planner/set_collision_cache \
curobo_msgs/srv/SetCollisionCache "{obb: 200, mesh: -1, blox: -1}"
# Apply changes
ros2 service call /unified_planner/update_motion_gen_config std_srvs/srv/Trigger
Manager Interactions Summary¶
Dependency Graph¶
ConfigManager (Phase 1)
├── RobotModelManager (Phase 2) - uses robot_cfg
├── ObstacleManager (Phase 4) - takes initial world_cfg
└── CameraSystemManager (Phase 5) - independent
RosServiceManager (Phase 3)
├── Delegates to ObstacleManager
├── Delegates to RobotModelManager
├── Uses ConfigManager
└── Updates ConfigWrapper
Communication Patterns¶
From |
To |
Via |
Purpose |
|---|---|---|---|
RosServiceManager |
ObstacleManager |
Method call |
Add/remove obstacles |
RosServiceManager |
RobotModelManager |
Method call |
Get collision spheres |
RosServiceManager |
ConfigWrapper |
Method call |
Update planners after world change |
ObstacleManager |
ConfigManager |
Constructor |
Get initial world_cfg |
RobotModelManager |
ConfigManager |
Constructor |
Get robot_cfg |
MotionGen |
ObstacleManager |
Shared reference |
Read world_cfg for collision checking |
MPC |
ObstacleManager |
Shared reference |
Read world_cfg for collision checking |
Backward Compatibility¶
ConfigWrapperMotion provides property-based access for backward compatibility with code expecting direct attribute access:
# Backward-compatible properties
@property
def world_cfg(self):
return self.obstacle_manager.get_world_cfg() # SSOT
@property
def robot_cfg(self):
return self.config_manager.robot_cfg
@property
def base_link(self):
return self.config_manager.base_link
@property
def collision_checker_type(self):
return self.obstacle_manager.collision_checker_type
@property
def camera_context(self):
return self.camera_system_manager.camera_context
Usage:
# Old code still works
world = config_wrapper.world_cfg # Internally calls obstacle_manager.get_world_cfg()
# New code can be explicit
world = config_wrapper.obstacle_manager.get_world_cfg()
Best Practices¶
✅ DO: Access World Config via ObstacleManager¶
# Correct: Get authoritative world configuration
world = obstacle_manager.get_world_cfg()
❌ DON’T: Access World Config via ConfigManager (after initialization)¶
# Wrong: This is the initial template, not runtime state
world = config_manager.get_world_config()
✅ DO: Update Planners After Obstacle Changes¶
# Add obstacle
ros2 service call /unified_planner/add_object curobo_msgs/srv/AddObject "{...}"
# ObstacleManager and RosServiceManager automatically call update_world_config()
# No manual update needed
✅ DO: Call update_motion_gen_config After Parameter Changes¶
# Modify collision cache
ros2 service call /unified_planner/set_collision_cache \
curobo_msgs/srv/SetCollisionCache "{obb: 200, mesh: -1, blox: -1}"
# REQUIRED: Apply changes
ros2 service call /unified_planner/update_motion_gen_config std_srvs/srv/Trigger
✅ DO: Let Managers Handle Their Domain¶
ConfigManager → Configuration loading
RobotModelManager → Kinematics
ObstacleManager → World state (SSOT)
CameraSystemManager → Sensor configuration
RosServiceManager → ROS interface
Don’t bypass managers by directly modifying internal state.
Troubleshooting¶
Issue 1: Collision Cache Errors¶
Symptoms: Collision cache exceeded error when adding obstacles
Possible Causes:
Too many obstacles for default cache size
Mesh voxelization created too many cuboids
Solution:
# Increase collision cache
ros2 service call /unified_planner/set_collision_cache \
curobo_msgs/srv/SetCollisionCache "{obb: 300, mesh: -1, blox: -1}"
# Apply changes
ros2 service call /unified_planner/update_motion_gen_config std_srvs/srv/Trigger
Issue 2: Manager Initialization Order Error¶
Symptoms: AttributeError: Manager not initialized during startup
Possible Causes:
Managers accessed before initialization complete
Incorrect initialization order
Solution:
Check logs for initialization sequence
Verify all managers follow Phase 1→2→4→5→3 order
Ensure dependencies are satisfied (ConfigManager first)