ROS2行动
版权信息
Copyright 2023 Herman Ye@Auromix. All rights reserved.
This course and all of its associated content, including but not limited to text,
images, videos, and any other materials, are protected by copyright law.
The author holds all rights to this course and its contents.
Any unauthorized use, reproduction, distribution, or modification of this course
or its contents is strictly prohibited and may result in legal action.
This includes, but is not limited to:
Copying or distributing course materials without express written permission.
Reposting, sharing, or distributing course content on any platform without proper attribution and permission.
Creating derivative works based on this course without permission.
Permissions and Inquiries
If you wish to use or reproduce any part of this course for purposes other than personal learning,
please contact the author to request permission.
The course content is provided for educational purposes, and the author makes no warranties or representations
regarding the accuracy, completeness, or suitability of the course content for any specific purpose.
The author shall not be held liable for any damages, losses,
or other consequences resulting from the use or misuse of this course.
Please be aware that this course may contain materials or images obtained from third-party sources.
The author and course creator diligently endeavor to ensure that these materials
are used in full compliance with copyright and fair use regulations.
If you have concerns about any specific content in this regard,
please contact the author for clarification or resolution.
By enrolling in this course, you agree to abide by the terms and conditions outlined in this copyright notice.
学习目标
- 熟悉ROS2的行动概念
- 了解ROS2行动相关的命令行工具操作
- 熟悉行动通信中的动作客户端和动作服务器代码编写
- 熟悉行动通信的测试手段
难度级别
初级 | 中级 | 高级 |
---|---|---|
√ |
预计耗时
45 mins
学习前提
对象 | 类型 | 状态 |
---|---|---|
ROS2 Humble | 软件 | 已安装 |
Ubuntu22.04操作系统 | 软件 | 已确认 |
Shell的基本使用 | 知识 | 已了解 |
ROS2 节点 | 知识 | 已了解 |
ROS2 话题 | 知识 | 已了解 |
ROS2 服务 | 知识 | 已了解 |
什么是行动?
在ROS2中,除了常见的话题通信和服务通信,还存在一种重要通信机制,称为Action通信。
根据牛津词典的定义,“action"一词的核心释义是"process”,即过程,表明它具有持续性。因此,可以将Action翻译为"行动"。
the process of doing something in order to make something happen or to deal with a situation
虽然这个定义抽象,但在ROS2中,可以将机器人的某些行为称为"Action"。以turtlesim
为例,它包含一种称为"rotate_absolute"
的行动,可用于让海龟机器人在当前位置绕绝对指定的角度旋转。
使用话题机制(Topic)时,无法知道海龟机器人是否已到达目的地,而使用服务机制(Service)时,可以知道目标已完成,但无法获取过程中的信息。因此,Action通信机制被引入,以提供关于行动执行过程的反馈信息。
Action通信适用于涉及跨足时间的目标任务,在此时间跨度内需要连续的过程反馈。
另一个典型的场景是机器人系统可能会使用动作进行导航。行动目标可以告诉机器人前往某个位置。当机器人导航到该位置时,它可以沿途发送更新(即反馈),然后在到达目的地后发送最终结果消息。
行动的特点
行动(Action)具有以下几个关键特点:
可中断性
行动可以随时中断。例如,通过Action控制轮子旋转,可以在执行过程中随时停止旋转,即可中断正在进行的行动。
不仅如此,还可以通过新的目标(goal)来覆盖旧的目标,在未完成旧目标时开始执行新目标。例如,如果轮子正以5m/s逐渐减速,但尚未完全停止,可以请求它以1m/s的目标速度旋转,这将导致速度趋向1m/s而不是立即停止。
不过,动作服务器可以选择其他目标,例如拒绝新目标或在第一个目标完成后执行第二个目标。不要假设每个动作服务器在获得新目标时都会选择中止当前目标。长时间运行
行动可持续运行,适用于需要执行时间相对较长的任务。提供过程反馈
行动提供定期的过程反馈,以让客户端了解操作的进展情况。这是与服务通信不同之处,服务通常只提供一次性的响应。
行动可以被视为一种新类型,由话题(Topic)和服务(Service)组成。它可以简化地看作包含两个服务:Goal Service(目标服务)和Result Service(结果服务),以及一个话题(Feedback Topic)。
整体的流程为:
- 动作客户端(Action Client)向动作服务器(Action Server)发出目标请求(Goal Request)
- 动作服务器(Action Server)向动作客户端(Action Client)返还目标反馈(Goal Response)
- 动作客户端(Action Client)向动作服务器(Action Server)请求目标任务的结果(Result Request)
- 动作服务器(Action Server)不断地发送过程反馈的话题(Feedback Topic)给动作客户端(Action Client),直到目标任务达成、中止、或被覆盖
- 目标任务达成后,动作服务器(Action Server)向动作客户端(Action Client)返还结果反馈(Result Response)
行动的命令行使用
查看行动的列表
要查看当前可用的行动列表,可以执行以下命令:
ros2 action list
查看行动的类型和具体的结构
如果您需要查看行动的类型以及其详细结构,可以使用-t选项来同时输出行动的结构类型。执行以下命令:
ros2 action list -t
根据返回的结构类型,进一步查看特定结构类型的详细内容,可以执行以下命令:
ros2 interface show turtlesim/action/RotateAbsolute
这将显示选定行动的具体结构,分为三个部分,使用三个短横线分隔开。这三个部分依次是目标部分 (Goal Request)
,结果部分 (Result Response
),和反馈部分 (Feedback
)。在小乌龟的 “RotateAbsolute” 动作中,它们分别表示目标角度、相对于起始位置的角度,以及剩余旋转角度。
查看行动的具体信息
要查看特定行动的详细信息,可以使用以下命令:
ros2 action info /turtle1/rotate_absolute
此命令将显示与该行动连接的客户端和服务器的信息。
请注意,我们使用“连接”一词,因为客户端和服务器通过行动建立的关系不仅仅是简单的订阅或请求与反馈,而是一种更为复杂的关系。以下是命令的输出示例:
设置行动的目标
要在命令行中发送行动目标,请使用以下命令:
ros2 action send_goal <action_name> <action_type> <values>
以小乌龟为例,发送一个旋转绝对角度为1.57弧度的目标:
ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.57}"
每个Goal都有独立的ID,这是因为行动是可以被中断和覆盖的。
下方的result
字段用于返回执行目标任务的结果,当目标成功执行后会返回相应的结果信息。
当需要在执行过程中显示反馈信息时,可以加入--feedback
选项,如下所示:
ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.57}" --feedback
思考:
如果打断行动,会发生什么?
如果中途取消行动,会发生什么?
行动的编程使用
自定义行动的消息类型接口
1.建立接口功能包
在ROS2中,自定义的接口通常被放在一个独立的功能包下,通常以_interfaces的方式命名。这个功能包包含三个子文件夹,分别用于存放自定义消息、服务和行动接口类型,他们是msg
、srv
、action
。这些细节会在后续课程中详细介绍,这里只是简要提及。
# Go to your src
cd ~/ros2_workspace/src
# Create interfaces package
ros2 pkg create ros2_learning_interfaces
2.建立自定义的动作消息类型文件
自定义行动消息的形式与自定义消息和服务相似,但它由三个部分组成:
- 动作客户端(Client)发送的目标请求(Goal Request)
- 动作服务器(Server)完成请求后的执行结果(Result Response)
- 动作服务器定期向动作客户端发送有关目标执行情况的反馈(Feedback)
# Request
---
# Result
---
# Feedback
在接口功能包的目录下创建一个名为"action"的文件夹,通常采用大写字母开头的命名规则。
# Go to your interfaces package
cd ros2_learning_interfaces
# Create action directory
mkdir action
# Go to action directory
cd action
# Create custom action
touch Fibonacci.action
以斐波那契行动为例,定义了三个字段:order
用于指定目标,sequence
用于存储执行结果,partial_sequence
用于在计算过程中提供反馈信息。
int32 order
---
int32[] sequence
---
int32[] partial_sequence
3.修改CMakeLists.txt
将以下内容添加到CMakeLists.txt文件中:
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"action/Fibonacci.action"
)
相关解释
find_package(rosidl_default_generators REQUIRED)
这一行代码用于查找并导入 ROS 2 中与消息和动作生成相关的默认生成器。这些生成器将帮助你生成用于编译和使用自定义消息和动作的代码。rosidl
全称可能是 "ROS Interface Definition Language"
。它用于定义和生成ROS消息、服务和动作的接口以及相关代码。rosidl
通过一种IDL(接口定义语言)的方式,允许开发者定义消息结构、服务接口和动作接口,然后生成与这些接口相关的代码,以便在ROS 2中进行消息通信、服务调用和动作执行。rosidl_default_generators
是一个用于生成ROS消息、服务和动作接口代码的工具,它在构建过程中会生成必要的代码以便你的软件包可以与其他ROS节点进行通信。
rosidl_generate_interfaces(${PROJECT_NAME} "action/Fibonacci.action")
这一行代码告诉 ROS 2 生成器为你的项目生成与自定义动作类型 "Fibonacci.action"
相关的代码。这包括自动生成的 C++ 源代码和头文件,以及与动作相关的其他文件,这些文件可以帮助你创建、发送和接收动作消息。
4.修改package.xml
添加以下部分到package.xml:
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<depend>action_msgs</depend>
<member_of_group>rosidl_interface_packages</member_of_group>
相关解释
<buildtool_depend>rosidl_default_generators</buildtool_depend>
这行代码指示你的ROS软件包在构建时需要依赖于 rosidl_default_generators
。
<member_of_group>rosidl_interface_packages</member_of_group>
这一行代码将你的软件包添加到 rosidl_interface_packages
组中。这是一种组织ROS软件包的方式,通常包含定义消息、服务和动作接口的软件包。
ROS系统中有许多不同类型的软件包,包括节点、库、工具等。rosidl_interface_packages组
是一种用于标识特定类型软件包的方式,即包含ROS接口(消息、服务和动作)定义的软件包。这有助于将具有相似功能和目的的软件包进行组织,使开发者更容易找到与ROS接口相关的软件包。
<depend>action_msgs</depend>
这一行代码将软件包添加了对 action_msgs
的依赖。这是因为在ROS 2 Humble中,动作(Actions)的定义包含一些额外的元数据,其中包括目标(goal)的标识(ID)等信息。这些元数据与 action_msgs
软件包相关,因此软件包需要依赖于它,以确保在定义和使用动作时有必要的元数据支持。
提示:
值得注意的是,这个缺点在ROS2更新的版本中(例如Iron)被优化,不再需要添加对 action_msgs
的依赖,但在Humble中,这条是需要的,相关问题请参考Iron action。
5.编译工作空间
在最后,编译工作空间,相关命令在之后的课程里会讲解,此处不涉及。
# Go to your workspace
cd ~/ros2_workspace
# Build workspace
colcon build --symlink-install
动作服务器
1.建立动作服务器源文件
在这一步,你需要进入你的ROS 2工作空间中的包目录,并创建一个名为action_demo_server.py
的Python文件。
# Go to your package
cd ros2_workspace/src/ros2_learning/ros2_learning
# Create file
touch action_demo_server.py
2.编写动作服务器
在这一部分,我们创建了一个用于计算斐波那契数列的ROS 2动作服务器。服务器将一直计算,直到斐波那契数列的长度达到用户指定的order
。在执行回调函数中,我们设置反馈并将其发布到客户端,然后使用time.sleep(1)
模拟计算的延迟。最后,我们将目标标记为成功,并返回结果。
import rclpy # 引入rclpy库
import time
from rclpy.action import ActionServer # 引入ActionServer类
from rclpy.node import Node # 引入Node类
from ros2_learning_interfaces.action import Fibonacci
class FibonacciActionServer(Node):
def __init__(self):
super().__init__("action_demo_server")
# 创建一个ActionServer self,消息类型接口名称,动作名,回调函数
self._action_server = ActionServer(
self, Fibonacci, "fibonacci_action", self.execute_callback
)
self.get_logger().info("action_demo_server start") # 表明节点已经启动
def execute_callback(self, goal_handle): # 动作被调用的回调函数
self.get_logger().info("Executing goal...") # 表明开始执行
feedback_msg = Fibonacci.Feedback() # 构建反馈信息
feedback_msg.partial_sequence = [0, 1] # 初始斐波那契数
# order是斐波那契数列的长度
for i in range(
1, goal_handle.request.order - 1
): # 遍历1到goal_handle.request.order-2
# 将下一个斐波那契数填入反馈的列表
feedback_msg.partial_sequence.append(
feedback_msg.partial_sequence[i] + feedback_msg.partial_sequence[i - 1]
)
# 打印过程反馈
self.get_logger().info(
"Feedback: {0}".format(feedback_msg.partial_sequence)
)
# 发布过程反馈
goal_handle.publish_feedback(feedback_msg)
# 休眠
time.sleep(1)
# 直到完成了用户要求的斐波那契长度
# 在目标句柄上指示目标成功
goal_handle.succeed()
result = Fibonacci.Result() # 构建结果反馈
result.sequence = feedback_msg.partial_sequence # 将最后的过程反馈列表值赋给结果反馈
return result # 返还结果
def main(args=None):
rclpy.init(args=args) # 初始化ROS接口
action_demo_server = FibonacciActionServer() # 实例化ActionServer
rclpy.spin(action_demo_server) # 循环
if __name__ == "__main__":
main()
3.修改package.xml
添加以下内容到package.xml,这是因为这个包依赖于之前创建的自定义动作消息类型接口包和rclpy
。
<depend>rclpy</depend>
<depend>ros2_learning_interfaces</depend>
4.修改setup.py
在你的setup.py
文件中添加以下行:
"action_server = ros2_learning.action_demo_server:main",
5.编译工作空间
编译工作空间,相关命令在之后的课程里会讲解,此处不涉及。
# Go to your workspace
cd ~/ros2_workspace
# Build workspace
colcon build --symlink-install
6.测试动作服务器
由于编写行动服务器较为复杂,因此首先进行单个服务器的命令行可行性测试。该动作将计算斐波那契数列,直到列表内的斐波那契数列长度达到用户指定的order:
# Run action server node
ros2 run ros2_learning action_server
# Send action goal
ros2 action send_goal --feedback fibonacci_action ros2_learning_interfaces/action/Fibonacci "{order: 5}"
动作客户端
1.建立动作客户端源文件
在这一步,你需要进入你的ROS 2工作空间中的包目录,并创建一个名为action_demo_client.py
的Python文件。
# Go to your package
cd ros2_workspace/src/ros2_learning/ros2_learning
# Create file
touch action_demo_client.py
2.编写动作客户端
import rclpy # 引入rclpy
from rclpy.action import ActionClient # 引入ActionClient类
from rclpy.node import Node # 引入Node类
from ros2_learning_interfaces.action import Fibonacci # 引入自定义的动作消息类型
class FibonacciActionClient(Node):
def __init__(self):
super().__init__("action_demo_client")
# 实例化ActionClient(self,行动的消息类型,行动名)
self._action_client = ActionClient(self, Fibonacci, "fibonacci_action")
self.get_logger().info("action client start")
def send_goal(self, order): # 发布目标的方法
goal_msg = Fibonacci.Goal() # 构建目标消息
goal_msg.order = order # 填入order值
self.get_logger().info("Waiting for action server...")
self._action_client.wait_for_server() # 等待服务器出现
self.get_logger().info("Action server found!")
# 发送异步目标并将结果存储在_send_goal_future中
self._send_goal_future = self._action_client.send_goal_async(goal_msg)
# 为_send_goal_future添加一个回调函数,当收到ActionServer的goal_response时调用self.goal_response_callback
self._send_goal_future.add_done_callback(self.goal_response_callback)
def goal_response_callback(self, future): # 收到ActionServer的Goal Response时的回调
goal_handle = future.result() # 创建句柄
if not goal_handle.accepted: # 若Goal Request未被接收,打印“拒绝”消息
self.get_logger().info("Goal rejected :(")
return
# 若Goal Request被接收,打印“接收”消息
self.get_logger().info("Goal accepted :)")
# 创建一个异步操作 _get_result_future 用于获取动作服务器的执行结果
self._get_result_future = goal_handle.get_result_async()
# 将 get_result_callback 作为回调函数添加到 _get_result_future,当收到结果时调用self.get_result_callback
self._get_result_future.add_done_callback(self.get_result_callback)
def get_result_callback(self, future):
result = future.result().result # 获取Result
self.get_logger().info("Result: {0}".format(result.sequence)) # 打印Result
rclpy.shutdown() # 关闭ROS
def main(args=None):
goal_value = 10
rclpy.init(args=args) # 初始化ROS
action_client = FibonacciActionClient() # 实例化ActionClient
action_client.send_goal(goal_value) # 发送目标请求
rclpy.spin(action_client)
if __name__ == "__main__":
main()
3.修改package.xml
添加以下内容到package.xml,这是因为这个包依赖于之前创建的自定义动作消息类型接口包和rclpy
。
<depend>rclpy</depend>
<depend>ros2_learning_interfaces</depend>
4.修改setup.py
在你的setup.py
文件中添加以下行:
"action_client = ros2_learning.action_demo_client:main",
5.编译工作空间
编译工作空间,相关命令在之后的课程里会讲解,此处不涉及。
# Go to your workspace
cd ~/ros2_workspace
# Build workspace
colcon build --symlink-install
测试行动通讯
启动行动客户端
ros2 run ros2_learning action_client
行动客户端启动后将等待行动服务器出现,当行动服务器出现后发送行动的目标,并在收到行动服务器返还的Goal Response
后打印目标已被接收的提示信息,直到服务器完成整套动作后,打印最终结果。
启动行动服务器
ros2 run ros2_learning action_server
行动服务器在接收到行动客户端的目标请求后开始服务,直到整套服务完成。