前言

上一篇我们详细拆解了ROS话题通信(Topic),它主打异步流式数据传输,适配传感器实时数据推送;而服务通信(Service)主打同步问答,适配单次简单指令交互。

但在机器人实际开发中,有大量长时间执行、需要实时进度反馈、支持中途取消的任务,比如:机器人导航移动、机械臂轨迹运动、定点巡航、持续运算任务等。这类场景,话题和服务通信都无法适配,此时就需要用到ROS的高级通信机制——动作通信(Action)

很多新手对Action认知模糊,只会套用代码不懂底层逻辑。本文从零吃透ROS动作通信,通俗讲解原理、拆解通信流程、提供可直接运行的Python/C++实战代码,同时对比三大通信机制,帮你精准匹配业务场景。


一、什么是ROS动作通信?

1.1 核心定义

动作通信(Action)是ROS专为长时任务、可中断、带进度反馈设计的异步客户端/服务端通信机制,融合了话题通信和服务通信的核心优势,是机器人运动控制、导航任务的核心通信方式。

通俗类比:

  • 话题通信:广播播报,单向持续推送,不管对方是否接收

  • 服务通信:打电话问答,一问一答,阻塞等待结果,无法中断、无进度反馈

  • 动作通信:带进度条、可随时取消的任务委托,下发任务后实时回传进度,任务结束返回最终结果,支持中途终止任务

1.2 四大核心特性

  • 异步执行:客户端下发任务后无需阻塞等待,可执行其他逻辑

  • 实时进度反馈:任务执行过程中,服务端持续推送进度数据

  • 支持任务取消:客户端可随时终止正在执行的长时任务

  • 结果闭环:任务完成/失败/取消后,返回明确最终状态

1.3 核心角色与数据结构

动作通信的完整交互依赖两大角色、三类核心数据:

1)核心角色
  • Action Client(动作客户端):任务发起方,负责发送目标任务、接收进度反馈与最终结果、发起取消请求

  • Action Server(动作服务端):任务执行方,负责接收任务、执行业务逻辑、实时推送进度、返回任务结果、响应取消请求

2)三类核心数据(.action文件定义)
  • Goal(目标):客户端下发的任务指令(如移动目标坐标、运动步数)

  • Feedback(反馈):任务执行中的实时进度数据(如完成百分比、当前位置)

  • Result(结果):任务结束后的最终状态数据(如执行成功/失败、最终数据)

底层原理:动作通信并非独立协议,本质是ROS话题+服务的封装组合,底层通过多个固定话题完成目标下发、反馈推送、结果返回、取消指令交互。


二、动作通信完整工作流程

动作通信交互逻辑比话题、服务通信更细致,完整流程分为任务下发、进度反馈、任务收尾、异常取消四大阶段,全程闭环可控。

2.1 正常执行流程

  1. 节点初始化注册:启动Action服务端与客户端,向ROS Master注册节点与动作服务信息

  2. 客户端下发Goal:客户端发送目标任务,服务端接收任务并确认受理

  3. 服务端循环反馈:服务端后台执行任务,周期性推送进度反馈给客户端

  4. 任务完成返回Result:任务执行完毕,服务端停止反馈,推送最终执行结果与状态

  5. 通信结束:客户端接收结果,单次动作任务闭环完成

2.2 异常取消流程

长时任务执行中,若需要终止任务(如机器人避障、紧急暂停):

  1. 客户端主动发送取消请求

  2. 服务端接收请求,终止当前任务逻辑

  3. 推送任务取消状态与终止时进度,结束本次任务


三、ROS三大通信机制终极对比

彻底分清Topic/Service/Action的适配场景,开发不再选错通信方式,也是ROS面试高频考点。

通信方式

通信模式

是否异步

进度反馈

任务取消

适用场景

话题通信Topic

发布/订阅、单向流式

不支持

传感器数据、实时状态广播、高频数据流

服务通信Service

请求/响应、同步问答

否(阻塞)

不支持

单次短耗时指令、参数查询、设备开关

动作通信Action

客户端/服务端、异步任务

实时持续反馈

支持

长时可控任务、导航移动、机械臂运动、轨迹执行


四、动作通信实战代码(Python+C++)

采用ROS官方经典斐波那契数列任务案例,实现完整动作通信:客户端下发生成数列长度,服务端实时反馈生成进度,完成后返回最终数列,代码适配Melodic/Noetic版本,可直接运行。

4.1 环境与依赖准备

  • 依赖功能包:actionlibactionlib_msgsstd_msgs

  • 核心文件:自定义Fibonacci.action动作文件

4.2 自定义动作文件(Fibonacci.action)

在功能包action目录下创建Fibonacci.action,定义目标、反馈、结果数据:


# 目标Goal:客户端指定数列长度 int32 order --- # 结果Result:最终生成的斐波那契数列 int32[] sequence --- # 反馈Feedback:实时生成的中间数列 int32[] partial_sequence

4.3 Python完整实现

1. 动作服务端(fib_server.py)

#!/usr/bin/env python3 # -*- coding: utf-8 -*- import rospy import actionlib from action_tutorials.msg import FibonacciAction, FibonacciFeedback, FibonacciResult # 全局反馈对象 feedback = FibonacciFeedback() result = FibonacciResult() def execute_cb(goal): # 任务执行回调函数 rospy.loginfo("开始执行任务,目标数列长度:%d", goal.order) success = True # 初始化斐波那契数列 feedback.partial_sequence = [0, 1] # 循环生成数列,模拟长时任务 for i in range(1, goal.order): # 任务取消判断 if server.is_preempt_requested(): rospy.loginfo("任务被主动取消!") success = False break # 生成新数列项 next_val = feedback.partial_sequence[i] + feedback.partial_sequence[i-1] feedback.partial_sequence.append(next_val) # 发布实时进度反馈 server.publish_feedback(feedback) rospy.loginfo("当前进度数列:%s", str(feedback.partial_sequence)) rospy.sleep(0.5) # 任务收尾 if success: result.sequence = feedback.partial_sequence server.set_succeeded(result, "任务执行成功!") else: server.set_preempted(result, "任务被取消!") if __name__ == "__main__": rospy.init_node("fib_action_server") # 创建动作服务端:动作名称、回调函数、自动启动 server = actionlib.SimpleActionServer("fibonacci", FibonacciAction, execute_cb, auto_start=False) server.start() rospy.loginfo("斐波那契动作服务端已启动,等待任务请求...") rospy.spin()

2. 动作客户端(fib_client.py)

#!/usr/bin/env python3 # -*- coding: utf-8 -*- import rospy import actionlib from action_tutorials.msg import FibonacciAction, FibonacciGoal def done_cb(state, result): # 任务完成回调 rospy.loginfo("任务最终结果:%s", str(result.sequence)) rospy.loginfo("任务状态码:%d", state) def feedback_cb(feedback): # 实时进度回调 rospy.loginfo("实时进度更新:%s", str(feedback.partial_sequence)) if __name__ == "__main__": rospy.init_node("fib_action_client") # 创建动作客户端 client = actionlib.SimpleActionClient("fibonacci", FibonacciAction) rospy.loginfo("等待连接动作服务端...") client.wait_for_server() rospy.loginfo("服务端连接成功,下发任务!") # 创建任务目标 goal = FibonacciGoal() goal.order = 8 # 生成8位斐波那契数列 # 下发任务,绑定回调函数 client.send_goal(goal, done_cb=done_cb, feedback_cb=feedback_cb) # 阻塞等待任务结束(可选,异步可注释) client.wait_for_result() rospy.loginfo("任务全部完成!")

4.4 C++完整实现

1. 动作服务端(fib_server.cpp)

#include <ros/ros.h> #include <actionlib/server/simple_action_server.h> #include <action_tutorials/FibonacciAction.h> typedef actionlib::SimpleActionServer<action_tutorials::FibonacciAction> Server; void executeCB(const action_tutorials::FibonacciGoalConstPtr &goal, Server* as) { ros::Rate r(2); action_tutorials::FibonacciFeedback feedback; action_tutorials::FibonacciResult result; ROS_INFO("开始执行任务,目标长度:%d", goal->order); feedback.partial_sequence.push_back(0); feedback.partial_sequence.push_back(1); bool success = true; for(int i = 1; i < goal->order; i++) { // 响应取消请求 if(as->isPreemptRequested() || !ros::ok()) { ROS_INFO("任务被取消!"); success = false; break; } // 生成数列 int next = feedback.partial_sequence[i] + feedback.partial_sequence[i-1]; feedback.partial_sequence.push_back(next); as->publishFeedback(feedback); r.sleep(); } // 任务收尾 if(success) { result.sequence = feedback.partial_sequence; as->setSucceeded(result, "任务执行成功"); } else { as->setPreempted(result, "任务被取消"); } } int main(int argc, char** argv) { ros::init(argc, argv, "fib_action_server"); ros::NodeHandle n; Server server(n, "fibonacci", boost::bind(&executeCB, _1, &server), false); server.start(); ROS_INFO("动作服务端启动成功!"); ros::spin(); return 0; }

2. 动作客户端(fib_client.cpp)

#include <ros/ros.h> #include <actionlib/client/simple_action_client.h> #include <action_tutorials/FibonacciAction.h> typedef actionlib::SimpleActionClient<action_tutorials::FibonacciAction> Client; int main(int argc, char** argv) { ros::init(argc, argv, "fib_action_client"); Client client("fibonacci", true); ROS_INFO("等待连接服务端..."); client.waitForServer(); ROS_INFO("连接成功,下发任务!"); // 下发目标任务 action_tutorials::FibonacciGoal goal; goal.order = 8; client.sendGoal(goal); // 等待任务结束 client.waitForResult(); // 输出结果 if(client.getState() == actionlib::SimpleClientGoalState::SUCCEEDED) ROS_INFO("任务执行成功!"); else ROS_INFO("任务执行失败或被取消!"); return 0; }

4.5 CMakeLists.txt核心配置

动作通信必须配置动作文件编译、依赖关联,核心配置如下:


find_package(catkin REQUIRED COMPONENTS roscpp rospy actionlib actionlib_msgs std_msgs ) # 声明动作文件 add_action_files( FILES Fibonacci.action ) # 生成动作消息 generate_messages( DEPENDENCIES actionlib_msgs std_msgs ) catkin_package( CATKIN_DEPENDS roscpp rospy actionlib actionlib_msgs std_msgs )

4.6 完整运行步骤

  1. 编译工作空间:catkin_make && source devel/setup.bash

  2. 启动ROS核心:roscore

  3. 启动动作服务端:rosrun 包名 fib_server.py

  4. 启动动作客户端:rosrun 包名 fib_client.py

  5. (可选)中途取消任务:可在客户端代码中添加 client.cancel_goal() 实现任务终止


五、动作通信调试指令(开发必备)

快速排查动作通信异常、定位连接问题:

  • rosaction list:查看当前所有活跃的动作服务

  • rosaction info /动作名:查看动作服务详情、消息结构

  • rosmsg show 自定义动作消息:查看Goal/Feedback/Result字段结构

  • rostopic list:可查看动作底层封装的所有通信话题


六、动作通信优缺点与适用场景

6.1 核心优点

  • 长时任务适配:异步执行不阻塞主线程,完美适配耗时任务

  • 全程可控:实时进度反馈+随时取消任务,容错性极强

  • 闭环完整:有目标、有进度、有结果、有状态,业务逻辑清晰

  • 高拓展性:底层基于话题封装,稳定性高,适配复杂机器人业务

6.2 局限性

  • 使用复杂:需要自定义action文件、配置编译、编写多回调函数,上手成本高于话题和服务

  • 资源开销略高:多话题交互、状态维护,轻量短时任务不推荐使用

6.3 典型适用场景

✅ 推荐使用动作通信:

  • 机器人导航定点移动、路径轨迹执行

  • 机械臂、舵机连续轨迹运动控制

  • 机器人巡航、巡检、持续作业任务

  • 需要进度展示、紧急暂停、中途取消的所有长时任务

❌ 不推荐:高频传感器数据传输、单次瞬时简单指令查询


七、常见问题总结(避坑指南)

  1. 动作服务端启动后客户端连接失败:大概率是action文件未编译、CMake配置缺失,重新编译并source环境变量即可

  2. 无进度反馈:未调用publish_feedback发布反馈数据,或回调函数未正常触发

  3. 任务无法取消:未在循环中添加is_preempt_requested取消判断逻辑

  4. 编译报错找不到动作消息:功能包依赖未添加actionlibactionlib_msgs


总结

ROS三大通信机制分工明确:Topic管流式数据、Service管短时问答、Action管长时可控任务。动作通信作为ROS进阶核心,是机器人运动控制、导航开发的必备技能,看似复杂,本质是标准化封装的“带进度、可中断的高级服务”。

掌握本文的原理逻辑+实战代码,即可应对绝大多数机器人长时任务开发,彻底告别通信机制选型混乱的问题。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐