当前位置: 首页 > news >正文

【ROS2学习笔记】动作

前言

本系列博文是本人的学习笔记,自用为主,不是教程,学习请移步其他大佬的相关教程。前几篇学习资源来自鱼香ROS大佬的详细教程,适合深入学习,但对本人这样的初学者不算友好,后续笔记将以@古月居的ROS2入门21讲为主,侵权即删。

一、学习目标

  1. 搞懂 “动作通信” 的核心场景 —— 什么时候必须用动作,而不是话题 / 服务
  2. 掌握动作通信的 “客户端 - 服务器” 模型及底层原理(基于话题 + 服务)
  3. 学会自定义动作接口(.action 文件)的 “定义→编译” 流程
  4. 能独立编写动作 “服务端” 和 “客户端” 代码,理解反馈 / 结果的处理逻辑
  5. 熟练使用动作相关命令行工具,方便调试与验证

二、为什么需要动作通信?(小白先搞懂 “场景”)

之前学过话题(单向高频,比如传图像)和服务(双向低频,比如查位置),但遇到以下场景就不够用了:比如让机器人 “转一圈(360 度)”“导航到客厅”“机械臂抓取物体”—— 这些任务有 3 个特点:

  1. 耗时久:不是一瞬间完成(转一圈可能要 5 秒);
  2. 需进度反馈:想知道 “现在转到 100 度了”“离客厅还有 2 米”;
  3. 可取消:万一遇到障碍,能随时让机器人停止任务。

话题(只有单向数据,没有 “任务结束” 的反馈)和服务(只能等任务结束才给结果,中间没进度)都满足不了这些需求。→ 动作通信就是为 “长时间、需反馈、可取消” 的任务设计的,相当于给任务装了 “进度条 + 暂停按钮”。

三、动作通信的核心概念(类比理解)

3.1 一句话说清动作:带 “进度条” 的服务

动作和服务类似,都是 “客户端发请求,服务器执行”,但多了两个关键功能:

  • 周期反馈:服务器执行过程中,不断给客户端发 “进度”(比如 “当前角度 10 度”);
  • 任务结果:任务结束后,服务器给客户端发 “最终状态”(比如 “转完 360 度,成功”)。

类比生活场景:你(客户端)给外卖员(服务器)发指令 “送一份 pizza 到我家”:

  • 任务中:外卖员每隔 5 分钟发消息(反馈):“已到小区门口”“正在上 3 楼”;
  • 任务结束:外卖员说 “披萨送到了(结果:成功)” 或 “路上洒了(结果:失败)”;
  • 可取消:你中途打电话说 “不用送了”(取消任务)。

3.2 动作通信的核心特性(对比话题 / 服务更清晰)

用表格对比 3 种通信机制,小白一眼看懂区别:

通信机制核心场景数据流向关键优势缺点
话题单向高频数据(如图像、速度)发布者→多个订阅者实时性高,适合传流数据没有 “任务结束” 反馈,无法确认接收方是否处理
服务双向低频请求(如查位置、算加法)客户端→服务器(请求);服务器→客户端(响应)有来有回,适合短任务中间无进度反馈,任务不能取消
动作长时间任务(如转圈、导航)客户端→服务器(目标);服务器→客户端(反馈 + 结果)有进度反馈,支持取消任务比服务复杂,适合长任务不适合短任务

3.3 动作的通信模型:客户端 - 服务器(和服务一样)

动作和服务采用相同的 “客户端 - 服务器” 模型,规则也一样:

  • 客户端:可以有多个(比如你和家人都能给机器人发 “转圈” 指令);
  • 服务器:只能有一个(机器人同一时间只能执行一个任务,比如先转完圈再导航);
  • 数据交互流程
    1. 客户端发送 “任务目标”(比如 “转 360 度”);
    2. 服务器确认 “收到目标”,开始执行任务;
    3. 服务器周期发送反馈(比如 “当前 100 度”“当前 200 度”);
    4. 任务结束后,服务器发送 “最终结果”(比如 “成功转完 360 度”);
    5. (可选)客户端中途发送 “取消指令”,服务器停止任务并返回结果。

3.4 底层原理:动作是 “话题 + 服务” 拼出来的

小白不用深究底层代码,但要知道这个关键知识点 —— 动作不是全新的通信方式,而是用之前学的话题和服务实现的:

  • 用 2 个服务实现:
    1. 客户端发 “任务目标” → 服务器用服务响应 “收到目标”;
    2. 客户端发 “取消指令” → 服务器用服务响应 “已取消”;
  • 用 1 个话题实现:服务器周期发 “进度反馈” → 客户端订阅这个话题接收进度;
  • 最终结果:用服务的响应返回(任务结束时服务器通过服务给结果)。

→ 动作是 “应用层封装”,把 “服务(目标 / 取消)+ 话题(反馈)” 打包成一套简单的接口,小白不用自己写话题和服务的组合逻辑。

四、动作接口的定义(.action 文件)

和话题(.msg)、服务(.srv)一样,动作也需要定义 “数据格式”—— 用.action文件,结构分 3 部分,用---分隔。

4.1 .action 文件结构(3 部分,固定顺序)

动作的核心是 “目标、反馈、结果”,所以.action文件分 3 块:

部分作用对应场景
目标(Goal)客户端发给服务器的 “任务指令”“转 360 度”“导航到 (1,2) 坐标”
结果(Result)服务器任务结束后给的 “最终状态”“转完 360 度(成功)”“导航失败”
反馈(Feedback)服务器执行中给的 “进度”“当前 100 度”“离目标还有 1 米”

格式示例(比如让机器人转圈的MoveCircle.action):

# 第一部分:动作目标(Goal)——客户端告诉服务器“要做什么”
bool enable     # true=开始转圈,false=不执行(相当于任务开关)
---
# 第二部分:动作结果(Result)——服务器告诉客户端“任务做完了怎么样”
bool finish     # true=转圈成功,false=转圈失败
---
# 第三部分:动作反馈(Feedback)——服务器告诉客户端“任务做到哪了”
int32 state     # 当前转圈的角度(比如10、20、30度)

4.2 自定义动作接口的 “定义→编译” 流程

和之前自定义话题 / 服务接口步骤一样,分 3 步:

步骤 1:创建.action 文件
  1. 进入之前的接口功能包learning_interface(没有就新建,参考上一篇笔记);
  2. 在功能包下新建action文件夹,创建MoveCircle.action文件,内容如上;
步骤 2:配置编译依赖(修改 2 个文件)

需要让 ROS 知道 “要编译这个.action 文件”,修改package.xmlCMakeLists.txt

1. 修改 package.xml(声明依赖)

打开learning_interface/package.xml,添加动作编译需要的依赖(如果之前加过话题 / 服务的依赖,只需确认包含这些):

<!-- 编译时依赖:生成动作接口代码的工具 -->
<build_depend>rosidl_default_generators</build_depend>
<!-- 运行时依赖:节点运行时需要的接口库 -->
<exec_depend>rosidl_default_runtime</exec_depend>
<!-- 声明这是接口包,让ROS识别 -->
<member_of_group>rosidl_interface_packages</member_of_group>
2. 修改 CMakeLists.txt(配置编译规则)

打开learning_interface/CMakeLists.txt,添加.action 文件的编译配置:

# 1. 查找生成接口代码的工具包(必须)
find_package(rosidl_default_generators REQUIRED)# 2. 生成动作接口代码:指定功能包名、.action文件路径
rosidl_generate_interfaces(${PROJECT_NAME}"action/MoveCircle.action"  # 你的动作接口文件# 如果有其他接口(.msg/.srv),继续加在这里,比如:# "msg/ObjectPosition.msg"# "srv/GetObjectPosition.srv"
)# 3. 导出接口,让其他功能包(比如动作节点包)能调用
ament_export_dependencies(rosidl_default_runtime)
步骤 3:编译接口包

回到 ROS 工作空间根目录(比如dev_ws),执行编译:

colcon build --packages-select learning_interface

编译后,ROS 会自动把.action文件转换成 Python/C++ 能识别的代码(小白不用管生成的文件,会调用就行)。

五、实战案例:动作通信的代码实现(小白重点)

以 “机器人转圈” 为例,实现:

  • 动作服务端:接收 “转圈” 目标,模拟机器人转圈,每 30 度发一次反馈,结束后返回 “成功” 结果;
  • 动作客户端:发送 “开始转圈” 目标,接收进度反馈和最终结果。

5.1 准备工作:创建动作节点包

先创建一个专门放动作节点的功能包learning_action,依赖动作相关库:

cd dev_ws/src
ros2 pkg create learning_action --build-type ament_python --dependencies rclpy action_msgs learning_interface
  • 依赖说明:rclpy(ROS2 Python 核心库)、action_msgs(动作基础库)、learning_interface(我们自定义的动作接口包)。

5.2 案例 1:动作服务端代码(执行转圈任务,发反馈)

文件名:learning_action/action_move_server.py代码 + 逐行注释

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ROS2动作服务端:执行机器人转圈任务
功能:1. 接收客户端的“转圈”目标 2. 模拟转圈(每0.5秒转30度) 3. 每转30度发一次反馈 4. 转完360度返回成功结果
"""# 1. 导入需要的库
import time                                   # 用于延时(模拟转圈耗时)
import rclpy                                  # ROS2 Python核心库
from rclpy.node import Node                   # ROS2节点类(所有节点必须继承)
from rclpy.action import ActionServer         # ROS2动作服务器类(实现服务端核心功能)
from learning_interface.action import MoveCircle  # 导入自定义的动作接口(MoveCircle.action)# 2. 定义动作服务端节点类(继承Node)
class MoveCircleActionServer(Node):def __init__(self, name):"""构造函数:初始化节点、创建动作服务器"""# 调用父类Node的构造函数,给节点起名字:"action_move_server"super().__init__(name)# 2.1 创建动作服务器:核心对象,负责接收目标、执行任务、发反馈/结果self._action_server = ActionServer(self,                  # 上下文(当前节点)MoveCircle,            # 动作接口类型(自定义的MoveCircle)'move_circle',         # 动作名(客户端必须用这个名字找到服务端)self.execute_callback  # 收到目标后执行的回调函数(核心逻辑在这里))self.get_logger().info("动作服务端已启动!等待客户端发送转圈目标...")def execute_callback(self, goal_handle):"""核心回调函数:收到客户端目标后,执行转圈任务,发反馈和结果"""# goal_handle:目标句柄,用于控制任务(发反馈、标记成功/失败、取消任务)# 1. 打印日志:提示开始执行任务self.get_logger().info('开始执行转圈任务!')# 2. 创建反馈消息对象(对应MoveCircle.action的Feedback部分)feedback_msg = MoveCircle.Feedback()# 3. 模拟转圈过程:从0度到360度,每次转30度(步长30)for current_angle in range(0, 360, 30):# 3.1 设置当前反馈:把当前角度赋值给feedback_msg的state字段feedback_msg.state = current_angle# 3.2 发布反馈:通过goal_handle把反馈发给客户端goal_handle.publish_feedback(feedback_msg)# 3.3 延时0.5秒:模拟转圈耗时(实际机器人这里会控制电机转动)time.sleep(0.5)# (拓展:如果需要支持“取消任务”,这里可以加判断:if goal_handle.is_cancel_requested(): ...)# 4. 任务执行完成:标记任务“成功”goal_handle.succeed()# 5. 创建结果消息对象(对应MoveCircle.action的Result部分)result = MoveCircle.Result()# 设置结果:finish=True表示转圈成功result.finish = True# 6. 返回结果给客户端,结束任务self.get_logger().info('转圈任务完成!已转完360度')return result# 3. 主入口函数:启动节点
def main(args=None):# 3.1 初始化ROS2 Python接口(必须第一步)rclpy.init(args=args)# 3.2 创建动作服务端节点对象node = MoveCircleActionServer("action_move_server")# 3.3 启动节点循环:让节点一直运行,等待处理客户端请求rclpy.spin(node)# 3.4 关闭节点(程序退出时执行,释放资源)node.destroy_node()# 3.5 关闭ROS2 Python接口rclpy.shutdown()

5.3 案例 2:动作客户端代码(发目标,收反馈 / 结果)

文件名:learning_action/action_move_client.py代码 + 逐行注释

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
ROS2动作客户端:请求机器人转圈任务
功能:1. 发送“开始转圈”目标给服务端 2. 接收服务端的周期反馈(当前角度) 3. 接收任务最终结果(是否成功)
"""# 1. 导入需要的库
import rclpy                                  # ROS2 Python核心库
from rclpy.node import Node                   # ROS2节点类
from rclpy.action import ActionClient         # ROS2动作客户端类(实现客户端核心功能)
from learning_interface.action import MoveCircle  # 导入自定义的动作接口# 2. 定义动作客户端节点类(继承Node)
class MoveCircleActionClient(Node):def __init__(self, name):"""构造函数:初始化节点、创建动作客户端"""# 调用父类Node的构造函数,给节点起名字:"action_move_client"super().__init__(name)# 2.1 创建动作客户端:负责发送目标、接收反馈/结果self._action_client = ActionClient(self,                  # 上下文(当前节点)MoveCircle,            # 动作接口类型(和服务端一致)'move_circle'          # 动作名(必须和服务端一致,才能找到服务端))self.get_logger().info("动作客户端已启动!准备发送转圈目标...")def send_goal(self, enable):"""发送动作目标的函数:enable=True表示请求开始转圈"""# 1. 等待服务端上线:如果服务端没启动,客户端会一直等(每隔1秒查一次)self._action_client.wait_for_server()# 2. 创建目标消息对象(对应MoveCircle.action的Goal部分)goal_msg = MoveCircle.Goal()# 设置目标:enable=True(请求开始转圈)goal_msg.enable = enable# 3. 异步发送目标:不会阻塞节点,发完后继续执行其他逻辑# feedback_callback:指定接收反馈的回调函数(收到反馈就执行)self._send_goal_future = self._action_client.send_goal_async(goal_msg,feedback_callback=self.feedback_callback)# 4. 设置“目标响应”的回调函数:服务端收到目标后,会调用这个函数self._send_goal_future.add_done_callback(self.goal_response_callback)def goal_response_callback(self, future):"""回调函数1:服务端收到目标后的响应处理"""# future:包含服务端的响应结果goal_handle = future.result()# 判断服务端是否接受目标if not goal_handle.accepted:self.get_logger().info("目标被服务端拒绝!可能服务端正忙...")return# 目标被接受,打印日志self.get_logger().info("目标被服务端接受!开始等待反馈和结果...")# 异步获取最终结果:任务结束后,服务端会返回结果,这里设置回调函数处理结果self._get_result_future = goal_handle.get_result_async()self._get_result_future.add_done_callback(self.get_result_callback)def get_result_callback(self, future):"""回调函数2:接收任务最终结果的处理"""# 读取服务端返回的结果result = future.result().result# 判断结果:如果finish=True,表示转圈成功if result.finish:self.get_logger().info(f"任务成功!结果:{result.finish}(已转完360度)")else:self.get_logger().info(f"任务失败!结果:{result.finish}")def feedback_callback(self, feedback_msg):"""回调函数3:接收周期反馈的处理(服务端每发一次反馈,就执行一次)"""# 读取反馈消息中的“当前角度”(feedback_msg.feedback对应Feedback部分)feedback = feedback_msg.feedbackself.get_logger().info(f"收到反馈:当前转圈角度 = {feedback.state} 度")# 3. 主入口函数:启动客户端,发送目标
def main(args=None):# 3.1 初始化ROS2 Python接口rclpy.init(args=args)# 3.2 创建动作客户端节点对象node = MoveCircleActionClient("action_move_client")# 3.3 发送目标:enable=True(请求开始转圈)node.send_goal(True)# 3.4 启动节点循环:等待接收反馈和结果rclpy.spin(node)# 3.5 关闭节点和ROS2接口node.destroy_node()rclpy.shutdown()

5.4 配置节点入口(让 ROS 能找到程序)

打开learning_action/setup.py,在entry_pointsconsole_scripts里添加客户端和服务端的入口(告诉 ROS“运行某个命令时,执行哪个文件的 main 函数”):

entry_points={'console_scripts': [# 格式:"命令名 = 包名.文件名:main函数"'action_move_server = learning_action.action_move_server:main','action_move_client = learning_action.action_move_client:main',],
},

5.5 运行步骤(3 个终端)

终端 1:编译功能包
cd dev_ws  # 进入工作空间
colcon build --packages-select learning_action  # 只编译动作节点包
source install/setup.bash  # 加载环境变量(每次编译后都要执行)
终端 2:启动动作服务端
cd dev_ws
source install/setup.bash
ros2 run learning_action action_move_server  # 执行服务端命令
# 预期输出:[INFO] [action_move_server]:动作服务端已启动!等待客户端发送转圈目标...
终端 3:启动动作客户端
cd dev_ws
source install/setup.bash
ros2 run learning_action action_move_client  # 执行客户端命令
# 预期输出:
# 1. 客户端发送目标,服务端接受;
# 2. 客户端每隔0.5秒收到反馈:“收到反馈:当前转圈角度 = 30 度”“收到反馈:当前转圈角度 = 60 度”...;
# 3. 转完360度后,客户端打印:“任务成功!结果:True(已转完360度)”

六、小海龟动作实战(验证动作通信)

除了自定义动作,ROS 自带的小海龟也支持动作,小白可以用这个案例快速验证动作的效果。

6.1 步骤 1:启动小海龟节点

打开 2 个终端,分别启动小海龟仿真器和键盘控制(可选,用于对比):

# 终端1:启动小海龟仿真器
ros2 run turtlesim turtlesim_node
# 终端2:启动键盘控制(可选,不用也能测试动作)
ros2 run turtlesim turtle_teleop_key

6.2 步骤 2:查看小海龟的动作列表

打开新终端,执行命令查看小海龟支持的动作:

ros2 action list
# 预期输出:/turtle1/rotate_absolute (小海龟的“绝对旋转”动作)

6.3 步骤 3:查看动作的接口定义

想知道这个动作需要什么 “目标”“反馈”“结果”,用ros2 action show命令:

ros2 action show turtlesim/action/RotateAbsolute
# 预期输出(动作接口定义):
# # Goal(目标:要旋转到的角度,单位:弧度)
# float32 theta
# ---
# # Result(结果:实际旋转到的角度)
# float32 delta
# ---
# # Feedback(反馈:当前旋转到的角度)
# float32 theta
  • 说明:theta是角度(弧度制,比如 - 1.57 弧度≈-90 度,即向左转 90 度)。

6.4 步骤 4:发送动作目标(让小海龟旋转)

命令格式:
ros2 action send_goal <动作名> <动作类型> <目标数据> [--feedback]
# --feedback:可选参数,加上后会显示实时反馈
实际命令(让小海龟向左转 90 度,即 theta=-1.57 弧度):
ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: -1.57}" --feedback
预期输出:
  1. 小海龟在仿真窗口中向左转 90 度;
  2. 终端输出反馈和结果:
    Sending goal:
    theta: -1.57Goal accepted with ID: a63b3f40-0f0d-4a3a-8c0a-78e7f1b8f5a0Feedback:
    theta: -0.785398163394928Feedback:
    theta: -1.570796326789856Result:
    delta: -1.570796326789856Goal finished with status: SUCCEEDED
    

七、动作通信命令行工具(调试必备)

整理常用命令,小白记下来,调试时直接用:

命令语法功能说明示例(小海龟案例)
ros2 action list查看系统中所有正在运行的动作ros2 action list → 输出/turtle1/rotate_absolute
ros2 action info <动作名>查看动作的详细信息(类型、客户端 / 服务器数量)ros2 action info /turtle1/rotate_absolute → 显示 “Action: /turtle1/rotate_absolute, Type: turtlesim/action/RotateAbsolute”
ros2 action show <动作类型>查看动作接口的定义(Goal/Result/Feedback)ros2 action show turtlesim/action/RotateAbsolute → 显示目标 / 结果 / 反馈的字段
ros2 action send_goal <动作名> <动作类型> <目标数据> [--feedback]发送动作目标,可选看反馈ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: -1.57}" --feedback

八、复习要点总结(小白必背)

  1. 动作的核心场景:长时间、需进度反馈、可取消的任务(转圈、导航、抓取);
  2. 动作的 3 个关键部分
    • Goal(目标):客户端告诉服务器 “做什么”;
    • Feedback(反馈):服务器告诉客户端 “做到哪了”;
    • Result(结果):服务器告诉客户端 “做完了怎么样”;
  3. 底层原理:动作是基于 “2 个服务(目标 / 取消)+1 个话题(反馈)” 实现的,是应用层封装;
  4. 代码核心逻辑
    • 服务端:创建ActionServer → 实现execute_callback(发反馈、返回结果);
    • 客户端:创建ActionClient → 调用send_goal_async(发目标)→ 实现 3 个回调(目标响应、反馈、结果);
  5. 命令行工具list查动作、info查详情、show查接口、send_goal发目标。

通过 “自定义转圈动作” 和 “小海龟旋转动作” 两个案例,小白能掌握动作通信的核心流程,后续遇到 “导航”“机械臂控制” 等场景,只需替换动作接口和执行逻辑即可

http://www.dtcms.com/a/442475.html

相关文章:

  • 李宏毅machine learning 2021学习笔记——Vit
  • 第三十一篇|AI 驱动的教育数据建模:以冈山外语学院为样本的结构化分析
  • 深圳网站的优化公司哪家好wordpress上传代码
  • 架构学习之旅-架构的由来
  • 洛阳市建设工程评标专家网站网站开发长沙
  • 专业的数字化转型培训方案哪家好
  • 第68篇:AI+零售:智能推荐、无人商店与需求预测
  • 常用网站字体分销平台软件哪个好
  • 近世代数(抽象代数)详细笔记--环
  • CTFHub 信息泄露通关笔记9:Git泄露 Index
  • 网址导航网站一键建设猎奇网站源码
  • ROOT的Android手机抓包安装系统跟证书
  • 网站免费正能量软件不良宣讲家网站官德修养与作风建设
  • 深入浅出 ArkTS:HarmonyOS 应用开发的语言基石
  • 网站建设在哪些石景山网站制作建设公司
  • 面试经历分享:从特斯拉到联影医疗的历程
  • 优化网站要怎么做枣庄网站建设 网站设计 网站制作
  • 开源 C++ QT QML 开发(三)常用控件
  • 网站404页面源码自己买台服务器做网站
  • 解码Huffman 编码与 Huffman 树
  • bypass--绕Waf
  • 企业门户网站开发背景seo自学网视频教程
  • 【龙泽科技】智能网联汽车视觉传感器仿真教学软件
  • Glup 和 Vite
  • 做网站图片的大小会计上网站建设做什么费用
  • 公司网站费怎么做分录绥德网站建设设计
  • Google开源Tunix:JAX生态的LLM微调方案来了
  • YOLO入门教程(番外):机器视觉实践—Kaggle实战:深度学习实现狗的品种识别
  • Redis和MySQL的数据同步
  • 织梦网站转移服务器厦门网站建设网络推广