Navigation
The navigation module provides autonomous navigation capabilities using the Nav2 stack. It supports two primary navigation modes: SLAM (Simultaneous Localization and Mapping) and AMCL (Adaptive Monte Carlo Localization).
Verbose Logging
Enable verbose logs to see detailed client and Nav2 activity in the console. This is useful while developing or debugging navigation flows.
// Enable verbose logging for core ROS operations
oloClient.core.setVerboseLogging(true);
// Enable verbose logging for Nav2 (status and log broadcasts)
oloClient.navigation.setVerboseLogging(true);
// ... run navigation operations as normal
// Disable later if desired
oloClient.core.setVerboseLogging(false);
oloClient.navigation.setVerboseLogging(false);Navigation Modes: SLAM vs AMCL
SLAM Mode - Real-time Mapping
- Purpose: Build a map in real-time while navigating
- Best for: New or unknown environments, first-time exploration
- How it works: Robot simultaneously tracks its position and creates a map of the environment
- Map persistence: Map exists only in memory until explicitly saved
- Localization: Uses odometry and sensor data to estimate position relative to the growing map
- Use cases: Exploration, mapping new areas, environments that change frequently
AMCL Mode - Map-based Localization
- Purpose: Navigate using a pre-existing saved map
- Best for: Known environments with saved maps, production navigation
- How it works: Robot localizes itself within a static, pre-loaded map
- Map persistence: Uses permanently saved maps from database
- Localization: Particle filter algorithm compares sensor readings to known map
- Use cases: Repeated navigation tasks, delivery routes, known warehouse/office environments
Choosing Between SLAM and AMCL
Use SLAM when:
- ๐บ๏ธ Exploring new environments - First time in an unknown space
- ๐ Environment changes frequently - Furniture moves, doors open/close regularly
- ๐ Creating accurate maps - Need to map the space for future use
- ๐งช Prototyping and testing - Developing navigation algorithms
- ๐ Quick setup - No existing maps available, want to start immediately
Use AMCL when:
- ๐ข Known environments - Have a saved map of the space
- ๐ Production deployments - Reliable, repeatable navigation tasks
- โก Better performance - Faster localization with existing map
- ๐ฏ Precise navigation - More accurate positioning with good maps
- ๐ Stable environments - Space layout doesn't change much
Navigation Workflow
For New Environments (SLAM):
- Start navigation in SLAM mode
- Drive robot around to build map
- Save the completed map to database
- Switch to AMCL mode for future navigation
For Known Environments (AMCL):
- Find and select a saved map from database
- Start navigation in AMCL mode with selected map
- Set initial pose to tell robot where it starts on the map
- Navigate using the saved map for precise localization
Hybrid Approach (Recommended):
- Phase 1 - Mapping: Use SLAM to explore and map new areas
- Phase 2 - Production: Switch to AMCL for daily operations
- Phase 3 - Updates: Periodically re-map areas that change
- Phase 4 - Optimization: Fine-tune maps and navigation parameters
Multi-Robot Support and Namespacing
The OLO navigation system supports multiple robots running simultaneously through ROS2 namespace isolation. Each robot operates independently with its own Nav2 stack, topics, and frames.
How It Works:
Specify a robotNamespace when starting navigation or sending goals. The system automatically:
- Isolates Nav2 processes and topics per namespace (e.g.,
/robot1/scan,/robot1/cmd_vel) - Transforms frame IDs (e.g.,
robot1/base_link,robot1/map) - Structures parameters for proper ROS2 namespace lookup
Usage Example:
// Discover available robots
const robots = await oloClient.core.discoverRobots();
// Start navigation for robot1
await oloClient.navigation.startNavigationEngine({
mode: 'slam',
robotNamespace: robots[0].namespace // e.g., 'robot1'
});
// Start navigation for robot2
await oloClient.navigation.startNavigationEngine({
mode: 'slam',
robotNamespace: robots[1].namespace // e.g., 'robot2'
});
// Send goals to specific robots
await oloClient.navigation.navigateTo(
{ x: 2.0, y: 1.5, yaw: 0.0 },
{ robotNamespace: 'robot1' }
);
await oloClient.navigation.navigateTo(
{ x: -1.0, y: 2.0, yaw: 1.57 },
{ robotNamespace: 'robot2' }
);Robot Requirements:
Your robots must publish topics and frames with namespace prefixes (e.g., /robot1/scan, robot1/base_link). Use discoverRobots() to automatically detect properly configured multi-robot setups.
Nav2 Configuration System
Named Configuration System
Navigation configurations are managed through the OLO Portal and stored in the database. Each configuration has three sections:
- Nav2 Configuration - Path planning, costmaps, controllers, and recovery behaviors
- AMCL Configuration - Localization parameters for map-based navigation
- SLAM Configuration - Mapping parameters for SLAM Toolbox
Configuration Usage:
- If no
configNameorconfigIdis specified, the system uses the user's default configuration - Configurations can be created, edited, and managed through the OLO Portal
- Configurations are user-specific and can be shared across robots
- Namespace prefixing is applied automatically when robot namespace is detected
Configuration Best Practices:
- Start with the default configuration to ensure basic functionality
- Create named configurations for different robot behaviors (e.g., "fast", "precise", "conservative")
- Test configuration changes incrementally
- Keep YAML properly indented and valid
- Do NOT add namespace prefixes to frame IDs or topics in your YAML - the system handles this automatically
startNavigationEngine(options)
Start the Nav2 navigation engine in specified mode using a named configuration from the OLO Portal.
Parameters:
options(object) - Navigation engine configurationmode(string) - 'slam' or 'localization' (AMCL)mapId(number) - Map ID for AMCL mode (obtained fromlistMaps())initialPose(object, optional) - Starting pose for AMCL modex(number) - X coordinate in metersy(number) - Y coordinate in metersyaw(number) - Orientation in radians
configName(string, optional) - Name of configuration to use (leave empty for user's default)configId(string, optional) - ID of configuration to use (alternative to configName)robotNamespace(string, optional) - Robot namespace for multi-robot scenarioslidar3d(boolean, optional) - Enable RTAB-Map for 3D SLAM with point cloud data (requires 3D LiDAR sensor)lidar3dTopic(string, optional) - Topic name for 3D LiDAR point cloud data (e.g., '/velodyne_points', '/points')gpsEnabled(boolean, optional) - Enable GPS sensor fusion for improved localization accuracy (requires GPS sensor)
Configuration Selection:
- If neither
configNamenorconfigIdis specified, the user's default configuration is used - Configurations are managed through the OLO Portal and contain Nav2, AMCL, and SLAM settings
- Each configuration has three sections that are automatically applied based on the mode
Multi-Robot Support:
Multiple Nav2 instances can run concurrently by specifying different robotNamespace values. Each namespace gets its own independent Nav2 stack with isolated topics and frames.
3D SLAM with RTAB-Map:
When lidar3d: true is specified, the system uses RTAB-Map for 3D simultaneous localization and mapping with point cloud data from 3D LiDAR sensors (e.g., Velodyne, Ouster, Hesai). This creates 3D maps with richer environmental understanding compared to traditional 2D SLAM.
GPS Sensor Fusion:
When gpsEnabled: true is specified, the system fuses GPS data with LiDAR, IMU, and odometry for improved localization accuracy, especially useful for outdoor navigation or large-scale environments.
Returns: Promise<boolean> - True if engine started successfully
// SLAM Mode with default configuration (single robot)
await oloClient.navigation.startNavigationEngine({
mode: 'slam'
// No configName specified - uses user's default configuration
});
// SLAM Mode with a specific named configuration
await oloClient.navigation.startNavigationEngine({
mode: 'slam',
configName: 'fast-navigation' // Use a configuration created in the portal
});
// 3D SLAM Mode with RTAB-Map (requires 3D LiDAR)
await oloClient.navigation.startNavigationEngine({
mode: 'slam',
lidar3d: true,
lidar3dTopic: '/velodyne_points', // Your 3D LiDAR point cloud topic
configName: '3d-mapping'
});
// SLAM with GPS sensor fusion (requires GPS sensor)
await oloClient.navigation.startNavigationEngine({
mode: 'slam',
gpsEnabled: true,
configName: 'outdoor-navigation'
});
// 3D SLAM with GPS fusion for outdoor mapping
await oloClient.navigation.startNavigationEngine({
mode: 'slam',
lidar3d: true,
lidar3dTopic: '/ouster/points',
gpsEnabled: true,
configName: 'outdoor-3d-mapping'
});
// SLAM Mode for specific robot in multi-robot setup
const robots = await oloClient.core.discoverRobots();
await oloClient.navigation.startNavigationEngine({
mode: 'slam',
robotNamespace: robots[0].namespace, // e.g., 'robot1'
configName: 'precise-mapping'
});
// Multi-robot: Start navigation for multiple robots concurrently
await oloClient.navigation.startNavigationEngine({
mode: 'slam',
robotNamespace: 'robot1',
configName: 'fast-navigation'
});
await oloClient.navigation.startNavigationEngine({
mode: 'slam',
robotNamespace: 'robot2',
configName: 'conservative'
});
// Both robots now navigate independently with isolated Nav2 stacks
// AMCL Mode with saved map
const maps = await oloClient.navigation.listMaps({ limit: 1 });
const savedMap = maps.maps[0];
await oloClient.navigation.startNavigationEngine({
mode: 'localization',
mapId: savedMap.id,
initialPose: { x: 0.0, y: 0.0, yaw: 0.0 },
configName: 'precise-localization'
});
// AMCL Mode with GPS fusion for outdoor localization
await oloClient.navigation.startNavigationEngine({
mode: 'localization',
mapId: savedMap.id,
initialPose: { x: 0.0, y: 0.0, yaw: 0.0 },
gpsEnabled: true,
configName: 'outdoor-localization'
});stopNavigationEngine()
Stop the Nav2 navigation engine.
Returns: Promise<boolean> - True if engine stopped successfully
await oloClient.navigation.stopNavigationEngine();sendNavigationGoal(goal, options)
Send a navigation goal to the robot.
Parameters:
goal(object) - Navigation goal with x, y coordinates and optional yawoptions(object, optional) - Navigation optionsonResult(function) - Callback when navigation result is received (success/fail/cancel)onError(function) - Callback for errors in sending/handling the goalonFeedback(function) - Callback for navigation progress feedbackabortSignal(AbortSignal) - Optional signal to auto-cancel the goal (e.g., SDK Playground Stop button)timeoutMs(number) - Optional timeout that auto-cancels the goal after N msrobotNamespace(string) - Optional robot namespace for multi-robot scenarios
Returns: Promise<string> - Goal ID
// Single robot or global navigation
const goalId = await oloClient.navigation.sendNavigationGoal(
{ x: 2.0, y: 1.5, yaw: 0.0 },
{
onResult: (message) => console.log('Navigation result:', message),
onError: (err) => console.error('Navigation error:', err),
onFeedback: (fb) => console.log('Progress:', fb),
// In SDK Playground, abortSignal is provided globally; in standalone apps, use your own AbortController
abortSignal,
timeoutMs: 60000
}
);
// Multi-robot navigation - send goal to specific robot
const robots = await oloClient.core.discoverRobots();
const goalId = await oloClient.navigation.sendNavigationGoal(
{ x: 2.0, y: 1.5, yaw: 0.0 },
{
robotNamespace: robots[0].namespace, // e.g., 'robot1'
onResult: (message) => console.log('Navigation result:', message),
abortSignal,
timeoutMs: 60000
}
);navigateTo(goal, options)
High-level helper that resolves when the goal completes or is cancelled. Supports abortSignal and timeoutMs like sendNavigationGoal.
Parameters:
goal(object) - Navigation goal with x, y coordinates and optional yawoptions(object, optional)onFeedback(function) - Callback for navigation progress feedbackabortSignal(AbortSignal) - Optional signal to auto-cancel (e.g., SDK Playground Stop button)timeoutMs(number) - Optional timeout that auto-cancels the goal after N msrobotNamespace(string) - Optional robot namespace for multi-robot scenarios
Returns: Promise<Object> - Resolves with { result, status, goalId } on completion or cancellation
// SDK Playground: abortSignal is automatically provided
try {
const result = await oloClient.navigation.navigateTo(
{ x: 1.0, y: 0.5, yaw: 0.0 },
{
abortSignal,
timeoutMs: 60000,
onFeedback: (fb) => console.log('Distance remaining โ', fb.distance_remaining)
}
);
console.log('Goal completed:', result);
} catch (err) {
if (abortSignal.aborted) {
console.log('Navigation aborted by user');
} else {
console.error('Navigation failed:', err);
}
}
// Multi-robot navigation - navigate specific robot
const robots = await oloClient.core.discoverRobots();
const result = await oloClient.navigation.navigateTo(
{ x: 1.0, y: 0.5, yaw: 0.0 },
{
robotNamespace: robots[0].namespace, // Target specific robot
abortSignal,
timeoutMs: 60000,
onFeedback: (fb) => console.log('Distance remaining โ', fb.distance_remaining)
}
);Which API should I use?
-
Use
navigateTo(recommended) when:- You want a simple, sequential flow that awaits goal completion/cancel.
- Youโre wiring the SDK Playground Stop button (pass
abortSignal) or want a builtโintimeoutMs. - You donโt need to juggle multiple concurrent goals or custom event routing.
-
Use
sendNavigationGoalwhen you need advanced control:- Managing multiple concurrent goals and retaining each goalโs ID.
- Fineโgrained handling of streaming updates (ack/status/feedback/result) to drive a custom UI.
- Custom cancellation/retry strategies and orchestration with other subsystems.
- Integrating goal lifecycle with your own state machine or telemetry pipeline.
cancelNavigationGoal(requestId, options)
Cancel current navigation goal.
Parameters:
requestId(string, optional) - Specific request ID to canceloptions(object, optional) - Cancel optionsrobotNamespace(string) - Optional robot namespace for multi-robot scenarios
Returns: Promise - Resolves when goal is cancelled
// Cancel global navigation goal
await oloClient.navigation.cancelNavigationGoal();
// Cancel navigation goal for specific robot
await oloClient.navigation.cancelNavigationGoal(null, {
robotNamespace: 'robot1'
});
// Cancel specific goal by ID for specific robot
await oloClient.navigation.cancelNavigationGoal('goal_id_123', {
robotNamespace: 'robot1'
});getNavigationStatus()
Get current navigation system status.
Returns: Object - Navigation status information
const status = oloClient.navigation.getNavigationStatus();
console.log('Nav2 available:', status.nav2_available);
console.log('Running components:', status.running_components);setInitialPose(pose)
Set initial pose for AMCL localization. This is critical for AMCL mode - the robot needs to know where it starts on the saved map.
When to use:
- Required for AMCL mode: Robot must know its starting position on the saved map
- Not needed for SLAM mode: SLAM builds the map relative to starting position
- After map loading: Set pose after starting AMCL with a saved map
- When robot is lost: Reset localization if robot becomes confused about its position
How to determine initial pose:
- Known position: If you know where robot starts (e.g., charging dock at coordinates 2.5, 1.0)
- Map center: Use center of saved map as fallback (automatically calculated)
- Previous session: Use last known position from previous navigation session
- Manual placement: Drive robot to known landmark, then set pose to that landmark's coordinates
Parameters:
pose(object) - Initial pose in map coordinatesx(number) - X coordinate in meters (map frame)y(number) - Y coordinate in meters (map frame)yaw(number) - Orientation in radians (0 = facing positive X direction)options(object, optional) - Additional optionsrobotNamespace(string) - Optional robot namespace for multi-robot scenarios
Returns: Promise<boolean> - True if pose set successfully
// Set pose at robot's known starting location
await oloClient.navigation.setInitialPose({
x: 2.5, // 2.5 meters from map origin
y: 1.0, // 1.0 meters from map origin
yaw: 1.57 // Facing 90 degrees (ฯ/2 radians)
});
// Set pose at map center (safe fallback)
const mapInfo = await oloClient.navigation.getCurrentMapInfo();
const centerX = mapInfo.origin.position.x + (mapInfo.width * mapInfo.resolution) / 2;
const centerY = mapInfo.origin.position.y + (mapInfo.height * mapInfo.resolution) / 2;
await oloClient.navigation.setInitialPose({
x: centerX,
y: centerY,
yaw: 0.0
});
// Use current odometry as seed (if robot hasn't moved much)
const currentPose = await oloClient.navigation.getCurrentPose();
await oloClient.navigation.setInitialPose({
x: currentPose.x,
y: currentPose.y,
yaw: currentPose.yaw
});
// Multi-robot: Set initial pose for specific robot
await oloClient.navigation.setInitialPose({
x: 2.5,
y: 1.0,
yaw: 1.57
}, {
robotNamespace: 'robot1'
});Important Notes:
- Coordinate system: Pose coordinates are in the map frame, not robot frame
- Accuracy matters: Inaccurate initial pose can cause navigation failures
- AMCL will refine: Particle filter will improve pose estimate over time using sensor data
- Multiple attempts: AMCL may need a few seconds to converge on correct localization
- Multi-robot support: Use
robotNamespaceto set initial pose for specific robots in multi-robot scenarios
getCurrentPose()
Get robot's current pose.
Returns: Promise<Object> - Current robot pose
const pose = await oloClient.navigation.getCurrentPose();
console.log('Robot position:', pose.x, pose.y, pose.yaw);saveMap(mapName, options)
Save current map with given name.
Parameters:
mapName(string) - Name for the saved mapoptions(object, optional) - Save optionsdescription(string) - Map description (optional)onProgress(function) - Progress callback (optional)onSuccess(function) - Success callback (optional)onError(function) - Error callback (optional)robotNamespace(string) - Robot namespace for multi-robot scenarios (optional)
Returns: Promise<Object> - Map save result with ID, name, and metadata
// Save map for global/single robot
await oloClient.navigation.saveMap('office_map', {
description: 'Office floor map with cubicles'
});
// Save map for specific robot in multi-robot setup
await oloClient.navigation.saveMap('warehouse_map', {
description: 'Warehouse navigation map',
robotNamespace: 'robot1',
onProgress: (step) => console.log('Save progress:', step),
onSuccess: (result) => console.log('Map saved:', result.name),
onError: (error) => console.error('Save failed:', error.message)
});
// Get detailed save result
const result = await oloClient.navigation.saveMap('detailed_map', {
description: 'Map with full metadata',
robotNamespace: 'burger'
});
console.log('Map saved with ID:', result.id);
console.log('File size:', (result.file_size / 1024).toFixed(1) + 'KB');
console.log('Dimensions:', result.metadata.width + 'x' + result.metadata.height);loadMap(mapName)
Load a previously saved map.
Parameters:
mapName(string) - Name of map to load
Returns: Promise<boolean> - True if map loaded successfully
await oloClient.navigation.loadMap('office_map');clearCostmaps()
Clear navigation costmaps (useful when robot gets stuck).
Returns: Promise<boolean> - True if costmaps cleared successfully
await oloClient.navigation.clearCostmaps();clearSlamMap()
Clear/reset the SLAM map to start fresh mapping.
Returns: Promise - Resolves when map is cleared
await oloClient.navigation.clearSlamMap();Map Discovery and Selection
Before using AMCL mode, you need to find and select a saved map. The OLO platform provides comprehensive map management capabilities.
listMaps(options)
Get list of saved maps for current user.
Parameters:
options(object, optional) - List optionslimit(number) - Maximum number of maps (default: 50)offset(number) - Offset for pagination (default: 0)search(string) - Search term to filter maps by name (optional)mapType(string) - Filter by map type (optional)
Returns: Promise<Object> - Maps list with pagination info and metadata
Response Structure:
{
maps: [
{
id: 123,
name: "office_map_2024-01-15",
description: "Office floor map with cubicles",
created_at: "2024-01-15T10:30:00Z",
updated_at: "2024-01-15T10:30:00Z",
size_bytes: 2048576,
width: 2000,
height: 1500,
resolution: 0.05,
area_meters: 7500.0,
user_id: 456
}
],
total: 15,
offset: 0,
limit: 50,
has_more: false
}// Get all available maps
const maps = await oloClient.navigation.listMaps({ limit: 10 });
console.log(`Found ${maps.total} maps:`);
maps.maps.forEach(map => {
console.log(`- ${map.name} (ID: ${map.id})`);
console.log(` Size: ${map.width}x${map.height}, Area: ${map.area_meters.toFixed(1)}mยฒ`);
console.log(` Created: ${new Date(map.created_at).toLocaleDateString()}`);
});
// Search for specific maps
const officeMaps = await oloClient.navigation.listMaps({
search: 'office',
limit: 5
});
// Get the most recent map
const recentMaps = await oloClient.navigation.listMaps({
limit: 1
}); // Maps are returned newest first
const latestMap = recentMaps.maps[0];getMap(mapId, includeData)
Get detailed information about a specific map, optionally including the full map data.
Parameters:
mapId(number) - Map ID (fromlistMaps()results)includeData(boolean) - Whether to include full map data (default: false)false: Returns only metadata (fast, small response)true: Includes full map data in base64 format (slower, large response)
Returns: Promise<Object> - Map information and optionally map data
// Get map metadata only (fast)
const mapInfo = await oloClient.navigation.getMap(123, false);
console.log('Map info:', mapInfo.name, mapInfo.width + 'x' + mapInfo.height);
// Get map with full data (for loading into AMCL)
const mapWithData = await oloClient.navigation.getMap(123, true);
console.log('Map data size:', mapWithData.map_data.length, 'bytes (base64)');
// Use map data for navigation
await oloClient.navigation.startNavigationEngine({
mode: 'localization',
mapId: mapWithData.id,
initialPose: { x: 0.0, y: 0.0, yaw: 0.0 }
});Complete AMCL Workflow Example
// Complete workflow: Find map, load it, and start AMCL navigation
async function startAmclNavigation() {
// Step 1: Find available maps
const maps = await oloClient.navigation.listMaps({ limit: 10 });
if (maps.maps.length === 0) {
console.error('No saved maps found. Create a map using SLAM mode first.');
return;
}
// Step 2: Select the most recent map (or let user choose)
const selectedMap = maps.maps[0]; // Most recent
console.log(`Using map: ${selectedMap.name} (${selectedMap.width}x${selectedMap.height})`);
// Step 3: Start AMCL with the selected map
await oloClient.navigation.startNavigationEngine({
mode: 'localization',
mapId: selectedMap.id,
initialPose: {
x: 0.0, // Robot's actual starting X position
y: 0.0, // Robot's actual starting Y position
yaw: 0.0 // Robot's actual starting orientation
}
});
console.log('โ
AMCL navigation started with saved map');
// Step 4: Navigate to a goal
await oloClient.navigation.sendNavigationGoal({
x: 2.0, y: 1.5, yaw: 0.0
});
}deleteMap(mapId)
Delete a saved map.
Parameters:
mapId(number) - Map ID to delete
Returns: Promise<Object> - Deletion result
await oloClient.navigation.deleteMap(123);subscribeToLocalization(callback)
Subscribe to robot localization updates.
Parameters:
callback(function) - Callback for pose updates
Returns: Promise<string> - Subscription ID
await oloClient.navigation.subscribeToLocalization((pose) => {
console.log('Robot pose:', pose.x, pose.y, pose.yaw);
});subscribeToMap(callback)
Subscribe to map updates.
Parameters:
callback(function) - Callback for map updates
Returns: Promise<string> - Subscription ID
await oloClient.navigation.subscribeToMap((mapData) => {
console.log('Map updated:', mapData.info.width, 'x', mapData.info.height);
});getCurrentMapInfo()
Get information about the current SLAM map.
Returns: Promise<Object> - Current map information
const mapInfo = await oloClient.navigation.getCurrentMapInfo();
console.log('Map size:', mapInfo.width, 'x', mapInfo.height);
console.log('Resolution:', mapInfo.resolution, 'm/cell');