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.
- What are ROS 2 Communication Patterns?
- Topics (Publish/Subscribe)
- Services (Request/Response)
- When to Use Topics vs Services
- Running the Examples
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 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.
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);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);- 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
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 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.
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);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);- 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
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);- ✅ 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
- ✅ 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
| 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 |
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.jsExpected 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
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.jsExpected 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 }
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_intsExplore 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.