ROS 行为(Action)机制

本文是关于 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);

🛠️ 自定义行为步骤

  1. 创建 .action 文件
  2. 编写服务端代码
  3. 编写客户端代码
  4. 配置 package.xmlCMakeLists.txt
  5. 生成行为头文件
  6. 编译并执行

依赖包:

  • actionlib
  • actionlib_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.xmlCMakeLists.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>

✅ 编译与运行

  1. 将以上文件放入 ~/catkin_ws/src/example_5/ 目录。

  2. 在工作空间根目录运行:

    bash 复制代码
    catkin build
  3. 启动服务端:

    bash 复制代码
    rosrun example_5 action_service
  4. 启动客户端(新终端):

    bash 复制代码
    rosrun example_5 action_client

你将看到客户端和服务端通过 feedback 和 result 进行通信,最终输出累加结果。


以上即为 example_5 功能包的完整实现。

相关推荐
炽烈小老头2 小时前
【 每天学习一点算法 2026/03/11】从前序与中序遍历序列构造二叉树
学习·算法
飞Link2 小时前
概率图模型的基石:隐马可夫模型 (HMM) 深度解析
开发语言·python·算法
_日拱一卒2 小时前
LeetCode(力扣):验证回文串
算法·leetcode·职场和发展
Eward-an2 小时前
LeetCode 128. 最长连续序列(O(n)时间复杂度详解)
数据结构·算法·leetcode
Frostnova丶2 小时前
LeetCode 1009 & 476 数字的补数
算法·leetcode
CppBlock2 小时前
HPX vs TBB vs OpenMP:并行任务模型对比
c++·算法
17(无规则自律)2 小时前
Leetcode第六题:用 C++ 解决三数之和
c++·算法·leetcode
进击的小头2 小时前
第4篇:二阶系统的时域响应分析
python·算法