【ROS2】Intermediate - rosdep 使用 / acition创建

一、使用rosdep管理依赖

目录

  • 什么是rosdep?
  • 关于package.xml文件的说明
    • <depend>
    • <build_depend>
    • <build_export_depend>
    • <exec_depend>
    • <test_depend>
  • rosdep的工作原理
  • 如何确定在package.xml中填写哪些键(key)?
  • 如果我的库不在rosdistro中该怎么办?
  • 如何使用rosdep工具?
    • rosdep安装
    • rosdep操作

目标:使用rosdep管理外部依赖。
教程级别:中级
预计时间:5分钟

本教程将介绍如何使用rosdep管理外部依赖。

警告:目前rosdep仅支持Linux和macOS系统,不支持Windows。长期计划为rosdep项目仓库添加Windows支持。

什么是rosdep?

rosdep是一款可处理功能包和外部库依赖的依赖管理工具,它是一款命令行工具,用于识别并安装构建或安装某个功能包所需的依赖。rosdep本身并非包管理器,而是一款“元包管理器”——它会利用自身对系统和依赖的认知,在特定平台上找到合适的待安装包,实际安装操作则通过系统包管理器(例如Debian/Ubuntu系统的apt、Fedora/RHEL系统的dnf等)完成。

rosdep最常见的用法是在构建工作空间前调用,用于安装工作空间内所有功能包的依赖。它既可以处理单个功能包,也可以处理包含多个功能包的目录(如工作空间)。

注意:尽管名称中带有“ROS”,但rosdep对ROS并非强依赖。你可以将其作为独立的Python包安装,在非ROS软件项目中使用这款强大的工具。rosdep的正常运行依赖于“rosdep键(rosdep keys)”的可用性,通过几条简单命令即可从公共Git仓库下载这些键。

关于package.xml文件的说明

package.xmlrosdep查找依赖集的配置文件。确保package.xml中的依赖列表完整且正确至关重要——这能让所有工具准确识别功能包的依赖。依赖缺失或错误可能导致用户无法使用你的功能包、工作空间内功能包构建顺序混乱,甚至功能包无法发布。

package.xml中的依赖通常被称为“rosdep键”,这些依赖由功能包创建者手动填写到package.xml中,应包含功能包所需的所有非内置库和功能包。

这些依赖通过以下标签表示(完整规范请参考REP-149):

<depend>

此类依赖是功能包在构建阶段和运行阶段都需要的依赖。对于C++功能包,若不确定依赖类型,建议使用此标签。纯Python功能包通常没有构建阶段,因此不应使用此标签,而应使用<exec_depend>

<build_depend>

若某个依赖仅在构建功能包时使用,而在运行阶段不需要,则可使用<build_depend>标签。

对于此类依赖,已安装的功能包二进制文件无需依赖该包即可运行。但需注意:如果你的功能包导出的头文件中包含了该依赖的头文件,那么还需要添加<build_export_depend>标签。

<build_export_depend>

若你的功能包导出的头文件中包含了某依赖的头文件,那么依赖你的功能包(通过<build_depend>声明依赖)的其他功能包也会需要该依赖。此标签主要适用于头文件和CMake配置文件。对于你导出的库所引用的库功能包,通常应使用<depend>标签——因为这些库在运行阶段同样需要。

<exec_depend>

此标签用于声明功能包在运行阶段所需的依赖,包括共享库、可执行文件、Python模块、启动脚本及其他运行时必需的文件。

<test_depend>

此标签用于声明仅测试阶段需要的依赖。此处的依赖不应与<build_depend><exec_depend><depend>标签中声明的键重复。

rosdep的工作原理

rosdep会在指定路径或特定功能包中查找package.xml文件,提取其中存储的rosdep键。随后,这些键会与一个中央索引进行交叉比对,在各类包管理器中找到对应的ROS功能包或软件库。最终,找到的包会被安装并可立即使用!

rosdep的工作机制是先将中央索引下载到本地机器,避免每次运行时都访问网络(在Debian/Ubuntu系统中,其配置存储在/etc/ros/rosdep/sources.list.d/20-default.list)。

这个中央索引被称为rosdistro,可在线访问。下一节将详细介绍。

如何确定在package.xml中填写哪些键(key)?

这是个很好的问题!

情况1:依赖的是ROS功能包

如果你需要依赖的功能包是基于ROS的,并且已发布到ROS生态系统中[1](例如nav2_bt_navigator),那么直接使用该功能包的名称作为键即可。你可以在rosdistro项目仓库中,找到对应ROS发行版的<distro>/distribution.yaml文件(例如humble/distribution.yaml),其中列出了所有已发布的ROS功能包。

情况2:依赖的是非ROS包(系统依赖)

若需依赖非ROS包(通常称为“系统依赖”),则需要找到对应库的rosdep键。通常可关注以下两个文件:

  • rosdep/base.yaml:包含apt系统依赖
  • rosdep/python.yaml:包含Python依赖

查找键的方法是:在上述文件中搜索目标库,找到对应的名称,该名称即为需填写到package.xml中的键。

例如,假设某个功能包依赖doxygen(一款优秀的文档生成工具),在rosdep/base.yaml中搜索doxygen会找到以下内容:

doxygen:
  arch: [doxygen]
  debian: [doxygen]
  fedora: [doxygen]
  freebsd: [doxygen]
  gentoo: [app-doc/doxygen]
  macports: [doxygen]
  nixos: [doxygen]
  openembedded: [doxygen@meta-oe]
  opensuse: [doxygen]
  rhel: [doxygen]
  ubuntu: [doxygen]

这表明对应的rosdep键是doxygen,该键会在不同操作系统的包管理器中解析为对应的包名,从而完成安装。

如果我的库不在rosdistro中该怎么办?

如果你的库不在rosdistro中,不妨体验开源软件开发的优势——你可以自行添加!rosdistro的拉取请求(Pull Request)通常会在一周内合并。

关于如何贡献新的rosdep键,可参考此处的详细说明。若因特殊原因无法公开贡献,也可fork rosdistro仓库,维护一个自定义索引供自己使用。

如何使用rosdep工具?

rosdep安装

方式1:随ROS发行版安装(推荐)

若在ROS环境中使用rosdep,它已随ROS发行版打包,可直接安装:

apt-get install python3-rosdep

注意:在Debian和Ubuntu系统中,存在一个名为python3-rosdep2的同名包。若已安装该包,需先卸载,再安装python3-rosdep

方式2:独立安装(非ROS环境)

若在非ROS环境中使用rosdep,系统包管理器可能未提供该包,此时可从PyPI直接安装:

pip install rosdep

rosdep操作

了解了rosdeppackage.xmlrosdistro的相关知识后,就可以使用rosdep工具了!

步骤1:初始化与更新(首次使用)

若为首次使用rosdep,需先执行初始化:

sudo rosdep init
rosdep update

rosdep init用于初始化rosdep,rosdep update用于更新本地缓存的rosdistro索引。建议定期执行rosdep update,以获取最新的索引。

步骤2:安装依赖

最终可通过rosdep install命令安装依赖。通常在工作空间根目录执行该命令,一次性安装工作空间内所有功能包的依赖(假设源代码存储在src目录中):

rosdep install --from-paths src -y --ignore-src

对命令参数的说明:

  • --from-paths src:指定查找package.xml文件的路径(此处为src),用于解析rosdep键。
  • -y:对包管理器的所有提示默认选择“yes”,无需手动确认即可安装。
  • --ignore-src:若某个依赖包本身也在当前工作空间中,即使存在对应的rosdep键,也忽略该依赖的安装。

rosdep还提供了更多参数和选项,可通过rosdep -h查看,或参考rosdep完整文档

[1] “发布到ROS生态系统”指该功能包在rosdistro数据库的<distro>/distribution.yaml目录中至少有一个条目。

二、创建动作(Action)

目标:在ROS 2功能包中定义一个动作(Action)。

教程级别:中级
预计时间:5分钟

目录

  • 背景
  • 前提条件
  • 任务
    1. 创建接口功能包
    2. 定义动作
    3. 构建动作
  • 总结
  • 下一步

背景

在之前的“理解动作(Understanding actions)”教程中,你已经了解了动作的相关知识。与其他通信类型及其对应的接口(话题/msg、服务/srv)类似,你也可以在自己的功能包中自定义动作。本教程将展示如何定义并构建一个动作,该动作可用于你将在下一教程中编写的动作服务器(action server)和动作客户端(action client)。

前提条件

  • 已安装ROS 2和colcon。
  • 已掌握工作空间搭建和功能包创建方法。
  • 记得先配置ROS 2环境。

任务

1. 创建接口功能包

根据不同操作系统,执行以下命令(可复用已有的同名工作空间):

Linux
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create --license Apache-2.0 custom_action_interfaces
macOS
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create --license Apache-2.0 custom_action_interfaces
Windows
md \ros2_ws\src
cd \ros2_ws\src
ros2 pkg create --license Apache-2.0 custom_action_interfaces

2. 定义动作

动作在.action文件中定义,文件结构如下:

# 请求(Request)
---
# 结果(Result)
---
# 反馈(Feedback)

一个动作定义由三个消息定义组成,各消息定义之间用---分隔:

  • 请求消息(Request):由动作客户端发送给动作服务器,用于发起一个新目标(goal)。
  • 结果消息(Result):由动作服务器发送给动作客户端,在目标完成时返回。
  • 反馈消息(Feedback):由动作服务器定期发送给动作客户端,提供目标执行进度的更新。

通常将一个动作实例称为“目标(goal)”。

假设我们要定义一个用于计算斐波那契数列的新动作“Fibonacci”,步骤如下:

  1. 进入custom_action_interfaces功能包目录,创建action目录:

    Linux/macOS
    cd custom_action_interfaces
    mkdir action
    
    Windows
    cd custom_action_interfaces
    md action
    
  2. action目录下创建Fibonacci.action文件,添加以下内容:

    int32 order
    ---
    int32[] sequence
    ---
    int32[] partial_sequence
    
    • 目标请求(Request):order(需计算的斐波那契数列的阶数)。
    • 结果(Result):sequence(最终计算出的斐波那契数列)。
    • 反馈(Feedback):partial_sequence(当前已计算出的部分斐波那契数列)。

3. 构建动作

要在代码中使用新的Fibonacci动作类型,需将其定义传入rosidl代码生成流水线,步骤如下:

  1. 编辑CMakeLists.txt:在ament_package()行之前添加以下代码:

    find_package(rosidl_default_generators REQUIRED)
    
    rosidl_generate_interfaces(${PROJECT_NAME}
      "action/Fibonacci.action"
    )
    
  2. 编辑package.xml:添加所需依赖:

    <buildtool_depend>rosidl_default_generators</buildtool_depend>
    <member_of_group>rosidl_interface_packages</member_of_group>
    
  3. 构建包含Fibonacci动作定义的功能包:

    cd ~/ros2_ws  # 进入工作空间根目录
    colcon build   # 执行构建
    
  4. 验证动作构建结果:

    • 首先配置工作空间环境:
      Linux
      source install/local_setup.bash
      
      macOS
      source install/local_setup.console
      
      Windows
      call install\local_setup.bat
      
    • 检查动作定义是否存在:
      ros2 interface show custom_action_interfaces/action/Fibonacci
      
      若屏幕上打印出Fibonacci动作的定义,说明构建成功。

总结

在本教程中,你学习了动作定义的结构,掌握了如何通过CMakeLists.txtpackage.xml正确构建新的动作接口,以及如何验证构建是否成功。

下一步

接下来,让我们利用你新定义的动作接口,创建动作服务器和客户端(可选择Python或C++实现)。

三、编写动作服务器与客户端(C++)

目标:使用C++实现动作服务器(Action Server)和动作客户端(Action Client)。

教程级别:中级
预计时间:15分钟

目录

  • 背景
  • 前提条件
  • 任务
    1. 创建custom_action_cpp功能包
    2. 编写动作服务器
    3. 编写动作客户端
  • 总结

背景

动作(Action)是ROS中的一种异步通信方式。动作客户端向动作服务器发送目标请求,动作服务器则向动作客户端发送目标反馈和结果。

前提条件

  • 已完成上一教程《创建动作》,并拥有custom_action_interfaces功能包及其中定义的Fibonacci.action接口。

任务

1. 创建custom_action_cpp功能包

正如在“创建功能包”教程中所学,我们需要创建一个新功能包来存放C++代码及相关支持文件。

1.1 创建custom_action_cpp功能包

进入上一教程创建的动作工作空间(记得配置工作空间环境),为C++动作服务器创建新功能包:

Linux

cd ~/ros2_ws/src
ros2 pkg create --dependencies custom_action_interfaces rclcpp rclcpp_action rclcpp_components --license Apache-2.0 -- custom_action_cpp

macOS

cd ~/ros2_ws/src
ros2 pkg create --dependencies custom_action_interfaces rclcpp rclcpp_action rclcpp_components --license Apache-2.0 -- custom_action_cpp

Windows

cd \ros2_ws\src
ros2 pkg create --dependencies custom_action_interfaces rclcpp rclcpp_action rclcpp_components --license Apache-2.0 -- custom_action_cpp
1.2 添加可见性控制

为确保功能包能在Windows系统上编译并正常工作,需添加“可见性控制”。更多细节可参考《Windows技巧与窍门》文档中的“Windows符号可见性”部分。

打开custom_action_cpp/include/custom_action_cpp/visibility_control.h文件,添加以下代码:

#ifndef CUSTOM_ACTION_CPP__VISIBILITY_CONTROL_H_
#define CUSTOM_ACTION_CPP__VISIBILITY_CONTROL_H_

#ifdef __cplusplus
extern "C"
{
#endif

// 此逻辑借鉴自gcc维基示例(并添加了命名空间):
//     https://gcc.gnu.org/wiki/Visibility

#if defined _WIN32 || defined __CYGWIN__
  #ifdef __GNUC__
    #define CUSTOM_ACTION_CPP_EXPORT __attribute__ ((dllexport))
    #define CUSTOM_ACTION_CPP_IMPORT __attribute__ ((dllimport))
  #else
    #define CUSTOM_ACTION_CPP_EXPORT __declspec(dllexport)
    #define CUSTOM_ACTION_CPP_IMPORT __declspec(dllimport)
  #endif
  #ifdef CUSTOM_ACTION_CPP_BUILDING_DLL
    #define CUSTOM_ACTION_CPP_PUBLIC CUSTOM_ACTION_CPP_EXPORT
  #else
    #define CUSTOM_ACTION_CPP_PUBLIC CUSTOM_ACTION_CPP_IMPORT
  #endif
  #define CUSTOM_ACTION_CPP_PUBLIC_TYPE CUSTOM_ACTION_CPP_PUBLIC
  #define CUSTOM_ACTION_CPP_LOCAL
#else
  #define CUSTOM_ACTION_CPP_EXPORT __attribute__ ((visibility("default")))
  #define CUSTOM_ACTION_CPP_IMPORT
  #if __GNUC__ >= 4
    #define CUSTOM_ACTION_CPP_PUBLIC __attribute__ ((visibility("default")))
    #define CUSTOM_ACTION_CPP_LOCAL  __attribute__ ((visibility("hidden")))
  #else
    #define CUSTOM_ACTION_CPP_PUBLIC
    #define CUSTOM_ACTION_CPP_LOCAL
  #endif
  #define CUSTOM_ACTION_CPP_PUBLIC_TYPE
#endif

#ifdef __cplusplus
}
#endif

#endif  // CUSTOM_ACTION_CPP__VISIBILITY_CONTROL_H_

2. 编写动作服务器

接下来编写一个动作服务器,它将使用上一教程中创建的动作接口来计算斐波那契数列。

2.1 编写动作服务器代码

打开custom_action_cpp/src/fibonacci_action_server.cpp文件,添加以下代码:

#include <functional>
#include <memory>
#include <thread>

#include "custom_action_interfaces/action/fibonacci.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "rclcpp_components/register_node_macro.hpp"

#include "custom_action_cpp/visibility_control.h"

namespace custom_action_cpp
{
class FibonacciActionServer : public rclcpp::Node
{
public:
  using Fibonacci = custom_action_interfaces::action::Fibonacci;
  using GoalHandleFibonacci = rclcpp_action::ServerGoalHandle<Fibonacci>;

  CUSTOM_ACTION_CPP_PUBLIC
  explicit FibonacciActionServer(const rclcpp::NodeOptions & options = rclcpp::NodeOptions())
  : Node("fibonacci_action_server", options)
  {
    using namespace std::placeholders;

    // 处理目标请求的回调函数
    auto handle_goal = [this](
      const rclcpp_action::GoalUUID & uuid,
      std::shared_ptr<const Fibonacci::Goal> goal)
    {
      RCLCPP_INFO(this->get_logger(), "Received goal request with order %d", goal->order);
      (void)uuid;  // 未使用的参数,避免编译警告
      return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;  // 接受并执行目标
    };

    // 处理取消请求的回调函数
    auto handle_cancel = [this](
      const std::shared_ptr<GoalHandleFibonacci> goal_handle)
    {
      RCLCPP_INFO(this->get_logger(), "Received request to cancel goal");
      (void)goal_handle;  // 未使用的参数,避免编译警告
      return rclcpp_action::CancelResponse::ACCEPT;  // 接受取消请求
    };

    // 处理目标接受的回调函数
    auto handle_accepted = [this](
      const std::shared_ptr<GoalHandleFibonacci> goal_handle)
    {
      // 此回调需快速返回,避免阻塞执行器,因此在新线程中调用执行函数
      auto execute_in_thread = [this, goal_handle](){return this->execute(goal_handle);};
      std::thread{execute_in_thread}.detach();
    };

    // 创建动作服务器
    this->action_server_ = rclcpp_action::create_server<Fibonacci>(
      this,                  // 节点实例
      "fibonacci",           // 动作名称
      handle_goal,           // 目标处理回调
      handle_cancel,         // 取消处理回调
      handle_accepted        // 目标接受回调
    );
  }

private:
  rclcpp_action::Server<Fibonacci>::SharedPtr action_server_;  // 动作服务器指针

  // 执行目标的核心函数(在独立线程中运行)
  void execute(const std::shared_ptr<GoalHandleFibonacci> goal_handle) {
    RCLCPP_INFO(this->get_logger(), "Executing goal");
    rclcpp::Rate loop_rate(1);  // 1Hz循环频率(每秒处理一次)
    const auto goal = goal_handle->get_goal();  // 获取目标请求
    auto feedback = std::make_shared<Fibonacci::Feedback>();  // 反馈对象
    auto & sequence = feedback->partial_sequence;  // 引用反馈中的部分数列
    sequence.push_back(0);  // 斐波那契数列初始值1
    sequence.push_back(1);  // 斐波那契数列初始值2
    auto result = std::make_shared<Fibonacci::Result>();  // 结果对象

    // 循环计算斐波那契数列(直到达到目标阶数或节点关闭)
    for (int i = 1; (i < goal->order) && rclcpp::ok(); ++i) {
      // 检查是否有取消请求
      if (goal_handle->is_canceling()) {
        result->sequence = sequence;  // 保存当前数列作为取消结果
        goal_handle->canceled(result);  // 标记目标为已取消
        RCLCPP_INFO(this->get_logger(), "Goal canceled");
        return;
      }

      // 更新斐波那契数列(下一个数 = 前两个数之和)
      sequence.push_back(sequence[i] + sequence[i - 1]);
      // 发布反馈
      goal_handle->publish_feedback(feedback);
      RCLCPP_INFO(this->get_logger(), "Publish feedback");

      loop_rate.sleep();  // 按1Hz频率休眠
    }

    // 若节点正常运行,标记目标为成功并返回结果
    if (rclcpp::ok()) {
      result->sequence = sequence;  // 保存完整数列作为结果
      goal_handle->succeed(result);  // 标记目标为成功
      RCLCPP_INFO(this->get_logger(), "Goal succeeded");
    }
  };

};  // class FibonacciActionServer

}  // namespace custom_action_cpp

// 注册节点为组件(支持动态加载)
RCLCPP_COMPONENTS_REGISTER_NODE(custom_action_cpp::FibonacciActionServer)
代码解析
  1. 头文件导入:开头导入编译所需的所有头文件,包括动作接口、ROS 2核心库、动作库及组件注册库。

  2. 节点类定义:创建FibonacciActionServer类,继承自rclcpp::Node,包含动作服务器的核心逻辑。

  3. 构造函数

    • 初始化节点名称为fibonacci_action_server
    • 定义三个关键回调函数:
      • handle_goal:接收目标请求,此处实现为“接受所有目标”。
      • handle_cancel:接收取消请求,此处实现为“接受所有取消请求”。
      • handle_accepted:目标被接受后触发,通过新线程调用execute函数(避免阻塞执行器)。
    • 调用rclcpp_action::create_server创建动作服务器,绑定上述回调函数。
  4. execute函数

    • 核心执行逻辑,在独立线程中运行,按1Hz频率计算斐波那契数列。
    • 每次循环检查是否有取消请求,若有则返回当前数列并标记目标为“已取消”。
    • 若无取消请求,更新数列并发布反馈,直至达到目标阶数。
    • 目标完成后,标记目标为“成功”并返回完整数列。
2.2 编译动作服务器

要让动作服务器代码编译运行,需配置CMakeLists.txt

打开custom_action_cpp/CMakeLists.txt,在find_package调用之后添加以下代码:

# 构建动作服务器库
add_library(action_server SHARED
  src/fibonacci_action_server.cpp)

# 设置头文件目录
target_include_directories(action_server PRIVATE
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)

# 定义编译宏(用于Windows可见性控制)
target_compile_definitions(action_server
  PRIVATE "CUSTOM_ACTION_CPP_BUILDING_DLL")

# 链接依赖库
target_link_libraries(action_server PUBLIC
  ${custom_action_interfaces_TARGETS}
  rclcpp::rclcpp
  rclcpp_action::rclcpp_action
  rclcpp_components::component)

# 注册节点组件并生成可执行文件
rclcpp_components_register_node(action_server 
  PLUGIN "custom_action_cpp::FibonacciActionServer" 
  EXECUTABLE fibonacci_action_server)

# 安装库文件
install(TARGETS
  action_server
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin)

进入ros2_ws工作空间根目录,执行编译:

colcon build

此命令会编译整个工作空间,包括custom_action_cpp功能包中的fibonacci_action_server

2.3 运行动作服务器

动作服务器编译完成后,配置工作空间环境并运行:

# 配置环境(根据操作系统选择对应命令)
# Linux: source install/local_setup.bash
# macOS: source install/local_setup.console
# Windows: call install\local_setup.bat

# 运行动作服务器
ros2 run custom_action_cpp fibonacci_action_server

3. 编写动作客户端

3.1 编写动作客户端代码

打开custom_action_cpp/src/fibonacci_action_client.cpp文件,添加以下代码:

#include <functional>
#include <future>
#include <memory>
#include <string>
#include <sstream>

#include "custom_action_interfaces/action/fibonacci.hpp"
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_action/rclcpp_action.hpp"
#include "rclcpp_components/register_node_macro.hpp"

namespace custom_action_cpp
{
class FibonacciActionClient : public rclcpp::Node
{
public:
  using Fibonacci = custom_action_interfaces::action::Fibonacci;
  using GoalHandleFibonacci = rclcpp_action::ClientGoalHandle<Fibonacci>;

  // 构造函数(接收节点选项)
  explicit FibonacciActionClient(const rclcpp::NodeOptions & options)
  : Node("fibonacci_action_client", options)
  {
    // 创建动作客户端
    this->client_ptr_ = rclcpp_action::create_client<Fibonacci>(
      this,                  // 节点实例
      "fibonacci"           // 动作名称(需与服务器一致)
    );

    // 创建定时器(500毫秒后触发send_goal函数,且仅触发一次)
    auto timer_callback_lambda = [this](){ return this->send_goal(); };
    this->timer_ = this->create_wall_timer(
      std::chrono::milliseconds(500),
      timer_callback_lambda
    );
  }

  // 发送目标请求的函数
  void send_goal()
  {
    using namespace std::placeholders;

    this->timer_->cancel();  // 取消定时器(确保仅发送一次目标)

    // 等待动作服务器启动(超时则关闭节点)
    if (!this->client_ptr_->wait_for_action_server()) {
      RCLCPP_ERROR(this->get_logger(), "Action server not available after waiting");
      rclcpp::shutdown();
    }

    // 创建目标请求(计算10阶斐波那契数列)
    auto goal_msg = Fibonacci::Goal();
    goal_msg.order = 10;

    RCLCPP_INFO(this->get_logger(), "Sending goal");

    // 配置目标发送选项(绑定回调函数)
    auto send_goal_options = rclcpp_action::Client<Fibonacci>::SendGoalOptions();

    // 目标响应回调(处理服务器对目标的接受/拒绝)
    send_goal_options.goal_response_callback = [this](const GoalHandleFibonacci::SharedPtr & goal_handle)
    {
      if (!goal_handle) {
        RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server");
      } else {
        RCLCPP_INFO(this->get_logger(), "Goal accepted by server, waiting for result");
      }
    };

    // 反馈回调(处理服务器发送的实时进度)
    send_goal_options.feedback_callback = [this](
      GoalHandleFibonacci::SharedPtr,  // 未使用的目标句柄
      const std::shared_ptr<const Fibonacci::Feedback> feedback)
    {
      std::stringstream ss;
      ss << "Next number in sequence received: ";
      for (auto number : feedback->partial_sequence) {
        ss << number << " ";
      }
      RCLCPP_INFO(this->get_logger(), ss.str().c_str());
    };

    // 结果回调(处理服务器返回的最终结果)
    send_goal_options.result_callback = [this](const GoalHandleFibonacci::WrappedResult & result)
    {
      // 根据结果代码处理不同情况
      switch (result.code) {
        case rclcpp_action::ResultCode::SUCCEEDED:
          break;  // 成功无需额外处理,直接打印结果
        case rclcpp_action::ResultCode::ABORTED:
          RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
          return;
        case rclcpp_action::ResultCode::CANCELED:
          RCLCPP_ERROR(this->get_logger(), "Goal was canceled");
          return;
        default:
          RCLCPP_ERROR(this->get_logger(), "Unknown result code");
          return;
      }

      // 打印最终结果(完整斐波那契数列)
      std::stringstream ss;
      ss << "Result received: ";
      for (auto number : result.result->sequence) {
        ss << number << " ";
      }
      RCLCPP_INFO(this->get_logger(), ss.str().c_str());
      rclcpp::shutdown();  // 结果处理完成后关闭节点
    };

    // 异步发送目标请求
    this->client_ptr_->async_send_goal(goal_msg, send_goal_options);
  }

private:
  rclcpp_action::Client<Fibonacci>::SharedPtr client_ptr_;  // 动作客户端指针
  rclcpp::TimerBase::SharedPtr timer_;  // 定时器指针(用于触发目标发送)
};  // class FibonacciActionClient

}  // namespace custom_action_cpp

// 注册节点为组件(支持动态加载)
RCLCPP_COMPONENTS_REGISTER_NODE(custom_action_cpp::FibonacciActionClient)
代码解析
  1. 节点类定义:创建FibonacciActionClient类,继承自rclcpp::Node,包含动作客户端的核心逻辑。

  2. 构造函数

    • 初始化节点名称为fibonacci_action_client
    • 调用rclcpp_action::create_client创建动作客户端,指定与服务器一致的动作名称fibonacci
    • 创建500毫秒定时器,触发send_goal函数(确保客户端启动后延迟发送目标,避免服务器未就绪)。
  3. send_goal函数

    • 取消定时器,确保仅发送一次目标。
    • 等待动作服务器启动,超时则关闭节点。
    • 创建目标请求(order=10,即计算10阶斐波那契数列)。
    • 配置三个关键回调函数并绑定到SendGoalOptions
      • goal_response_callback:处理服务器对目标的响应(接受/拒绝)。
      • feedback_callback:实时接收并打印服务器发送的部分数列反馈。
      • result_callback:根据结果代码处理最终结果(成功/中止/取消),打印完整数列后关闭节点。
    • 调用async_send_goal异步发送目标请求。
3.2 编译动作客户端

配置CMakeLists.txt以编译动作客户端代码:

打开custom_action_cpp/CMakeLists.txt,在动作服务器相关配置之后添加以下代码:

# 构建动作客户端库
add_library(action_client SHARED
  src/fibonacci_action_client.cpp)

# 设置头文件目录
target_include_directories(action_client PRIVATE
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)

# 定义编译宏(用于Windows可见性控制)
target_compile_definitions(action_client
  PRIVATE "CUSTOM_ACTION_CPP_BUILDING_DLL")

# 链接依赖库
target_link_libraries(action_client PUBLIC
  ${custom_action_interfaces_TARGETS}
  rclcpp::rclcpp
  rclcpp_action::rclcpp_action
  rclcpp_components::component)

# 注册节点组件并生成可执行文件
rclcpp_components_register_node(action_client 
  PLUGIN "custom_action_cpp::FibonacciActionClient" 
  EXECUTABLE fibonacci_action_client)

# 安装库文件
install(TARGETS
  action_client
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin)

进入ros2_ws工作空间根目录,执行编译:

colcon build

此命令会编译整个工作空间,包括custom_action_cpp功能包中的fibonacci_action_client

3.3 运行动作客户端
  1. 首先在单独的终端中启动动作服务器(确保已配置环境):

    ros2 run custom_action_cpp fibonacci_action_server
    
  2. 打开新终端,配置工作空间环境并运行动作客户端:

    # 配置环境(根据操作系统选择对应命令)
    # Linux: source install/local_setup.bash
    # macOS: source install/local_setup.console
    # Windows: call install\local_setup.bat
    
    # 运行动作客户端
    ros2 run custom_action_cpp fibonacci_action_client
    

你将看到终端中输出以下日志信息:

  • 目标被服务器接受的提示。
  • 服务器发送的每一步反馈(部分斐波那契数列)。
  • 最终计算完成的完整斐波那契数列结果。

总结

在本教程中,你逐步构建了C++动作服务器和动作客户端,并配置它们实现目标、反馈和结果的交互:

  • 动作服务器:接收目标请求,按1Hz频率计算斐波那契数列,实时发送反馈,支持目标取消,并在完成后返回结果。
  • 动作客户端:延迟发送目标请求,处理服务器的响应、反馈和结果,并在结果接收后关闭节点。
rosdep update无法处理osx-homebrew.yaml源时,可按以下方法更换rosdep的源: - **使用gitee上的源**:参考他人在gitee上上传的rosdistro仓库,如https://gitee.com/XiaoXiaoShuai1/rosdistro 。注意要将仓库权限设置为public,路径默认的blob改为raw。例如,正确的链接是https://gitee.com/zhao-xuzuo/rosdistro/raw/master/rosdep/osx-homebrew.yaml ,而默认的https://gitee.com/zhao-xuzuo/rosdistro/blob/master/rosdep/osx-homebrew.yaml 会出现中文相关的错误 [^1]。 - **使用本地源**: - **替换文件内容**:删除文件内所有内容,粘贴特定内容,如博主文件解压地址是/home/pi/Downloads/rosdistro ,需根据实际情况修改。具体内容如下: ```plaintext yaml file:///home/pi/Downloads/rosdistro/master/rosdep/osx-homebrew.yaml osx yaml file:///home/pi/Downloads/rosdistro/master/rosdep/base.yaml yaml file:///home/pi/Downloads/rosdistro/master/rosdep/python.yaml yaml file:///home/pi/Downloads/rosdistro/master/rosdep/ruby.yaml gbpdistro file:///home/pi/Downloads/rosdistro/master/releases/fuerte.yaml fuerte # 如果上行命令在update时无法识别修改为即可成功下载 # yaml file:///home/pi/Downloads/rosdistro/master/releases/fuerte.yaml fuerte # newer distributions (Groovy, Hydro, ...) must not be listed anymore, they are being fetched from the rosdistro index.yaml instead ``` - **替换系统目录内容**:把相关链接都换成本地的目录,示例如下: ```plaintext # os-specific listings first yaml file:///etc/rosdistro/rosdep/osx-homebrew.yaml osx # generic yaml file:///etc/rosdistro/rosdep/base.yaml yaml file:///etc/rosdistro/rosdep/python.yaml yaml file:///etc/rosdistro/rosdep/ruby.yaml gbpdistro file:///etc/rosdistro/releases/fuerte.yaml fuerte # newer distributions (Groovy, Hydro, ...) must not be listed anymore, they are being fetched from the rosdistro index.yaml instead ``` - **本地文件替换**:对fuerte.yaml、targets.yaml进行本地替换,操作如下: ```bash cd home/用户名 mkdir rosdep # 存放之前说的目录和内容 # 确认一下有这个目录存在 /etc/ros/rosdep/ sudo cp -r rosdep /etc/ros/ # 然后开始替换 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ray.so

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值