@TOC
下面通过代码演示自定义 Action 的基本实现。
一、创建功能包
cd ros2_learning/src
ros2 pkg create --build-type ament_cmake action_learning_cpp
其中,
使用 --build-type 指定编译系统为 ament_cmake
action_learning_cpp:自定义功能包名称
生成的目录结构如下:
action_learning_cpp
├── CMakeLists.txt
├── include
│ └── action_learning_cpp
├── LICENSE
├── package.xml
└── src
二、Action服务端
我们编写一个服务端、客户端,实现计算斐波那契数列。
在 action_learning_cpp/src 目录下新增 action_server_base.cpp 文件,文件内容如下:
/**
* @file action_server_basic.cpp
* @brief Action Server 基础 —— 斐波那契数列服务端
*
* 知识点:
* - rclcpp_action::create_server() 创建 Action Server
* - handle_goal:决定接受/拒绝目标
* - handle_cancel:决定允许/拒绝取消
* - handle_accepted:启动执行线程
* - execute:执行逻辑 + 发布 Feedback + 返回 Result
*
* 功能:
* 客户端指定 order(项数),服务端逐步计算斐波那契数列,
* 每计算出一项就通过 Feedback 发布当前已生成的部分数列,
* 最终通过 Result 返回完整的斐波那契数列。
*
* 运行:
* ros2 run action_learning_cpp action_server_basic
* ros2 action send_goal /fibonacci action_learning_cpp/action/Fibonacci "{order: 10}"
*/
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <action_learning_cpp/action/fibonacci.hpp>
#include <thread>
using Fibonacci = action_learning_cpp::action::Fibonacci;
class FibonacciServer : public rclcpp::Node
{
public:
FibonacciServer() : Node("fibonacci_server")
{
action_server_ = rclcpp_action::create_server<Fibonacci>(
this,
"fibonacci",
std::bind(&FibonacciServer::handle_goal, this,
std::placeholders::_1, std::placeholders::_2),
std::bind(&FibonacciServer::handle_cancel, this,
std::placeholders::_1),
std::bind(&FibonacciServer::handle_accepted, this,
std::placeholders::_1));
RCLCPP_INFO(this->get_logger(), "=== Fibonacci Action Server started ===");
RCLCPP_INFO(thresult_callbackis->get_logger(), "Action name: /fibonacci");
RCLCPP_INFO(this->get_logger(), "Waiting for client to send goal...");
}
private:
rclcpp_action::GoalResponse handle_goal(
const rclcpp_action::GoalUUID &uuid,
std::shared_ptr<const Fibonacci::Goal> goal)
{
(void)uuid;
RCLCPP_INFO(this->get_logger(), "Received goal request with order: %ld", goal->order);
// ── 目标合法性校验 ──
// 斐波那契数列至少需要 1 项
if (goal->order <= 0)
{
RCLCPP_WARN(this->get_logger(), "Goal rejected: order must be > 0");
return rclcpp_action::GoalResponse::REJECT;
}
RCLCPP_INFO(this->get_logger(), "Goal accepted with order: %ld", goal->order);
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
rclcpp_action::CancelResponse handle_cancel(
const std::shared_ptr<rclcpp_action::ServerGoalHandle<Fibonacci>> goal_handle)
{
RCLCPP_INFO(this->get_logger(), "Cancel request received, cancel accepted");
(void)goal_handle;
return rclcpp_action::CancelResponse::ACCEPT;
}
void handle_accepted(
const std::shared_ptr<rclcpp_action::ServerGoalHandle<Fibonacci>> goal_handle)
{
// 在新线程中执行,避免阻塞 executor
std::thread{
std::bind(&FibonacciServer::execute, this, std::placeholders::_1),
goal_handle}
.detach();
}
void execute(
const std::shared_ptr<rclcpp_action::ServerGoalHandle<Fibonacci>> goal_handle)
{
RCLCPP_INFO(this->get_logger(), "Executing goal...");
// 从 GoalHandle 获取目标
auto goal = goal_handle->get_goal();
// 创建 Result 和 Feedback 对象
auto result = std::make_shared<Fibonacci::Result>();
auto feedback = std::make_shared<Fibonacci::Feedback>();
rclcpp::Rate loop_rate(1); // 1Hz — 每秒计算一项
// 初始化斐波那契数列的前两项
int64_t a = 0, b = 1;
for (int64_t i = 0; i < goal->order; ++i)
{
// ── 检查是否被取消 ──
if (goal_handle->is_canceling())
{
// 返回已计算出的部分数列
result->sequence = feedback->current_sequence;
goal_handle->canceled(result);
RCLCPP_INFO(this->get_logger(),
"Goal canceled, computed %ld/%ld terms",
i, goal->order);
return;
}
// ── 计算当前项 ──
int64_t current_value;
if (i == 0)
{
current_value = 0;
}
else if (i == 1)
{
current_value = 1;
}
else
{
current_value = a + b;
a = b;
b = current_value;
}
// ── 更新并发布 Feedback(当前已生成的部分数列)──
feedback->current_sequence.push_back(current_value);
goal_handle->publish_feedback(feedback);
RCLCPP_INFO(this->get_logger(),
"Progress: %ld/%ld, current term: %ld",
i + 1, goal->order, current_value);
// 保存到结果序列
result->sequence.push_back(current_value);
loop_rate.sleep();
}
// ── 执行成功 ──
goal_handle->succeed(result);
RCLCPP_INFO(this->get_logger(),
"Goal completed! Fibonacci sequence has %ld terms", result->sequence.size());
}
rclcpp_action::Server<Fibonacci>::SharedPtr action_server_;
};
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<FibonacciServer>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
其中,line:34 创建了一个action服务端,并绑定回调函数,handle_goal 处理客户端的 send_goal 请求;handle_cancel 处理客户端的 async_cancel_goal 请求;handle_accepted 在服务端通过 handle_goal 返回 ACCEPT 后同步被回调,用于处理 goal 的任务:
action_server_ = rclcpp_action::create_server<Fibonacci>(
this,
"fibonacci",
std::bind(&FibonacciServer::handle_goal, this,
std::placeholders::_1, std::placeholders::_2),
std::bind(&FibonacciServer::handle_cancel, this,
std::placeholders::_1),
std::bind(&FibonacciServer::handle_accepted, this,
std::placeholders::_1));
handle_goal 中提供的参数 uuid 是目标请求会话的唯一标识符,可用于日志追踪。
rclcpp_action::GoalResponse handle_goal(
const rclcpp_action::GoalUUID &uuid,
std::shared_ptr<const Fibonacci::Goal> goal)
另外,需要注意的是,handle_accepted 应快速返回,避免阻塞执行器。一般处理是在这里创建一个新线程处理goal的任务:
void handle_accepted(const std::shared_ptr<rclcpp_action::ServerGoalHandle<Fibonacci>> goal_handle)
{
std::thread{
std::bind(&FibonacciServer::execute, this, std::placeholders::_1),
goal_handle
}.detach();
}
三、Action客户端
在 action_learning_cpp/src 目录下新增 action_server_base.cpp 文件,文件内容如下:
/**
* @file action_client_basic.cpp
* @brief Action Client 基础 —— 斐波那契数列客户端
*
* 知识点:
* - rclcpp_action::create_client() 创建 Action Client
* - wait_for_action_server() 等待服务端上线
* - async_send_goal() 发送目标
* - SendGoalOptions 的三个回调:
* goal_response_callback —— 服务端接受/拒绝目标的回调
* feedback_callback —— 收到反馈的回调
* result_callback —— 收到最终结果的回调
*
* 运行(先启动 action_server_basic):
* ros2 run action_learning_cpp action_client_basic
*/
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <action_learning_cpp/action/fibonacci.hpp>
#include <chrono>
#include <sstream>
#include <memory>
using Fibonacci = action_learning_cpp::action::Fibonacci;
using namespace std::chrono_literals;
class FibonacciClient : public rclcpp::Node
{
public:
FibonacciClient() : Node("fibonacci_client")
{
client_ = rclcpp_action::create_client<Fibonacci>(this, "fibonacci");
RCLCPP_INFO(this->get_logger(), "=== Fibonacci Action Client created ===");
// 等待服务端上线
if (!client_->wait_for_action_server(10s))
{
RCLCPP_ERROR(this->get_logger(),
"Action Server /fibonacci not available, exiting");
return;
}
RCLCPP_INFO(this->get_logger(), "Action Server is available, sending goal...");
// 发送目标:计算前 10 项斐波那契数列
send_goal(10);
}
private:
// 将数列格式转为字符串,方便日志输出
std::string format_sequence(const std::vector<int64_t> &seq)
{
std::ostringstream oss;
oss << "[";
for (size_t i = 0; i < seq.size(); ++i)
{
if (i > 0)
oss << ", ";
oss << seq[i];
}
oss << "]";
return oss.str();
}
// ── 成员函数回调1: goal_response_callback ──
// 服务端接受或拒绝目标时触发
void goal_response_callback(const rclcpp_action::ClientGoalHandle<Fibonacci>::SharedPtr &goal_handle)
{
if (!goal_handle)
{
RCLCPP_ERROR(this->get_logger(), "Goal rejected!");
}
else
{
RCLCPP_INFO(this->get_logger(),
"Goal accepted, waiting for execution...");
}
}
// ── 成员函数回调2: feedback_callback ──
// 服务端发布反馈时触发
void feedback_callback(
rclcpp_action::ClientGoalHandle<Fibonacci>::SharedPtr,
const std::shared_ptr<const Fibonacci::Feedback> feedback)
{
RCLCPP_INFO(this->get_logger(),
"Feedback received: %s",
format_sequence(feedback->current_sequence).c_str());
}
// ── 成员函数回调3: result_callback ──
// 目标执行完成时触发(成功/失败/取消)
void result_callback(const rclcpp_action::ClientGoalHandle<Fibonacci>::WrappedResult &result)
{
switch (result.code)
{
case rclcpp_action::ResultCode::SUCCEEDED:
RCLCPP_INFO(this->get_logger(),
"Goal succeeded! Fibonacci sequence: %s",
format_sequence(result.result->sequence).c_str());
break;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(this->get_logger(), "Goal aborted!");
break;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_WARN(this->get_logger(),
"Goal canceled, partial sequence: %s",
format_sequence(result.result->sequence).c_str());
break;
default:
RCLCPP_ERROR(this->get_logger(), "Unknown result code");
break;
}
}
void send_goal(int64_t order)
{
// ── 创建目标消息 ──
auto goal_msg = Fibonacci::Goal();
goal_msg.order = order;
// 配置 SendGoalOptions —— 三个核心回调
auto send_goal_options = rclcpp_action::Client<Fibonacci>::SendGoalOptions();
// ── 绑定成员函数作为回调 ──
// 方法1: 使用 std::bind
send_goal_options.goal_response_callback =
std::bind(&FibonacciClient::goal_response_callback, this, std::placeholders::_1);
send_goal_options.feedback_callback =
std::bind(&FibonacciClient::feedback_callback, this, std::placeholders::_1, std::placeholders::_2);
// 方法2: 使用 lambda 包装成员函数(更现代的风格)
send_goal_options.result_callback =
[this](const auto &result)
{
this->result_callback(result);
};
// ── 发送目标 ──
RCLCPP_INFO(this->get_logger(), "Sending goal, order: %ld", order);
client_->async_send_goal(goal_msg, send_goal_options);
}
rclcpp_action::Client<Fibonacci>::SharedPtr client_;
};
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<FibonacciClient>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
其中,line:33 创建了一个action客户端,入参为节点指针和Action名称,返回Action客户端指针,用于后续操作。
client_ = rclcpp_action::create_client<Fibonacci>(this, "fibonacci");
line:54 将发送目标封装为一个成员函数,函数中包括创建目标对象、配置目标操作、发送目标等基本流程:
// 1.创建目标消息
auto goal_msg = Fibonacci::Goal();
goal_msg.order = order;
// 2.配置 SendGoalOptions —— 三个核心回调
auto send_goal_options = rclcpp_action::Client<Fibonacci>::SendGoalOptions();
send_goal_options.goal_response_callback = ...
send_goal_options.feedback_callback = ...
send_goal_options.result_callback = ...
// 3.发送目标
client_->async_send_goal(goal_msg, send_goal_options);
其中,注册回调函数展示了两种方法,第一种使用 std::bind 传统的标准C++风格,第二种使用 lambda 包装成员函数,更现代的C++风格。
// 方法1:使用 std::bind (传统的标准C++风格)
send_goal_options.goal_response_callback =
std::bind(&FibonacciClient::goal_response_callback, this, std::placeholders::_1);
send_goal_options.feedback_callback =
std::bind(&FibonacciClient::feedback_callback, this, std::placeholders::_1, std::placeholders::_2);
// 方法2: 使用 lambda 包装成员函数(更现代的C++风格)
send_goal_options.result_callback =
[this](const auto &result)
{
this->result_callback(result);
};
四、编辑编译配置文件CMakeList.txt
在 action_learning_cpp/src 目录下的 CMakeList.txt 文件中,新增如下内容:
# 添加action依赖
find_package(rclcpp REQUIRED)
find_package(rclcpp_action REQUIRED)
find_package(std_msgs REQUIRED)
# 生成自定义 Action 消息
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"action/Fibonacci.action"
)
# 获取生成的消息类型支持目标
rosidl_get_typesupport_target(cpp_typesupport_target ${PROJECT_NAME} rosidl_typesupport_cpp)
# 辅助宏:创建可执行目标
macro(add_action_executable name)
add_executable(${name} src/${name}.cpp)
ament_target_dependencies(${name} rclcpp rclcpp_action rclcpp_lifecycle lifecycle_msgs std_msgs geometry_msgs)
target_link_libraries(${name} "${cpp_typesupport_target}")
install(TARGETS ${name} DESTINATION lib/${PROJECT_NAME})
endmacro()
# 添加节点
add_action_executable(action_server_basic)
add_action_executable(action_client_basic)
五、编译运行
进入到工作空间目录,执行如下指令编译该工程:
colcon build
编译成功后,先设置环境,再依次启动服务端与客户端节点。
source install/setup.bash
ros2 run action_learning_cpp action_server_basic
ros2 run action_learning_cpp action_client_basic
结果如下: