本文是关于 ROS 行为(Action)机制的学习笔记,结构清晰、重点突出,便于理解:
🧠 理解行为(Action)
- 问题背景:ROS 中的服务(Service)适用于短任务,但对于耗时任务(如机器人从 A 点移动到 B 点),会导致线程阻塞。
- 解决方案 :ROS 提供了 行为(Action) 机制,专用于处理耗时长的异步任务。
📁 行为的组织(Action 文件)
- 行为由 .action 文件定义,存放在功能包的
action/目录下。 - 文件内容由三部分组成,之间用
---分隔:- goal:任务目标
- result:任务执行结果(仅发送一次)
- feedback:任务执行的周期反馈数据
示例:
# goal
int32 total
---
# result
int32 sum
---
# feedback
🔁 行为的核心概念
| 概念 | 说明 |
|---|---|
| goal | 客户端发送的任务目标 |
| cancel | 客户端请求取消任务 |
| status | 通知客户端当前状态 |
| feedback | 服务端周期反馈任务进度 |
| result | 服务端发送最终执行结果(仅一次) |
⚙️ 行为的实现机制
- 行为本质上基于 消息的发布/订阅 机制。
- ActionServer 内部实现包含:
- 订阅:
goal_sub_,cancel_sub_ - 发布:
status_pub_,result_pub_,feedback_pub_ - 定时器:
status_timer_
- 订阅:
关键发布代码示例:
cpp
result_pub_ = node_.advertise<ActionResult>("result", queue_size);
feedback_pub_ = node_.advertise<ActionFeedback>("feedback", queue_size);
🔁 行为的状态机流转
Receive Goal → PENDING → ACTIVE → [RECALLING / PREEMPTED] → TERMINAL STATE
- Client Triggered:客户端触发状态变化
- Server Triggered:服务端触发状态变化
- Terminal State:最终状态(如成功、取消、中止)
🧩 行为的核心 API(客户端与服务端)
✅ 客户端常用方法(SimpleActionClient)
cpp
void sendGoal(const Goal &goal,
SimpleDoneCallback done_cb,
SimpleActiveCallback active_cb,
SimpleFeedbackCallback feedback_cb);
bool waitForServer(const ros::Duration &timeout);
✅ 服务端常用方法
cpp
void publishFeedback(const Feedback &feedback);
bool isPreemptRequested();
void setSucceeded(const Result &result, const std::string &text);
🛠️ 自定义行为步骤
- 创建
.action文件 - 编写服务端代码
- 编写客户端代码
- 配置
package.xml和CMakeLists.txt - 生成行为头文件
- 编译并执行
依赖包:
actionlibactionlib_msgs
💻 示例代码分析(来自图片)
客户端核心代码(client.cpp)
cpp
actionlib::SimpleActionClient<example_5::RunningAction> ac("running", true);
ac.waitForServer();
example_5::RunningGoal goal;
goal.total = 20;
ac.sendGoal(goal, &doneCb, &activeCb, &feedbackCb);
bool finished = ac.waitForResult(ros::Duration(50.0));
回调函数示例
cpp
void feedbackCb(const example_5::RunningFeedbackConstPtr& feedback){
ROS_INFO("running number is: %d", feedback->number);
}
void doneCb(const actionlib::SimpleClientGoalState& state,
const example_5::RunningResultConstPtr& result){
ROS_INFO("Finished: %s, sum = %d", state.toString().c_str(), result->sum);
}
输出示例
Executing, creating 5 of total 20
running number is: 6
...
the running are now finished! the sum is 20
running: Succeeded
✅ 总结
- 行为(Action) 是 ROS 中处理长时间异步任务的标准机制。
- 它通过 goal/result/feedback 三部分实现任务目标、结果和进度的通信。
- 内部基于 发布/订阅 模型,支持状态管理和任务取消。
- 使用
.action文件定义接口,配合actionlib库实现客户端和服务端。
根据你提供的图片内容,我将继续整理关于 ROS 行为(Action)的实践部分,包括服务端实现、包配置、编译执行等内容,形成一份完整的笔记。
🛠️ 行为服务端实现(service.cpp)
服务端负责接收目标、执行任务、发布反馈和最终结果。
✅ 关键代码解析
1. 包含头文件
cpp
#include <ros/ros.h>
#include <actionlib/server/simple_action_server.h>
#include "example_5/RunningAction.h"
2. 定义 Action 类
cpp
class RunningAction
{
protected:
ros::NodeHandle nh_;
actionlib::SimpleActionServer<example_5::RunningAction> as_;
std::string action_name_;
example_5::RunningFeedback feedback_;
example_5::RunningResult result_;
public:
RunningAction(std::string name) :
as_(nh_, name, boost::bind(&RunningAction::executeCB, this, _1), false),
action_name_(name)
{
as_.start();
}
~RunningAction(void) {}
3. 执行回调函数 executeCB
cpp
void executeCB(const example_5::RunningGoalConstPtr &goal)
{
ros::Rate r(1);
bool success = true;
for(int i = 1; i <= goal->total; i++)
{
if (as_.isPreemptRequested() || !ros::ok())
{
as_.setPreempted();
success = false;
break;
}
feedback_.number = i;
as_.publishFeedback(feedback_);
r.sleep();
}
if(success)
{
result_.sum = goal->total; // 实际可能是累加值,示例中直接用total
ROS_INFO("%s: Succeeded", action_name_.c_str());
as_.setSucceeded(result_);
}
}
};
4. main 函数
cpp
int main(int argc, char** argv)
{
ros::init(argc, argv, "running_server");
RunningAction running("running");
ros::spin();
return 0;
}
🔄 执行流程
- 服务端启动,等待客户端发送 goal。
- 一旦收到 goal,进入
executeCB循环执行任务。 - 每秒发布一次 feedback(当前计数)。
- 完成后发布 result,状态设为
SUCCEEDED。
📦 包配置(package.xml)
在自定义行为包中,需要声明依赖项。
xml
<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>actionlib_msgs</build_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>rospy</build_export_depend>
<build_export_depend>actionlib_msgs</build_export_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>message_generation</exec_depend>
⚙️ CMakeLists.txt 配置
1. 添加 action 文件
cmake
add_action_files(
FILES
Running.action
)
2. 生成消息(包括 action 生成的中间消息)
cmake
generate_messages(
DEPENDENCIES
std_msgs
actionlib_msgs
)
3. 指定可执行文件及链接库
cmake
add_executable(action_service src/service.cpp)
add_executable(action_client src/client.cpp)
target_link_libraries(action_service
${catkin_LIBRARIES}
)
target_link_libraries(action_client
${catkin_LIBRARIES}
)
4. 添加依赖(确保生成消息后再编译)
cmake
add_dependencies(action_service ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(action_client ${PROJECT_NAME}_generate_messages_cpp)
🧪 编译与执行
编译
bash
cd ~/catkin_ws
catkin build
运行
启动服务端:
bash
rosrun example_5 action_service
启动客户端(另一终端):
bash
rosrun example_5 action_client
输出示例(服务端)
[INFO] [1616655438.390392518]: Executing, creating 5 of total 20
[INFO] [1616655439.391079154]: running number is: 6
[INFO] [1616655440.39101389]: running number is: 7
...
[INFO] [1616655454.391006046]: the running are now finished! the sum is 20
[INFO] [1616655454.391480481]: running: Succeeded
输出示例(客户端)
[INFO] [1616655438.390392518]: Executing, creating 5 of total 20
[INFO] [1616655439.391079154]: running number is: 6
...
[INFO] [1616655454.391006046]: the running are now finished! the sum is 20
[INFO] [1616655454.391480481]: Action finished: SUCCEEDED
✅ 总结
- 服务端 通过
SimpleActionServer接收 goal,在回调中执行任务,并发布 feedback 和 result。 - 客户端 通过
SimpleActionClient发送 goal,注册回调函数接收 feedback 和 result。 - 配置 需要在
package.xml和CMakeLists.txt中正确声明依赖和生成 action 消息。 - 执行 时先启动服务端,再启动客户端,可实现长时间任务的异步通信。
自定义 Action 的全部代码
以下是功能包 example_5 的完整目录结构和所有代码文件内容,基于你提供的图片和 ROS Action 标准实践整理而成。
📁 目录结构
~/catkin_ws/src/example_5/
├── action
│ └── Running.action
├── include
│ └── example_5 # 生成的头文件会放在 devel 空间,此处仅为占位
├── src
│ ├── client.cpp
│ └── service.cpp
├── CMakeLists.txt
└── package.xml
以及,
bash
mkdir -p ~/catkin_ws/src/example_5/{action,include/example_5,src} && touch ~/catkin_ws/src/example_5/{action/Running.action,src/{client.cpp,service.cpp},CMakeLists.txt,package.xml}
📄 文件内容
1. action/Running.action
txt
# goal
int32 total
---
# result
int32 sum
---
# feedback
int32 number
2. src/client.cpp
cpp
#include <ros/ros.h>
#include <actionlib/client/simple_action_client.h>
#include <actionlib/client/terminal_state.h>
#include "example_5/RunningAction.h"
// 完成回调
void doneCb(const actionlib::SimpleClientGoalState& state,
const example_5::RunningResultConstPtr& result)
{
ROS_INFO("The running are now finished! the sum is %d", result->sum);
}
// 激活回调
void activeCb()
{
ROS_INFO("Goal just went active");
}
// 反馈回调
void feedbackCb(const example_5::RunningFeedbackConstPtr& feedback)
{
ROS_INFO("running number is: %d", feedback->number);
}
int main(int argc, char** argv)
{
ros::init(argc, argv, "running_client");
// 创建动作客户端,连接到名为 "running" 的服务端
actionlib::SimpleActionClient<example_5::RunningAction> actionClient("running", true);
ROS_INFO("Waiting for action server to start.");
actionClient.waitForServer(); // 无限等待
ROS_INFO("Action server started, sending goal.");
example_5::RunningGoal goal;
goal.total = 20;
// 发送目标,并绑定回调
actionClient.sendGoal(goal, &doneCb, &activeCb, &feedbackCb);
// 等待结果,超时时间 50 秒
bool finished_before_timeout = actionClient.waitForResult(ros::Duration(50.0));
if (finished_before_timeout)
{
actionlib::SimpleClientGoalState state = actionClient.getState();
ROS_INFO("Action finished: %s", state.toString().c_str());
}
else
{
ROS_INFO("Action did not finish before the time out.");
}
return 0;
}
3. src/service.cpp
cpp
#include <ros/ros.h>
#include <actionlib/server/simple_action_server.h>
#include "example_5/RunningAction.h"
class RunningAction
{
protected:
ros::NodeHandle nh_;
actionlib::SimpleActionServer<example_5::RunningAction> as_;
std::string action_name_;
example_5::RunningFeedback feedback_;
example_5::RunningResult result_;
public:
RunningAction(std::string name) :
as_(nh_, name, boost::bind(&RunningAction::executeCB, this, _1), false),
action_name_(name)
{
as_.start();
}
~RunningAction(void) {}
void executeCB(const example_5::RunningGoalConstPtr &goal)
{
ros::Rate r(1); // 1 Hz
bool success = true;
// 从 1 累加到 goal->total
for (int i = 1; i <= goal->total; i++)
{
// 检查是否被客户端取消或 ROS 关闭
if (as_.isPreemptRequested() || !ros::ok())
{
ROS_INFO("%s: Preempted", action_name_.c_str());
as_.setPreempted();
success = false;
break;
}
// 发布反馈(当前累加值)
feedback_.number = i;
as_.publishFeedback(feedback_);
r.sleep();
}
if (success)
{
result_.sum = goal->total; // 此处简单用 total 作为结果,实际可改为累加和
ROS_INFO("%s: Succeeded", action_name_.c_str());
as_.setSucceeded(result_);
}
}
};
int main(int argc, char** argv)
{
ros::init(argc, argv, "running_server");
RunningAction running("running");
ros::spin();
return 0;
}
4. CMakeLists.txt
cmake
cmake_minimum_required(VERSION 3.0.2)
project(example_5)
## 编译选项
add_compile_options(-std=c++11)
## 寻找依赖包
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
actionlib_msgs
actionlib
message_generation
)
## 添加 action 文件
add_action_files(
FILES
Running.action
)
## 生成消息(action 会自动生成对应的消息)
generate_messages(
DEPENDENCIES
std_msgs
actionlib_msgs
)
## catkin 包配置
catkin_package(
INCLUDE_DIRS include
LIBRARIES ${PROJECT_NAME}
CATKIN_DEPENDS roscpp rospy std_msgs actionlib_msgs
DEPENDS system_lib
)
## 包含目录
include_directories(
include
${catkin_INCLUDE_DIRS}
)
## 可执行文件
add_executable(action_service src/service.cpp)
add_executable(action_client src/client.cpp)
## 链接库
target_link_libraries(action_service
${catkin_LIBRARIES}
)
target_link_libraries(action_client
${catkin_LIBRARIES}
)
## 添加依赖(确保消息头先生成)
add_dependencies(action_service ${PROJECT_NAME}_generate_messages_cpp)
add_dependencies(action_client ${PROJECT_NAME}_generate_messages_cpp)
5. package.xml
xml
<?xml version="1.0"?>
<package format="2">
<name>example_5</name>
<version>0.0.0</version>
<description>The example_5 package</description>
<maintainer email="user@todo.todo">user</maintainer>
<license>TODO</license>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>roscpp</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>actionlib_msgs</build_depend>
<build_depend>actionlib</build_depend>
<build_depend>message_generation</build_depend>
<build_export_depend>roscpp</build_export_depend>
<build_export_depend>rospy</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<build_export_depend>actionlib_msgs</build_export_depend>
<exec_depend>roscpp</exec_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<exec_depend>actionlib_msgs</exec_depend>
<exec_depend>actionlib</exec_depend>
<exec_depend>message_runtime</exec_depend>
<export>
</export>
</package>
✅ 编译与运行
-
将以上文件放入
~/catkin_ws/src/example_5/目录。 -
在工作空间根目录运行:
bashcatkin build -
启动服务端:
bashrosrun example_5 action_service -
启动客户端(新终端):
bashrosrun example_5 action_client
你将看到客户端和服务端通过 feedback 和 result 进行通信,最终输出累加结果。
以上即为 example_5 功能包的完整实现。