Lab7: ROS2 Services - Music and Dance
Introduction
In this lab, you will learn about ROS2 Services, a request-response communication pattern. Unlike topics (publish-subscribe), services allow synchronous communication where a client sends a request and waits for a response. You’ll use the Mini Pupper’s music and dance services as practical examples.
Prerequisites
- Completed Lab1-Lab6
- Mini Pupper with ROS2 installed
- Understanding of ROS2 topics from previous labs
Part 1: Understanding ROS2 Services
Topics vs Services
| Feature | Topics | Services |
|---|---|---|
| Pattern | Publish-Subscribe | Request-Response |
| Communication | Asynchronous | Synchronous |
| Use Case | Continuous data streams | One-time requests |
| Example | Sensor data, camera feed | Play music, trigger action |
Service Architecture
┌─────────────────┐ Request ┌─────────────────┐
│ │ ─────────────────────────►│ │
│ Service Client │ │ Service Server │
│ │ ◄─────────────────────────│ │
└─────────────────┘ Response └─────────────────┘
Part 2: Exploring Services
List Available Services
export ROS_DOMAIN_ID=30
source ~/ros2_ws/install/setup.bash
# List all services
ros2 service list
Get Service Type
# Get the type of a service
ros2 service type /play_music
View Service Definition
# Show the service interface
ros2 interface show mini_pupper_interfaces/srv/PlayMusic
Example output:
# Request
string file_name
int32 start_second
---
# Response
bool success
string message
Part 3: Music Service
Step 1: Launch the Robot
On Mini Pupper:
export ROS_DOMAIN_ID=30
source ~/ros2_ws/install/setup.bash
ros2 launch mini_pupper_bringup bringup.launch.py hardware_connected:=True
Step 2: Launch Music Service
In a new terminal:
export ROS_DOMAIN_ID=30
source ~/ros2_ws/install/setup.bash
ros2 launch mini_pupper_music music.launch.py
Step 3: Call the Service
Play Music
ros2 service call /play_music mini_pupper_interfaces/srv/PlayMusic \
"{file_name: 'robot1.mp3', start_second: 3}"
Stop Music
ros2 service call /stop_music mini_pupper_interfaces/srv/StopMusic
Explore the Music Package
# What is the package name?
ros2 pkg list | grep music
# What nodes are in the package?
ros2 pkg executables mini_pupper_music
# What services does the node provide?
ros2 service list | grep music
Part 4: Dance Service
Launch Dance Service
export ROS_DOMAIN_ID=30
source ~/ros2_ws/install/setup.bash
ros2 launch mini_pupper_dance dance.launch.py
Trigger Dance Routine
ros2 service call /dance mini_pupper_interfaces/srv/Dance "{routine: 'wave'}"
Available Dance Routines
Explore the dance package to find available routines:
# View the service definition
ros2 interface show mini_pupper_interfaces/srv/Dance
Part 5: Creating a Service Client in Python
Basic Service Client
Create music_client.py:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from mini_pupper_interfaces.srv import PlayMusic, StopMusic
class MusicClient(Node):
def __init__(self):
super().__init__('music_client')
# Create service clients
self.play_client = self.create_client(PlayMusic, '/play_music')
self.stop_client = self.create_client(StopMusic, '/stop_music')
# Wait for services to be available
while not self.play_client.wait_for_service(timeout_sec=1.0):
self.get_logger().info('Waiting for play_music service...')
while not self.stop_client.wait_for_service(timeout_sec=1.0):
self.get_logger().info('Waiting for stop_music service...')
self.get_logger().info('Music services are available!')
def play_music(self, file_name, start_second=0):
"""Send request to play music"""
request = PlayMusic.Request()
request.file_name = file_name
request.start_second = start_second
future = self.play_client.call_async(request)
rclpy.spin_until_future_complete(self, future)
response = future.result()
if response.success:
self.get_logger().info(f'Playing: {file_name}')
else:
self.get_logger().error(f'Failed: {response.message}')
return response
def stop_music(self):
"""Send request to stop music"""
request = StopMusic.Request()
future = self.stop_client.call_async(request)
rclpy.spin_until_future_complete(self, future)
self.get_logger().info('Music stopped')
return future.result()
def main():
rclpy.init()
client = MusicClient()
# Play music
client.play_music('robot1.mp3', start_second=0)
# Wait 5 seconds
import time
time.sleep(5)
# Stop music
client.stop_music()
client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Run the Client
python3 music_client.py
Part 6: Creating a Dance Client
Create dance_client.py:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from mini_pupper_interfaces.srv import Dance
class DanceClient(Node):
def __init__(self):
super().__init__('dance_client')
self.client = self.create_client(Dance, '/dance')
while not self.client.wait_for_service(timeout_sec=1.0):
self.get_logger().info('Waiting for dance service...')
self.get_logger().info('Dance service is available!')
def dance(self, routine):
"""Send request to perform dance routine"""
request = Dance.Request()
request.routine = routine
future = self.client.call_async(request)
rclpy.spin_until_future_complete(self, future)
response = future.result()
self.get_logger().info(f'Dance response: {response}')
return response
def main():
rclpy.init()
client = DanceClient()
# Perform dance
client.dance('wave')
client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Part 7: Adding Services to Launch Files
Instead of running multiple terminals, add service nodes to your launch file.
Create Custom Launch File
Create bringup_with_services.launch.py:
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description():
# Include the base bringup
bringup_launch = IncludeLaunchDescription(
PythonLaunchDescriptionSource([
os.path.join(
get_package_share_directory('mini_pupper_bringup'),
'launch',
'bringup.launch.py'
)
]),
launch_arguments={'hardware_connected': 'True'}.items()
)
# Add music server node
music_server_node = Node(
package="mini_pupper_music",
namespace="",
executable="service",
name="music_server",
)
# Add dance server node
dance_server_node = Node(
package="mini_pupper_dance",
namespace="",
executable="service",
name="dance_server",
)
return LaunchDescription([
bringup_launch,
music_server_node,
dance_server_node,
])
Part 8: Combining Services with Topics
Create a node that plays music when the robot starts moving:
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
from mini_pupper_interfaces.srv import PlayMusic, StopMusic
class MusicOnMove(Node):
def __init__(self):
super().__init__('music_on_move')
# Subscribe to velocity commands
self.subscription = self.create_subscription(
Twist,
'/cmd_vel',
self.velocity_callback,
10
)
# Service clients
self.play_client = self.create_client(PlayMusic, '/play_music')
self.stop_client = self.create_client(StopMusic, '/stop_music')
self.is_playing = False
self.get_logger().info('Music on move node started')
def velocity_callback(self, msg):
is_moving = abs(msg.linear.x) > 0.1 or abs(msg.angular.z) > 0.1
if is_moving and not self.is_playing:
self.play_music()
elif not is_moving and self.is_playing:
self.stop_music()
def play_music(self):
if not self.play_client.wait_for_service(timeout_sec=0.5):
return
request = PlayMusic.Request()
request.file_name = 'robot1.mp3'
request.start_second = 0
self.play_client.call_async(request)
self.is_playing = True
self.get_logger().info('Started playing music')
def stop_music(self):
if not self.stop_client.wait_for_service(timeout_sec=0.5):
return
request = StopMusic.Request()
self.stop_client.call_async(request)
self.is_playing = False
self.get_logger().info('Stopped music')
def main():
rclpy.init()
node = MusicOnMove()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Exercises
Exercise 1: Dance on Touch
Create a node that triggers a dance when a touch sensor is activated (combine with Lab1).
Exercise 2: Music Playlist
Create a service client that plays multiple songs in sequence.
Exercise 3: Custom Service
Create your own ROS2 service that controls the LCD display.
Exercise 4: Service with Feedback
Modify the music client to check if music is currently playing before sending a new request.
Troubleshooting
| Issue | Solution |
|---|---|
| Service not found | Ensure service server is running |
| Timeout waiting for service | Check ROS_DOMAIN_ID matches |
| Music not playing | Check audio device: aplay -l |
| Dance not working | Verify robot is in standing position |
Debugging Commands
# List all services
ros2 service list
# Check service type
ros2 service type /play_music
# Call service from command line
ros2 service call /play_music mini_pupper_interfaces/srv/PlayMusic "{file_name: 'test.mp3', start_second: 0}"
# View node graph
rqt_graph
Summary
In this lab, you learned:
- The difference between ROS2 topics and services
- How to explore and call services from command line
- How to create Python service clients
- How to add service nodes to launch files
- How to combine services with topic subscriptions