ROS 2 Basic Concepts Tutorial
This tutorial introduces the fundamental communication patterns in ROS 2: Topics (publish/subscribe) and Services (request/response). These are essential building blocks for any ROS 2 application.
Table of Contents
- What are ROS 2 Communication Patterns?
- Topics (Publish/Subscribe)
- Services (Request/Response)
- When to Use Topics vs Services
- Running the Examples
What are ROS 2 Communication Patterns?
ROS 2 provides three primary communication patterns:
- ๐ก Topics - Continuous data streams (pub/sub)
- ๐ง Services - Remote procedure calls (request/response)
- โก Actions - Long-running tasks with feedback (covered in separate tutorial)
These patterns enable distributed communication between nodes in a robotics system, allowing for flexible, modular architectures.
Topics (Publish/Subscribe)
Concept Overview
Topics implement a publish/subscribe communication pattern where:
- Publishers produce data and send it to a named topic
- Subscribers consume data from the same named topic
- Anonymous - Subscribers don't know which publisher sent the data
- Many-to-many - Multiple publishers and subscribers per topic
- Asynchronous - Publishers don't wait for subscribers
Topics are ideal for continuous data streams like sensor readings, robot state, or camera images.
Basic Publisher Example
const rclnodejs = require('rclnodejs'); async function createPublisher() { await rclnodejs.init(); const node = rclnodejs.createNode('publisher_example_node'); // Create a publisher for String messages on 'topic' const publisher = node.createPublisher('std_msgs/msg/String', 'topic'); let counter = 0; setInterval(() => { const message = `Hello ROS ${counter}`; console.log(`Publishing message: ${message}`); publisher.publish(message); counter++; }, 1000); rclnodejs.spin(node); } createPublisher().catch(console.error);
Basic Subscriber Example
const rclnodejs = require('rclnodejs'); async function createSubscriber() { await rclnodejs.init(); const node = rclnodejs.createNode('subscriber_example_node'); // Create a subscriber for String messages on 'topic' node.createSubscription('std_msgs/msg/String', 'topic', (msg) => { console.log(`Received message: ${typeof msg}`, msg); }); rclnodejs.spin(node); } createSubscriber().catch(console.error);
Topic Features
- Strongly Typed - Messages have well-defined types (e.g.,
std_msgs/msg/String) - Buffered - Publishers can send data even if no subscribers exist
- Discoverable - Use
ros2 topic listto see available topics - Quality of Service - Configure reliability, durability, and latency
Advanced Publisher with Custom Messages
const rclnodejs = require('rclnodejs'); async function publishSensorData() { await rclnodejs.init(); const node = rclnodejs.createNode('sensor_publisher'); // Publisher for geometry messages const publisher = node.createPublisher('geometry_msgs/msg/Twist', 'cmd_vel'); setInterval(() => { // Create a Twist message for robot velocity const twist = { linear: { x: 1.0, y: 0.0, z: 0.0 }, angular: { x: 0.0, y: 0.0, z: 0.5 }, }; console.log('Publishing velocity command'); publisher.publish(twist); }, 100); // 10 Hz rclnodejs.spin(node); } publishSensorData().catch(console.error);
Services (Request/Response)
Concept Overview
Services implement a request/response communication pattern where:
- Service Server provides a computation/service
- Service Client requests the service and waits for response
- Synchronous - Client waits for server response
- One-to-one - One server per service name, multiple clients allowed
- Short-lived - Services should return quickly
Services are ideal for remote procedure calls, configuration requests, or triggering specific actions.
Basic Service Server Example
const rclnodejs = require('rclnodejs'); async function createServiceServer() { await rclnodejs.init(); const node = rclnodejs.createNode('service_example_node'); // Create a service that adds two integers const service = node.createService( 'example_interfaces/srv/AddTwoInts', 'add_two_ints', (request, response) => { console.log(`Request: ${request.a} + ${request.b}`); // Compute the result const result = response.template; result.sum = request.a + request.b; console.log(`Sending response: ${typeof result}`, result); response.send(result); } ); console.log('Service server ready'); rclnodejs.spin(node); } createServiceServer().catch(console.error);
Basic Service Client Example
const rclnodejs = require('rclnodejs'); async function createServiceClient() { await rclnodejs.init(); const node = rclnodejs.createNode('client_example_node'); // Create a client for the add_two_ints service const client = node.createClient( 'example_interfaces/srv/AddTwoInts', 'add_two_ints' ); // Wait for service to become available const serviceAvailable = await client.waitForService(5000); if (!serviceAvailable) { console.log('Service not available'); rclnodejs.shutdown(); return; } // Create request const request = { a: BigInt(10), b: BigInt(15), }; console.log(`Calling service with: ${request.a} + ${request.b}`); // Send request with callback client.sendRequest(request, (response) => { console.log(`Result: ${typeof response}`, response); rclnodejs.shutdown(); }); rclnodejs.spin(node); } createServiceClient().catch(console.error);
Service Features
- Request/Response Structure - Services define both request and response message types
- Blocking - Clients wait for server response
- Error Handling - Services can fail and return errors
- Discoverability - Use
ros2 service listto see available services
Practical Service Example: Robot Configuration
const rclnodejs = require('rclnodejs'); class RobotConfigurationService { constructor() { this.robotConfig = { maxSpeed: 2.0, safetyEnabled: true, operationMode: 'autonomous', }; } async start() { await rclnodejs.init(); this.node = rclnodejs.createNode('robot_config_service'); // Service to get robot configuration this.node.createService( 'example_interfaces/srv/Trigger', 'get_robot_config', (request, response) => { const result = response.template; result.success = true; result.message = JSON.stringify(this.robotConfig); response.send(result); } ); // Service to set max speed this.node.createService( 'example_interfaces/srv/SetBool', 'set_safety_mode', (request, response) => { this.robotConfig.safetyEnabled = request.data; const result = response.template; result.success = true; result.message = `Safety mode set to: ${request.data}`; response.send(result); } ); console.log('Robot configuration services ready'); rclnodejs.spin(this.node); } } const configService = new RobotConfigurationService(); configService.start().catch(console.error);
When to Use Topics vs Services
Use Topics When:
- โ Continuous data - Sensor readings, status updates
- โ Multiple consumers - Many nodes need the same data
- โ Asynchronous - Publisher doesn't need immediate response
- โ High frequency - Data published regularly (> 1 Hz)
- โ Fire-and-forget - Don't care if anyone receives the data
Examples: Camera images, laser scans, robot odometry, joint states
Use Services When:
- โ Request/response - Need a specific computation or action
- โ Occasional use - Triggered by events, not continuous
- โ Synchronous - Need to wait for result before continuing
- โ Configuration - Setting parameters or modes
- โ Validation - Need confirmation that action succeeded
Examples: Calculating path, setting robot mode, triggering calibration, querying status
Quick Comparison
| Aspect | Topics | Services |
|---|---|---|
| Pattern | Publish/Subscribe | Request/Response |
| Communication | Asynchronous | Synchronous |
| Frequency | Continuous/High | Occasional/On-demand |
| Response | No response | Always responds |
| Multiple consumers | Yes | One server, many clients |
| Use case | Data streams | Remote procedure calls |
Running the Examples
Topic Examples
Run the publisher and subscriber in separate terminals:
# Terminal 1 - Start subscriber cd /path/to/rclnodejs node example/topics/subscriber/subscription-example.js # Terminal 2 - Start publisher cd /path/to/rclnodejs node example/topics/publisher/publisher-example.js
Expected Output:
Subscriber terminal:
Received message: string Hello ROS 0
Received message: string Hello ROS 1
Received message: string Hello ROS 2
Publisher terminal:
Publishing message: Hello ROS 0
Publishing message: Hello ROS 1
Publishing message: Hello ROS 2
Service Examples
Run the service server and client in separate terminals:
# Terminal 1 - Start service server cd /path/to/rclnodejs node example/services/service/service-example.js # Terminal 2 - Start service client cd /path/to/rclnodejs node example/services/client/client-example.js
Expected Output:
Service server terminal:
Incoming request: object { a: 45n, b: 67n }
Sending response: object { sum: 112n }
Service client terminal:
Sending: object { a: 45n, b: 67n }
Result: object { sum: 112n }
Using ROS 2 CLI Tools
Monitor your topics and services:
# List all active topics ros2 topic list # Listen to topic messages ros2 topic echo /topic # Show topic information ros2 topic info /topic # List all services ros2 service list # Call a service manually ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5, b: 10}" # Show service type ros2 service type /add_two_ints
Additional Examples
Explore more patterns in the examples directory:
example/topics/publisher/- Various publisher patternsexample/topics/subscriber/- Different subscription approachesexample/services/- Service implementation examples- QoS Examples - Quality of Service configuration
- Message Examples - Working with complex message types
This tutorial covers the fundamental communication patterns that form the backbone of any ROS 2 application. Topics and services provide the foundation for building distributed, modular robotics systems with rclnodejs.