Voice Output
Introduction
The Kachaka robot is equipped with voice output functionality, enabling voice communication in various situations. This lesson teaches how to make the Kachaka robot speak using ROS2.
Learning Objectives
By the end of this lesson, you will be able to:
- Understand how to use ROS2 actions for robot control
- Implement voice output functionality using Kachaka’s action server
- Create nodes that send commands to the robot
- Handle action client callbacks and responses
Voice Output Functionality of Kachaka
The Kachaka robot can control voice output through an action server. Using this function enables the following uses:
- Robot status notifications
- Operation instructions to users
- Error and warning notifications
- Interactive communication
Code Explanation
Full Code
The following is the code for speak.py:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import rclpy
from kachaka_interfaces.action import ExecKachakaCommand
from kachaka_interfaces.msg import KachakaCommand
from rclpy.action import ActionClient
from rclpy.node import Node
class Speak(Node):
"""
Node for making Kachaka robot speak
"""
def __init__(self) -> None:
"""
Node initialization
- Set up action client
- Wait for server
"""
super().__init__("speak")
self._action_client = ActionClient(
self, ExecKachakaCommand, "/kachaka/kachaka_command/execute"
)
self._action_client.wait_for_server()
def send_goal(self):
"""
Send speech command
Returns:
Future: Future object representing the action execution result
"""
# Create a KachakaCommand message
# This message specifies what command to execute
command = KachakaCommand()
# Set the command type to SPEAK_COMMAND (tells the robot to speak)
command.command_type = KachakaCommand.SPEAK_COMMAND
# Set the text that the robot should speak
command.speak_command_text = "Hello, I am Kachaka"
# Create a goal message for the action
# Goals are what you send to action servers to request an action
goal_msg = ExecKachakaCommand.Goal()
# Attach the command to the goal
goal_msg.kachaka_command = command
# Send the goal asynchronously (non-blocking)
# This returns a Future object that will be completed when the server responds
# The action will execute on the robot, and we can check the result later
return self._action_client.send_goal_async(goal_msg)
def main(args=None):
"""
Main function
- Initialize ROS2
- Create and run node
- Release resources
"""
# Initialize ROS2
rclpy.init(args=args)
# Create node
speak = Speak()
# Send speech command to the robot
# This returns a Future object representing the action execution
future = speak.send_goal()
# Wait for the action to complete
# spin_until_future_complete processes ROS callbacks until the future is done
# This blocks until the robot finishes speaking (or the action fails)
rclpy.spin_until_future_complete(speak, future)
# Destroy node
speak.destroy_node()
# Shutdown ROS2
rclpy.shutdown()
if __name__ == "__main__":
main()Required Imports
import rclpy
from kachaka_interfaces.action import ExecKachakaCommand
from kachaka_interfaces.msg import KachakaCommand
from rclpy.action import ActionClient
from rclpy.node import NodeExplanation of each import:
rclpy: ROS2 Python client library. Provides basic ROS2 functionality.ExecKachakaCommand: Action interface for executing Kachaka commands. Can execute various commands including voice output.KachakaCommand: Kachaka command message type. Defines command types and parameters.ActionClient: ROS2 action client. Provides functionality to communicate with action servers.Node: Base class for ROS2 nodes. All ROS2 nodes inherit from this class.
Implementation of Speak Class
class Speak(Node):
def __init__(self) -> None:
super().__init__("speak")
self._action_client = ActionClient(
self, ExecKachakaCommand, "/kachaka/kachaka_command/execute"
)
self._action_client.wait_for_server()About class initialization:
super().__init__("speak"): Initialize with node name “speak”.ActionClientinitialization:- 1st argument: Node instance (self)
- 2nd argument: Action type (ExecKachakaCommand)
- 3rd argument: Action server name ("/kachaka/kachaka_command/execute")
wait_for_server(): Wait until the action server becomes available.
Sending Voice Commands
def send_goal(self):
command = KachakaCommand()
command.command_type = KachakaCommand.SPEAK_COMMAND
command.speak_command_text = "Hello, I am Kachaka"
goal_msg = ExecKachakaCommand.Goal()
goal_msg.kachaka_command = command
return self._action_client.send_goal_async(goal_msg)Steps for sending commands:
- Create
KachakaCommandmessage - Set command type to
SPEAK_COMMAND - Set text to speak
- Create goal message
- Send goal asynchronously
Main Function
def main(args=None):
rclpy.init(args=args)
speak = Speak()
future = speak.send_goal()
rclpy.spin_until_future_complete(speak, future)
speak.destroy_node()
rclpy.shutdown()Flow of main function processing:
rclpy.init(args=args): Initialize ROS2speak = Speak(): Create instance of Speak nodefuture = speak.send_goal(): Send voice commandrclpy.spin_until_future_complete(speak, future): Wait for command completionspeak.destroy_node(): Destroy noderclpy.shutdown(): Shutdown ROS2
Usage
Prerequisites
- Kachaka robot is running
- ROS2 environment is correctly configured
kachaka_speakpackage is built
Execution Steps
- Run the node:
ros2 run kachaka_speak speakExercises
Make it speak different messages
- Change the content of
speak_command_textto make it speak different messages.
- Change the content of
Make it speak multiple messages in sequence
- Add functionality to manage messages in an array and make it speak them in order.
Check command results
- Process the return value of
send_goal_asyncto check command execution results.
- Process the return value of
Notes
- If the Kachaka robot is not running, it cannot connect to the action server
- It may take time to start as it waits for the action server to become available
- By default, it speaks the message “Hello, I am Kachaka”