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

Reference