ROS2入门-话题-服务-接口

news2025/1/1 23:44:00

ROS2入门-话题-服务-接口

本文学习的是《动手学ROS2》

报错放在另一个文章中。

文章目录

  • ROS2入门-话题-服务-接口
    • Linux常用命令
      • sudo
      • chomd 修改文件权限
      • 安装软件
        • apt安装软件
        • dpkg安装deb包
      • 打开终端
      • VS code
      • 关机/重启
      • 静态链接库/动态链接库
      • Cmake设置
      • tree
    • ROS节点
    • 功能包
      • 创建功能包
      • 列出可执行文件
      • 列出所有的包
      • 输出某个包所在路径的前缀
    • colcon
      • 编译工程
      • 只编译一个包
      • 不编译测试单元
      • 运行编译的包的测试
      • 允许通过更改src下的部分文件来改变install(重要)
      • build参数
        • 构建指令
        • 指定构建后安装的目录
        • 合并构建目录
        • 错误时继续安装
        • CMake参数
        • 控制构建线程
        • 开启构建日志
    • 话题
      • 序列化与反序列化
        • 序列化和反序列化的概念
        • 什么情况下需要序列化
      • ROSC++ API链接
      • C++发布者
      • C++接收者
      • 文件结构
      • 执行效果
    • 服务
      • 服务命令
        • 查看服务列表
        • 手动调用服务
        • 查看服务接口类型
        • 查找使用某一接口的服务
      • 服务端
      • 客户端
      • 执行效果
        • round1
        • round2
        • round3
    • 接口
      • 接口格式
      • 可用的类型
      • 创建接口
        • 接口命令
          • 查看接口列表
          • 查看某一个接口详细内容
      • C++自定义接口应用
    • Qos

Linux常用命令

sudo

  • su 是 substitute user 的缩写,表示 使用另一个用户的身份

  • sudo 命令用来以其他身份来执行命令,预设的身份为 root

  • 用户使用 sudo 时,必须先输入密码,之后有 5 分钟的有效期限,超过期限则必须重新输入密码

chomd 修改文件权限

image-20230126220304676

​ 能够设置组,对组进行权限设置,然后后面的文件挂在这个组别下,就可以依照这个组的权限来使用。

安装软件

apt安装软件

apt-get install xxx 安装xxx 。如果带有参数,那么-d 表示仅下载 ,-f 表示强制安装

apt-get remove xxx 卸载xxx
apt-get update 更新软件信息数据库
apt-get upgrade 进行系统升级
apt-cache search 搜索软件包

dpkg安装deb包

  sudo dpkg -i package.deb
dpkg -i package.deb安装包
dpkg -r package删除包
dpkg -P package删除包(包括配置文件)
dpkg -L package列出与该包关联的文件
dpkg -l package显示该包的版本
dpkg –unpack package.deb解开 deb 包的内容
dpkg -S keyword搜索所属的包内容
dpkg -l列出当前已安装的包
dpkg -c package.deb列出 deb 包的内容
dpkg –configure package配置包

打开终端

ctrl+alt+t

VS code

  • 打开终端 Ctrl+Shift+~
  • 即可打开隐藏侧边栏 Ctrl+B

关机/重启

shutdown 关机

reboot 重启

静态链接库/动态链接库

​ 在widows平台下,静态链接库是.lib文件,动态库文件是.dll文件。在linux平台下,静态链接库是.a文件,动态链接库是.so文件。

​ 使用ldd命令可以查看程序的库依赖:

img

Cmake设置

cmake_minimum_required(VERSION 3.22)

project(first_node)

#include_directories 添加特定的头文件搜索路径 ,相当于指定g++编译器的-I参数
include_directories(/opt/ros/humble/include/rclcpp/)
include_directories(/opt/ros/humble/include/rcl/)
include_directories(/opt/ros/humble/include/rcutils/)
include_directories(/opt/ros/humble/include/rcl_yaml_param_parser/)
include_directories(/opt/ros/humble/include/rosidl_runtime_c/)
include_directories(/opt/ros/humble/include/rosidl_typesupport_interface/)
include_directories(/opt/ros/humble/include/rcpputils/)
include_directories(/opt/ros/humble/include/builtin_interfaces/)
include_directories(/opt/ros/humble/include/rmw/)
include_directories(/opt/ros/humble/include/rosidl_runtime_cpp/)
include_directories(/opt/ros/humble/include/tracetools/)
include_directories(/opt/ros/humble/include/rcl_interfaces/)
include_directories(/opt/ros/humble/include/libstatistics_collector/)
include_directories(/opt/ros/humble/include/statistics_msgs/)

# link_directories - 向工程添加多个特定的库文件搜索路径,相当于指定g++编译器的-L参数
link_directories(/opt/ros/humble/lib/)

# add_executable - 生成first_node可执行文件
add_executable(first_node first_ros2_node.cpp)

# target_link_libraries - 为first_node(目标) 添加需要动态链接库,相同于指定g++编译器-l参数
# 下面的语句代替 -lrclcpp -lrcutils
target_link_libraries(first_node rclcpp rcutils)

​ 简化后为:

cmake_minimum_required(VERSION 3.22)

project(first_node)

#include_directories 添加特定的头文件搜索路径 ,相当于指定g++编译器的-I参数
# include_directories(/opt/ros/humble/include/rclcpp/)
# include_directories(/opt/ros/humble/include/rcl/)
# include_directories(/opt/ros/humble/include/rcutils/)
# include_directories(/opt/ros/humble/include/rcl_yaml_param_parser/)
# include_directories(/opt/ros/humble/include/rosidl_runtime_c/)
# include_directories(/opt/ros/humble/include/rosidl_typesupport_interface/)
# include_directories(/opt/ros/humble/include/rcpputils/)
# include_directories(/opt/ros/humble/include/builtin_interfaces/)
# include_directories(/opt/ros/humble/include/rmw/)
# include_directories(/opt/ros/humble/include/rosidl_runtime_cpp/)
# include_directories(/opt/ros/humble/include/tracetools/)
# include_directories(/opt/ros/humble/include/rcl_interfaces/)
# include_directories(/opt/ros/humble/include/libstatistics_collector/)
# include_directories(/opt/ros/humble/include/statistics_msgs/)

# link_directories - 向工程添加多个特定的库文件搜索路径,相当于指定g++编译器的-L参数
# link_directories(/opt/ros/humble/lib/)

#简化后用下面这一句替换上面注释的一堆
find_package(rclcpp REQUIRED)

# add_executable - 生成first_node可执行文件
add_executable(first_node first_ros2_node.cpp)

# target_link_libraries - 为first_node(目标) 添加需要动态链接库,相同于指定g++编译器-l参数
# 下面的语句代替 -lrclcpp -lrcutils
# target_link_libraries(first_node rclcpp rcutils)
#简化时用下面这句替换上面那个,不知道有啥区别
target_link_libraries(first_node rclcpp::rclcpp)

​ 简化前cmake …和make的结果:

image-20230130170322717

​ 简化后cmake …和make的结果:

image-20230130170411524

​ 这里用cmake…的原因是Cmakelist.text在上一级目录,这样生成的链接库可以放在build下面

image-20230130170545937

​ 生成了这么一堆东西。

tree

查看文件目录结构:

tree

image-20230131213840215

ROS节点

​ ROS有四种通信方式:

  • 话题-topics
  • 服务-services
  • 动作-Action
  • 参数-parameters

​ 启动ROS节点:

ros2 run <package_name> <executable_name>

​ 查看节点列表(常用):

ros2 node list

​ 查看节点信息(常用):

ros2 node info <node_name>

​ 重映射节点名称

ros2 run turtlesim turtlesim_node --ros-args --remap __node:=my_turtle

​ 运行节点时设置参数

ros2 run example_parameters_rclcpp parameters_basic --ros-args -p rcl_log_level:=10

功能包

​ 安装一般使用

sudo apt install ros-<version>-package_name

​ 功能包相关指令:

ros2 pkg +
    create       Create a new ROS2 package
    executables  Output a list of package specific executables
    list         Output a list of available packages
    prefix       Output the prefix path of a package
    xml          Output the XML of the package manifest or a specific tag

创建功能包

ros2 pkg create <package-name>  --build-type  {cmake,ament_cmake,ament_python}  --dependencies <依赖名字>

列出可执行文件

​ 列出所有

ros2 pkg executables

​ 列出turtlesim功能包的所有可执行文件

ros2 pkg executables turtlesim

image-20230131173155932

列出所有的包

ros2 pkg list

输出某个包所在路径的前缀

ros2 pkg prefix  <package-name>

image-20230131195156219

colcon

这一部分不是很懂,直接摘抄下来了,等后面有对应案例怎么用再补充吧

编译工程

colcon build

只编译一个包

colcon build --packages-select YOUR_PKG_NAME

不编译测试单元

colcon build --packages-select YOUR_PKG_NAME  --cmake-args -DBUILD_TESTING=0

运行编译的包的测试

colcon test

允许通过更改src下的部分文件来改变install(重要)

每次调整 python 脚本时都不必重新build了

启用--symlink-install后将不会把文拷贝到install目录,而是通过创建符号链接的方式。

colcon build --symlink-install

build参数

构建指令

  • --packages-select,仅生成单个包(或选定的包)。
  • --packages-up-to,构建选定的包,包括其依赖项。
  • --packages-above,整个工作区,然后对其中一个包进行了更改。此指令将重构此包以及(递归地)依赖于此包的所有包。

指定构建后安装的目录

可以通过 --build-base参数和--install-base,指定构建目录和安装目录。

合并构建目录

--merge-install,使用 作为所有软件包的安装前缀,而不是安装基中的软件包特定子目录。–install-base如果没有此选项,每个包都将提供自己的环境变量路径,从而导致非常长的环境变量值。使用此选项时,添加到环境变量的大多数路径将相同,从而导致环境变量值更短。

错误时继续安装

启用--continue-on-error,当发生错误的时候继续进行编译。

CMake参数

--cmake-args,将任意参数传递给CMake。与其他选项匹配的参数必须以空格为前缀。

控制构建线程

  • --executor EXECUTOR,用于处理所有作业的执行程序。默认值是根据所有可用执行程序扩展的优先级选择的。要查看完整列表,请调用 colcon extensions colcon_core.executor --verbose

    • sequential [colcon-core]

      一次处理一个包。

    • parallel [colcon-parallel-executor]

      处理多个作业平行.

  • –parallel-workers NUMBER

    • 要并行处理的最大作业数。默认值为 os.cpu_count() 给出的逻辑 CPU 内核数。

开启构建日志

使用--log-level可以设置日志级别,比如--log-level info

话题

序列化与反序列化

序列化和反序列化的概念

​ 序列化:把对象转换为字节序列的过程称为对象的序列化。
​ 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。

​ 上面是专业的解释,现在来点通俗的解释。在代码运行的时候,我们可以看到很多的对象(debug过的都造吧),可以是一个,也可以是一类对象的集合,很多的对象数据,这些数据中,有些信息我们想让他持久的保存起来,那么这个序列化。就是把内存里面的这些对象给变成一连串的字节描述的过程。常见的就是变成文件。

什么情况下需要序列化

​ 当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;
​ 当你想用套接字在网络上传送对象的时候;
​ 当你想通过RMI传输对象的时候;

​ ROS2的消息序列化与反序列化通信是可以做到跨编程语言、跨平台和跨设备之间的。

ROSC++ API链接

https://docs.ros2.org/latest/api/rclcpp/

image-20230214161626955

C++发布者

​ 基本节点:

#include "rclcpp/rclcpp.hpp"

class TopicPublisher01 : public rclcpp::Node
{
public:
    //构建函数,有一个参数为节点名称
    TopicPublisher01(std::string name) : Node(name)
    {
        RCLCPP_INFO(this->get_logger(),"大家好,我是%s",name.c_str());
    }

private:
    //声明节点
};

int main(int argc, char **argv)
{
    rclcpp::init(argc,argv);
    /*产生一个发送节点*/
    auto node = std::make_shared<TopicPublisher01>("topic_publisher_01");//< >里面的是消息类型
    /*运行节点,并检测退出信号*/
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

​ < >里面的是消息类型

​ 加入发布者后:

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class TopicPublisher01 : public rclcpp::Node
{
public:
    //构建函数,有一个参数为节点名称
    TopicPublisher01(std::string name) : Node(name)
    {
        RCLCPP_INFO(this->get_logger(),"大家好,我是%s",name.c_str());
        //创建发布者
        command_publisher_ = this->create_publisher<std_msgs::msg::String>("command",10);
        //创建定时器,500ms为周期,定时发布
        timer_ = this->create_wall_timer(std::chrono::milliseconds(500),std::bind(&TopicPublisher01::timer_callback,this));
        //std::chrono::milliseconds(500),代表500ms,chrono是c++ 11中的时间库,提供计时,时钟等功能。
        //std::bind(&TopicPublisher01::timer_callback, this),bind() 函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。
    }

private:
    void timer_callback()
    {
        //创建消息
        std_msgs::msg::String message;
        message.data = "forward";
        //日志打印
        RCLCPP_INFO(this->get_logger(),"Publishing:'%s'",message.data.c_str());
        //发布消息
        command_publisher_->publish(message);
    }
    //声明定时器指针
    rclcpp::TimerBase::SharedPtr timer_;
    //声明话题发布者指针
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;
};

int main(int argc, char **argv)
{
    rclcpp::init(argc,argv);
    /*产生一个发送节点*/
    auto node = std::make_shared<TopicPublisher01>("topic_publisher_01");
    /*运行节点,并检测退出信号*/
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

image-20230215095801828

	//声明话题发布者指针
    rclcpp::Publisher<std_msgs::msg::String>::SharedPtr command_publisher_;
	//创建发布者
	command_publisher_ = this->create_publisher<std_msgs::msg::String>("command",10);

​ 先声明定义一个发布者指针,然后创建create_publisher

​ command是topic_name,10是qos值。

​ 也就是这个发布者向command话题发布消息,消息缓冲区是10。

image-20230215101014747

	//声明定时器指针
    rclcpp::TimerBase::SharedPtr timer_;
    //创建定时器,500ms为周期,定时发布
    timer_ = this->create_wall_timer(std::chrono::milliseconds(500),std::bind(&TopicPublisher01::timer_callback,this));
    //std::chrono::milliseconds(500),代表500ms,chrono是c++ 11中的时间库,提供计时,时钟等功能。
    //std::bind(&TopicPublisher01::timer_callback, this),bind() 函数的意义就像它的函数名一样,是用来绑定函数调用的某些参数的。

​ period:std::chrono::milliseconds(500),500ms,每隔500ms触发一次回调

​ callback:std::bind(&TopicPublisher01::timer_callback,this),设置回调函数为timer_callback

​ group:执行该回调的回调组,目前没用到

C++接收者

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class TopicSubscribe01 : public rclcpp::Node
{
public:
    // 构造函数,有一个参数为节点名称
    TopicSubscribe01(std::string name) : Node(name)
    {
        RCLCPP_INFO(this->get_logger(), "大家好,我是%s.", name.c_str());
        //创建一个订阅者订阅话题
        command_subscribe_ = this->create_subscription<std_msgs::msg::String>("command",10,std::bind(&TopicSubscribe01::command_callback, this ,std::placeholders::_1));
    }
    	//订阅command话题,qos为10,回调函数为command_callback,后面两个是默认参数,目前没看出来啥用

private:
    // 声明一个订阅者
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr command_subscribe_;
    //收到话题数据的回调函数
    void command_callback(const std_msgs::msg::String::SharedPtr msg)
    {
        double speed =0.0f;
        if(msg->data == "forward")
        {
            speed = 0.2f;
        }
        RCLCPP_INFO(this->get_logger(),"收到[%s]指令,发送速度%f",msg->data.c_str(),speed);
    }
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    /*产生一个的节点*/
    auto node = std::make_shared<TopicSubscribe01>("topic_subscribe_01");
    /* 运行节点,并检测退出信号*/
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

image-20230215101648090

	// 声明一个订阅者
    rclcpp::Subscription<std_msgs::msg::String>::SharedPtr command_subscribe_;
	//创建一个订阅者订阅话题
	command_subscribe_ = this->create_subscription<std_msgs::msg::String>("command",10,std::bind(&TopicSubscribe01::command_callback, this ,std::placeholders::_1));
    }

​ topic_name:“command”,订阅的话题名称

​ qos:10,消息接收缓冲区10

​ callback:std::bind(&TopicSubscribe01::command_callback, this ,std::placeholders::_1),回调函数

​ 后面俩没用到,说是有默认值。

​ options:创建订阅者的可选项

​ msg_mem_start:用于分配消息的消息内存策略

文件结构

fandos@fandos-virtual-machine:~/d2lros2/chapt3$ tree
.
└── chapt3_ws
    ├── build
    │   ├── COLCON_IGNORE
    │   └── exmaple_topic_rclcpp
    │       ├── ament_cmake_core
    │       │   ├── exmaple_topic_rclcppConfig.cmake
    │       │   ├── exmaple_topic_rclcppConfig-version.cmake
    │       │   ├── package.cmake
    │       │   └── stamps
    │       │       ├── ament_prefix_path.sh.stamp
    │       │       ├── nameConfig.cmake.in.stamp
    │       │       ├── nameConfig-version.cmake.in.stamp
    │       │       ├── package_xml_2_cmake.py.stamp
    │       │       ├── package.xml.stamp
    │       │       ├── path.sh.stamp
    │       │       └── templates_2_cmake.py.stamp
    │       ├── ament_cmake_environment_hooks
    │       │   ├── ament_prefix_path.dsv
    │       │   ├── local_setup.bash
    │       │   ├── local_setup.dsv
    │       │   ├── local_setup.sh
    │       │   ├── local_setup.zsh
    │       │   ├── package.dsv
    │       │   └── path.dsv
    │       ├── ament_cmake_index
    │       │   └── share
    │       │       └── ament_index
    │       │           └── resource_index
    │       │               ├── package_run_dependencies
    │       │               │   └── exmaple_topic_rclcpp
    │       │               ├── packages
    │       │               │   └── exmaple_topic_rclcpp
    │       │               └── parent_prefix_path
    │       │                   └── exmaple_topic_rclcpp
    │       ├── ament_cmake_package_templates
    │       │   └── templates.cmake
    │       ├── ament_cmake_uninstall_target
    │       │   └── ament_cmake_uninstall_target.cmake
    │       ├── ament_cppcheck
    │       ├── ament_lint_cmake
    │       ├── ament_uncrustify
    │       ├── ament_xmllint
    │       ├── cmake_args.last
    │       ├── CMakeCache.txt
    │       ├── CMakeFiles
    │       │   ├── 3.22.1
    │       │   │   ├── CMakeCCompiler.cmake
    │       │   │   ├── CMakeCXXCompiler.cmake
    │       │   │   ├── CMakeDetermineCompilerABI_C.bin
    │       │   │   ├── CMakeDetermineCompilerABI_CXX.bin
    │       │   │   ├── CMakeSystem.cmake
    │       │   │   ├── CompilerIdC
    │       │   │   │   ├── a.out
    │       │   │   │   ├── CMakeCCompilerId.c
    │       │   │   │   └── tmp
    │       │   │   └── CompilerIdCXX
    │       │   │       ├── a.out
    │       │   │       ├── CMakeCXXCompilerId.cpp
    │       │   │       └── tmp
    │       │   ├── cmake.check_cache
    │       │   ├── CMakeDirectoryInformation.cmake
    │       │   ├── CMakeOutput.log
    │       │   ├── CMakeRuleHashes.txt
    │       │   ├── CMakeTmp
    │       │   ├── exmaple_topic_rclcpp_uninstall.dir
    │       │   │   ├── build.make
    │       │   │   ├── cmake_clean.cmake
    │       │   │   ├── compiler_depend.make
    │       │   │   ├── compiler_depend.ts
    │       │   │   ├── DependInfo.cmake
    │       │   │   └── progress.make
    │       │   ├── Makefile2
    │       │   ├── Makefile.cmake
    │       │   ├── progress.marks
    │       │   ├── TargetDirectories.txt
    │       │   ├── topic_publisher_01.dir
    │       │   │   ├── build.make
    │       │   │   ├── cmake_clean.cmake
    │       │   │   ├── compiler_depend.internal
    │       │   │   ├── compiler_depend.make
    │       │   │   ├── compiler_depend.ts
    │       │   │   ├── DependInfo.cmake
    │       │   │   ├── depend.make
    │       │   │   ├── flags.make
    │       │   │   ├── link.txt
    │       │   │   ├── progress.make
    │       │   │   └── src
    │       │   │       ├── topic_publisher_01.cpp.o
    │       │   │       └── topic_publisher_01.cpp.o.d
    │       │   ├── topic_subscribe_01.dir
    │       │   │   ├── build.make
    │       │   │   ├── cmake_clean.cmake
    │       │   │   ├── compiler_depend.internal
    │       │   │   ├── compiler_depend.make
    │       │   │   ├── compiler_depend.ts
    │       │   │   ├── DependInfo.cmake
    │       │   │   ├── depend.make
    │       │   │   ├── flags.make
    │       │   │   ├── link.txt
    │       │   │   ├── progress.make
    │       │   │   └── src
    │       │   │       ├── topic_subscribe_01.cpp.o
    │       │   │       └── topic_subscribe_01.cpp.o.d
    │       │   └── uninstall.dir
    │       │       ├── build.make
    │       │       ├── cmake_clean.cmake
    │       │       ├── compiler_depend.make
    │       │       ├── compiler_depend.ts
    │       │       ├── DependInfo.cmake
    │       │       └── progress.make
    │       ├── cmake_install.cmake
    │       ├── colcon_build.rc
    │       ├── colcon_command_prefix_build.sh
    │       ├── colcon_command_prefix_build.sh.env
    │       ├── CTestConfiguration.ini
    │       ├── CTestCustom.cmake
    │       ├── CTestTestfile.cmake
    │       ├── install_manifest.txt
    │       ├── Makefile
    │       ├── topic_publisher_01
    │       └── topic_subscribe_01
    ├── install
    │   ├── COLCON_IGNORE
    │   ├── exmaple_topic_rclcpp
    │   │   ├── lib
    │   │   │   └── exmaple_topic_rclcpp
    │   │   │       ├── topic_publisher_01
    │   │   │       └── topic_subscribe_01
    │   │   └── share
    │   │       ├── ament_index
    │   │       │   └── resource_index
    │   │       │       ├── package_run_dependencies
    │   │       │       │   └── exmaple_topic_rclcpp
    │   │       │       ├── packages
    │   │       │       │   └── exmaple_topic_rclcpp
    │   │       │       └── parent_prefix_path
    │   │       │           └── exmaple_topic_rclcpp
    │   │       ├── colcon-core
    │   │       │   └── packages
    │   │       │       └── exmaple_topic_rclcpp
    │   │       └── exmaple_topic_rclcpp
    │   │           ├── cmake
    │   │           │   ├── exmaple_topic_rclcppConfig.cmake
    │   │           │   └── exmaple_topic_rclcppConfig-version.cmake
    │   │           ├── environment
    │   │           │   ├── ament_prefix_path.dsv
    │   │           │   ├── ament_prefix_path.sh
    │   │           │   ├── path.dsv
    │   │           │   └── path.sh
    │   │           ├── hook
    │   │           │   ├── cmake_prefix_path.dsv
    │   │           │   ├── cmake_prefix_path.ps1
    │   │           │   └── cmake_prefix_path.sh
    │   │           ├── local_setup.bash
    │   │           ├── local_setup.dsv
    │   │           ├── local_setup.sh
    │   │           ├── local_setup.zsh
    │   │           ├── package.bash
    │   │           ├── package.dsv
    │   │           ├── package.ps1
    │   │           ├── package.sh
    │   │           ├── package.xml
    │   │           └── package.zsh
    │   ├── local_setup.bash
    │   ├── local_setup.ps1
    │   ├── local_setup.sh
    │   ├── _local_setup_util_ps1.py
    │   ├── _local_setup_util_sh.py
    │   ├── local_setup.zsh
    │   ├── setup.bash
    │   ├── setup.ps1
    │   ├── setup.sh
    │   └── setup.zsh
    ├── log
    │   ├── build_2023-02-14_16-06-19
    │   │   ├── events.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_16-08-23
    │   │   ├── events.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_16-09-09
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_16-10-02
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_16-11-15
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_16-57-33
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_16-57-42
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_16-58-33
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_16-59-02
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_17-02-15
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_17-02-47
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_17-04-10
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_17-05-23
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_17-45-52
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_18-08-37
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_18-09-12
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_18-09-52
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-14_18-10-23
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-15_09-50-31
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── build_2023-02-15_09-51-12
    │   │   ├── events.log
    │   │   ├── exmaple_topic_rclcpp
    │   │   │   ├── command.log
    │   │   │   ├── stderr.log
    │   │   │   ├── stdout.log
    │   │   │   ├── stdout_stderr.log
    │   │   │   └── streams.log
    │   │   └── logger_all.log
    │   ├── COLCON_IGNORE
    │   ├── latest -> latest_build
    │   └── latest_build -> build_2023-02-15_09-51-12
    └── src
        └── exmaple_topic_rclcpp
            ├── CMakeLists.txt
            ├── include
            │   └── exmaple_topic_rclcpp
            ├── package.xml
            └── src
                ├── topic_publisher_01.cpp
                └── topic_subscribe_01.cpp

94 directories, 260 files

​ 这里主要就是包名:exmaple_topic_rclcpp,example拼错了。

​ 还有就是两个代码topic_publisher_01.cpp,topic_subscribe_01.cpp上面都贴出来了。

​ CMakeLists.txt内容:

cmake_minimum_required(VERSION 3.8)
project(exmaple_topic_rclcpp)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
# 用啥包需要在这里让它自己找去
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

#联系节点名称和代码文件
add_executable(topic_publisher_01 src/topic_publisher_01.cpp)
#指明需要的依赖
ament_target_dependencies(topic_publisher_01 rclcpp std_msgs)

#还不知道干嘛的,反正那么写上了
install(TARGETS
  topic_publisher_01
  DESTINATION lib/${PROJECT_NAME}
)

#订阅者的,重复上面操作,但最上面的find dependencies只需要一遍就行
add_executable(topic_subscribe_01 src/topic_subscribe_01.cpp)
ament_target_dependencies(topic_subscribe_01 rclcpp std_msgs)

install(TARGETS
topic_subscribe_01
  DESTINATION lib/${PROJECT_NAME}
)

​ package.xml内容:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>exmaple_topic_rclcpp</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="fandos@todo.todo">fandos</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  //这里也是,只需要添加一次需要的依赖包就可以啦
  <depend>rclcpp</depend>
  <depend>std_msgs</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

执行效果

cd chapt3/chapt3_ws/
colcon build --packages-select example_topic_rclcpp
source install/setup.bash
ros2 run example_topic_rclcpp topic_publisher_01

image-20230215174006628

cd chapt3/chapt3_ws/
colcon build --packages-select example_topic_rclcpp
source install/setup.bash
ros2 run example_topic_rclcpp topic_subscribe_01

image-20230215174200767

服务

服务命令

启动一个示例服务节点:

ros2 run examples_rclpy_minimal_service service

查看服务列表

ros2 service list

image-20230215104807880

手动调用服务

ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"

image-20230215104932482

查看服务接口类型

ros2 service type /add_two_ints

image-20230215104948270

查找使用某一接口的服务

ros2 service find example_interfaces/srv/AddTwoInts

image-20230215105049910

add_executable(service_client_01 src/service_client_01.cpp)
ament_target_dependencies(service_client_01 rclcpp)

add_executable(service_server_01 src/service_server_01.cpp)
ament_target_dependencies(service_server_01 rclcpp)

install(TARGETS 
  service_server_01
  DESTINATION lib/${PROJECT_NAME}
)

install(TARGETS 
  service_client_01
  DESTINATION lib/${PROJECT_NAME}
)

服务端

#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"

class ServiceServer01 : public rclcpp::Node{
public:
    ServiceServer01(std::string name) :Node(name){
        RCLCPP_INFO(this->get_logger(),"节点已启动:%s.",name.c_str());
        //创建服务
        add_ints_server_ = this->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints_srv",
        std::bind(&ServiceServer01::handle_add_two_ints,this,std::placeholders::_1,std::placeholders::_2));
    }

private:
    //声明一个服务
    rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr add_ints_server_;
    //收到请求的处理函数
    void handle_add_two_ints(
        const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
        std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response){
            RCLCPP_INFO(this->get_logger(),"收到a:%ld b:%ld",request->a,request->b);
            response->sum = request->a + request->b;
        }
};

int main(int argc, char** argv)
{
    rclcpp::init(argc,argv);
    auto node = std::make_shared<ServiceServer01>("service_server_01");
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

image-20230215164836484

  • ServiceT,消息接口example_interfaces::srv::AddTwoInts
  • service_name,服务名称
  • callback,回调函数,使用成员函数作为回调函数,std::bind进行转换
  • qos_profile,服务质量配置文件,默认rmw_qos_profile_services_default
  • group,调用服务的回调组,默认nullptr
	//声明一个服务
    rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr add_ints_server_;
	//创建服务
    add_ints_server_ = this->create_service<example_interfaces::srv::AddTwoInts>
            ("add_two_ints_srv",                                                                     				std::bind(&ServiceServer01::handle_add_two_ints,                                                      	 this,std::placeholders::_1,std::placeholders::_2));
    }

add_two_ints_srv服务名称,std::bind链接回调函数,剩下两个参数有默认值。

客户端

#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"

class ServiceClient01 : public rclcpp::Node{
public:
    //构建函数,有一个参数为节点名称
    ServiceClient01(std::string name) : Node(name){
        RCLCPP_INFO(this->get_logger(),"节点已启动:%s.",name.c_str());
        //创建客户端
        client_ = this->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints_srv");
    }

    void send_request(int a, int b){
        RCLCPP_INFO(this->get_logger(),"计算%d+%d",a,b);

        //1.等待服务端上线
        while(!client_->wait_for_service(std::chrono::seconds(1))){
            //等待时检测rclcpp的状态
            if(!rclcpp::ok()){
                RCLCPP_ERROR(this->get_logger(),"等待服务的过程中被打断...");
                return;
            }
            RCLCPP_INFO(this->get_logger(),"等待服务端上线中");
        }

        //2.构造请求
        auto request = std::make_shared<example_interfaces::srv::AddTwoInts_Request>();
        request->a = a;
        request->b = b;

        //3.发送异步请求,然后等待返回,返回时调用回调函数
        client_->async_send_request(
            request,std::bind(&ServiceClient01::result_callback_,this,std::placeholders::_1));
    }


private:
    //声明客户端
    rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_;

    void result_callback_(
        rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedFuture result_future){
            auto response = result_future.get();
            RCLCPP_INFO(this->get_logger(),"计算结果:%ld",response->sum);
        }
};

int main(int argc, char** argv)
{
    rclcpp::init(argc,argv);
    /*产生一个节点*/
    auto node = std::make_shared<ServiceClient01>("service_client01");
    /*运行节点,并检测退出信号*/
    //计算5+6的结果
    node->send_request(5, 6);
    rclcpp::spin(node);
    rclcpp::shutdown();
    return 0;
}

image-20230215173413887

​ 创建客户端就要简单得多,后面两个参数老朋友了,忽略就行,就需要输入第一个参数,订阅的服务名称。

	//声明客户端
    rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_;

	//创建客户端
    client_ = this->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints_srv");

​ 订阅add_two_ints_srv服务。

image-20230215175716042

​ 这是重载是吧,记不清了,反正就三种实现方式。

  • request,请求的消息,这里用于放a,b两个数。
  • CallBack,回调函数,异步接收服务器的返回的函数。
	//3.发送异步请求,然后等待返回,返回时调用回调函数
    client_->async_send_request(
        request,std::bind(&ServiceClient01::result_callback_,this,std::placeholders::_1));

​ request是之前按照接口类型定义的

	auto request = std::make_shared<example_interfaces::srv::AddTwoInts_Request>();

​ Callback链接的是result_callback_回调函数

    void result_callback_(
        rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedFuture result_future){
            auto response = result_future.get();
            RCLCPP_INFO(this->get_logger(),"计算结果:%ld",response->sum);
        }

​ 函数的参数是客户端AddTwoInts类型的SharedFuture对象这部分是C++11里面的特性。

image-20230215191517589

​ 原址链接

​ 反正就是get能获取到结果,然后放到了response里面,然后下面那个语句打印出去了。

	auto response = result_future.get();
    RCLCPP_INFO(this->get_logger(),"计算结果:%ld",response->sum);

image-20230215180432228

​ 这个wait_for_service()不是在rclcpp::Client中定义的,而是在它的父类rclcpp::ClientBase里面定义的

image-20230215180810709

​ 参数就一个,等待的时间,返回值是bool类型的,上线了就是true,不上线就是false。

​ 之所以会用的这个函数的原因是,再发送请求之前保证服务端启动了,避免发送一个请求出去而无人响应的尴尬局面。

	//1.等待服务端上线
    while(!client_->wait_for_service(std::chrono::seconds(1))){

​ 要是没上线,就一直执行while里面的东西

执行效果

round1

cd chapt3_ws/
colcon build --packages-select example_service_rclcpp
source install/setup.bash
ros2 run example_service_rclcpp service_client_01

​ 只运行客户端,然后直接ctrl+c结束进程,就得到下面效果:

image-20230215192057411

round2

先运行客户端,然后再运行服务端:

cd chapt3_ws/
colcon build --packages-select example_service_rclcpp
source install/setup.bash
ros2 run example_service_rclcpp service_client_01

另开一个终端:

cd chapt3_ws/
colcon build --packages-select example_service_rclcpp
source install/setup.bash
ros2 run example_service_rclcpp service_server_01

image-20230215192544412

image-20230215193142216

round3

​ 先执行服务端,然后再执行客户端

cd chapt3_ws/
colcon build --packages-select example_service_rclcpp
source install/setup.bash
ros2 run example_service_rclcpp service_server_01

​ 另开一个终端:

cd chapt3_ws/
colcon build --packages-select example_service_rclcpp
source install/setup.bash
ros2 run example_service_rclcpp service_client_01

image-20230215193142216

image-20230215193329801

接口

​ 除了参数之外,话题、服务和动作(Action)都支持自定义接口,每一种通信方式所适用的场景各不相同,所定义的接口也被分为话题接口、服务接口、动作接口三种。

接口格式

​ 话题接口格式:xxx.msg

int64 num

​ 服务接口格式:xxx.srv

int64 a
int64 b
---
int64 sum

​ 动作接口格式:xxx.action

int32 order
---
int32[] sequence
---
int32[] partial_sequence

可用的类型

​ 可以用的基础类型:

bool
byte
char
float32,float64
int8,uint8
int16,uint16
int32,uint32
int64,uint64
string

​ 包装类型:

geometry_msgs/Pose pose

image-20230218200923752

​ 这些接口会被转换为头文件使用

创建接口

这一段有个很坑的大错,所以这一段我都截下来,并修改:

​ 创建功能包,功能包类型必须为:ament_cmake

​ 依赖rosidl_default_generators必须添加,geometry_msgs视内容情况添加(我们这里有geometry_msgs/Pose pose所以要添加)。

ros2 pkg create example_ros2_interfaces --build-type ament_cmake --dependencies rosidl_default_generators geometry_msgs

​ 按照下面目录创建文件夹和文件:

.
├── CMakeLists.txt
├── msg
│   ├── RobotPose.msg
│   └── RobotStatus.msg
├── package.xml
└── srv
    └── MoveRobot.srv

2 directories, 5 files

​ 写入对应文件内容:

​ 服务接口MoveRobot.srv

# 前进后退的距离
float32 distance
---
# 当前的位置
float32 pose

​ 话题接口,采用基础类型 RobotStatus.msg

uint32 STATUS_MOVEING = 1
uint32 STATUS_STOP = 2
uint32  status
float32 pose

​ 话题接口,混合包装类型 RobotPose.msg

uint32 STATUS_MOVEING = 1
uint32 STATUS_STOP = 2
uint32  status
geometry_msgs/Pose pose

​ 接着修改CMakeLists.txt

find_package(rosidl_default_generators REQUIRED)
find_package(geometry_msgs REQUIRED)
# 添加下面的内容
rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/RobotPose.msg"
  "msg/RobotStatus.msg"
  "srv/MoveRobot.srv"
  DEPENDENCIES geometry_msgs
)

​ 修改package.xml:

  <export>
    <build_type>ament_cmake</build_type>
  </export>
  # 添加下面的内容
  <member_of_group>rosidl_interface_packages</member_of_group>

​ 然后编译:

	colcon build --packages-select example_ros2_interfaces

​ 编译完成后在chapt3_ws/install/example_ros2_interfaces/include下你应该可以看到C++的头文件。在chapt3_ws/install/example_ros2_interfaces/local/lib/python3.10/dist-packages下应该可以看到Python版本的头文件。

接口命令

查看接口列表
ros2 interface list

image-20230218213300364

查看某一个接口详细内容
ros2 interface show std_msgs/msg/String

image-20230218213320751

C++自定义接口应用

​ 这里有很重要一个点,也算是可以偷懒省事耍帅的地方,在创建包的时候:

cd chapt3_ws/
ros2 pkg create example_interfaces_rclcpp --build-type ament_cmake --dependencies rclcpp example_ros2_interfaces --destination-directory src --node-name example_interfaces_robot_01 
touch src/example_interfaces_rclcpp/src/example_interfaces_control_01.cpp

​ 逐个解释一下:

  • –build-type ament_cmake 是用ament_cmake进行编译
  • –dependencies rclcpp example_ros2_interfaces 这是指明了两个依赖,其中example_ros2_interfaces是上面代码生成的包
  • –destination-directory src --node-name example_interfaces_robot_01 这一块连起来看,就是在src下生成一个example_interfaces_robot_01 节点,这样的节点一开始有初始代码(虽然没啥用)

​ 然后因为这个例子需要两个节点,所以又用touch创建了一个cpp文件。

​ 这里用–node-name创建节点有个好处,这样创建完在CmakeLists.txt里面关于这个节点需要的编译信息就都已经写好了。方便许多。但可惜–node-name只能添加一个包。

​ 然后手动补全另一个节点example_interfaces_control_01的CmakeLists.txt得到:

cmake_minimum_required(VERSION 3.8)
project(example_interfaces_rclcpp)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(example_ros2_interfaces REQUIRED)

add_executable(example_interfaces_robot_01 src/example_interfaces_robot_01.cpp)
target_include_directories(example_interfaces_robot_01 PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)
target_compile_features(example_interfaces_robot_01 PUBLIC c_std_99 cxx_std_17)  # Require C99 and C++17
ament_target_dependencies(
  example_interfaces_robot_01
  "rclcpp"
  "example_ros2_interfaces"
)

install(TARGETS example_interfaces_robot_01
  DESTINATION lib/${PROJECT_NAME})


add_executable(example_interfaces_control_01 src/example_interfaces_control_01.cpp)
target_include_directories(example_interfaces_control_01 PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)
target_compile_features(example_interfaces_control_01 PUBLIC c_std_99 cxx_std_17)  # Require C99 and C++17
ament_target_dependencies(
  example_interfaces_control_01
  "rclcpp"
  "example_ros2_interfaces"
)

install(TARGETS example_interfaces_control_01
  DESTINATION lib/${PROJECT_NAME})

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

​ 对于这里面的很多内容具体是什么意思还是不清楚,能知道的也不过是节点和节点之间有分开的独立部分,也有公用的部分。

​ 公用的部分,包括package.xml部分和CmakeLists.txt里的这些:

cmake_minimum_required(VERSION 3.8)
project(example_interfaces_rclcpp)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

# find dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(example_ros2_interfaces REQUIRED)

......(中间这部分两个节点各写各的)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  # the following line skips the linter which checks for copyrights
  # comment the line when a copyright and license is added to all source files
  set(ament_cmake_copyright_FOUND TRUE)
  # the following line skips cpplint (only works in a git repo)
  # comment the line when this package is in a git repo and when
  # a copyright and license is added to all source files
  set(ament_cmake_cpplint_FOUND TRUE)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()

​ 中间的部分各写各的,前后是一样的。各写各的部分:

example_interfaces_robot_01:

add_executable(example_interfaces_robot_01 src/example_interfaces_robot_01.cpp)
target_include_directories(example_interfaces_robot_01 PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)
target_compile_features(example_interfaces_robot_01 PUBLIC c_std_99 cxx_std_17)  # Require C99 and C++17
ament_target_dependencies(
  example_interfaces_robot_01
  "rclcpp"
  "example_ros2_interfaces"
)

install(TARGETS example_interfaces_robot_01
  DESTINATION lib/${PROJECT_NAME})

example_interfaces_control_01:

add_executable(example_interfaces_control_01 src/example_interfaces_control_01.cpp)
target_include_directories(example_interfaces_control_01 PUBLIC
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
  $<INSTALL_INTERFACE:include>)
target_compile_features(example_interfaces_control_01 PUBLIC c_std_99 cxx_std_17)  # Require C99 and C++17
ament_target_dependencies(
  example_interfaces_control_01
  "rclcpp"
  "example_ros2_interfaces"
)

install(TARGETS example_interfaces_control_01
  DESTINATION lib/${PROJECT_NAME})

​ 也能看出来,两个节点虽然各写各的,但内容也极度相似。但应该这两部分可以写不一样的内容。

​ 顺便贴一下package.xml内容,这部分两个节点是一样的:

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>example_interfaces_rclcpp</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="fandos@todo.todo">fandos</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>rclcpp</depend>
  <depend>example_ros2_interfaces</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

​ 这里最值得注意的就是example_ros2_interfaces,这是另外一个包的包名,我不知道究竟是用包名做依赖还是用接口名做依赖,这个可能后边才能知道。

​ 接下来看看这两个节点代码:

​ example_interfaces_control_01.cpp:

#include "rclcpp/rclcpp.hpp"
//这里包含了两个头文件,使用的是example_ros2_interfaces里面的两个接口
#include "example_ros2_interfaces/srv/move_robot.hpp"
#include "example_ros2_interfaces/msg/robot_status.hpp"
/*MoveRobot.srv
# 前进后退的距离
float32 distance
---
# 当前的位置
float32 pose
*/

/*RobotStatus.msg
uint32 STATUS_MOVEING = 1
uint32 STATUS_STOP = 2
uint32  status
float32 pose
*/

class ExampleInterfacesControl : public rclcpp::Node {
public:
  // 构造函数,有一个参数为节点名称
  ExampleInterfacesControl(std::string name) : Node(name) {
    RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
    //创建move_robot客户端 service
    client_ = this->create_client<example_ros2_interfaces::srv::MoveRobot>("move_robot");
    //订阅机器人状态话题 topic
    robot_status_subscribe_ = this->create_subscription<example_ros2_interfaces::msg::RobotStatus>("robot_status",10,std::bind(&ExampleInterfacesControl::robot_status_callback_,this,std::placeholders::_1));
  }

  /**
   * @brief 发送移动机器人请求函数
   * 步骤:1.等待服务上线
   *      2.构造发送请求
   * 
   * @param distance 
   */
  void move_robot(float distance){
    //先打印输出要做的请求操作
    RCLCPP_INFO(this->get_logger(),"请求让机器人移动%f",distance);

    //等待服务端上线
    //每隔1s刷新一次等待,客户端准备好了就跳出while
    while(!client_->wait_for_service(std::chrono::seconds(1))){
      //等待时检测rclcpp的状态
      if(!rclcpp::ok()){
        RCLCPP_ERROR(this->get_logger(),"等待服务的过程中被打断...");
        return;
      }
      RCLCPP_INFO(this->get_logger(),"等待服务端上线中");
    }

    //服务上线后构造请求
    //使用example_ros2_interfaces包中的Request接口来传输数据
    auto request = std::make_shared<example_ros2_interfaces::srv::MoveRobot::Request>();
    request->distance = distance;

    //发送异步请求,然后等待返回,返回时调用回调函数
    client_->async_send_request(
      request,std::bind(&ExampleInterfacesControl::result_call_back_,this,std::placeholders::_1)
    );
  }

private:
  // 声明客户端
  rclcpp::Client<example_ros2_interfaces::srv::MoveRobot>::SharedPtr client_;
  //声明订阅端
  rclcpp::Subscription<example_ros2_interfaces::msg::RobotStatus>::SharedPtr robot_status_subscribe_;
  
  /* 机器人移动结果服务回调函数 */
  void result_call_back_(
    rclcpp::Client<example_ros2_interfaces::srv::MoveRobot>::SharedFuture
      result_future) {
    auto response = result_future.get();
    RCLCPP_INFO(this->get_logger(), "收到移动结果:%f", response->pose);
  }

  /**
   * @brief 机器人状态话题接收回调函数
   * 
   * @param msg 
   */
  void robot_status_callback_(const example_ros2_interfaces::msg::RobotStatus::SharedPtr msg)
  {
    RCLCPP_INFO(this->get_logger(), "收到状态数据位置:%f 状态:%d", msg->pose ,msg->status);
  }
};

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  auto node = std::make_shared<ExampleInterfacesControl>("example_interfaces_control_01");
  /*这里调用了服务,让机器人向前移动5m*/
  node->move_robot(5.0);
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

​ example_interfaces_robot_01.cpp:

#include "rclcpp/rclcpp.hpp"
//这里包含了两个头文件,使用的是example_ros2_interfaces里面的两个接口
#include "example_ros2_interfaces/msg/robot_status.hpp"
#include "example_ros2_interfaces/srv/move_robot.hpp"
/*RobotStatus.msg
uint32 STATUS_MOVEING = 1
uint32 STATUS_STOP = 2
uint32  status
float32 pose
*/

/*MoveRobot.srv
# 前进后退的距离
float32 distance
---
# 当前的位置
float32 pose
*/

/*创建一个机器人类,模拟真实机器人*/
class Robot {
public:
  Robot() = default;
  ~Robot() = default;
  
  //移动指定距离
  float move_distance(float distance)
  {
    //把当前状态设为移动
    status_ = example_ros2_interfaces::msg::RobotStatus::STATUS_MOVEING;
    target_pose_ += distance;
    //当目标距离和当前距离大于0.01则持续向目标移动
    while(fabs(target_pose_ - current_pose_)>0.01)
    {
      //每一步移动当前到目标距离的1/10 
      //distance/fabs(distance) 这个用于标明前进或者后退,即提供一个符号
      float step = distance/fabs(distance) * fabs(target_pose_ - current_pose_) * 0.1;
      current_pose_ += step;
      std::cout<<"移动了:" <<step<<"当前位置:"<<current_pose_<<std::endl;
      //当前线程休眠500ms
      std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    //把当前状态设为停止
    status_ = example_ros2_interfaces::msg::RobotStatus::STATUS_STOP;
    return current_pose_;
  }

  //获得current_pose_当前位置
  float get_current_pose(){
    return current_pose_;
  }

  /**
   * @brief Get the status 获得当前状态
   *
   * @return int
   *  1 example_ros2_interfaces::msg::RobotStatus::STATUS_MOVEING
   *  2 example_ros2_interfaces::msg::RobotStatus::STATUS_STOP
   */
  int get_status(){
    return status_;
  }

private:
  //初始化当前位置
  float current_pose_ = 0.0;
  //初始化目标距离
  float target_pose_ = 0.0;
  //初始化当前状态为停止
  int status_ = example_ros2_interfaces::msg::RobotStatus::STATUS_STOP;
};


class ExampleInterfacesRobot : public rclcpp::Node {
public:
  ExampleInterfacesRobot(std::string name) : Node(name) {
    RCLCPP_INFO(this->get_logger(), "节点已启动:%s.", name.c_str());
   //创建move_robot服务端
   move_robot_server_ = this->create_service<example_ros2_interfaces::srv::MoveRobot>(
    "move_robot",std::bind(&ExampleInterfacesRobot::handle_move_robot,this,std::placeholders::_1,std::placeholders::_2));
    //创建发布者
    robot_status_publisher_ = this->create_publisher<example_ros2_interfaces::msg::RobotStatus>("robot_status",10);
    //创建一个周期为500ms的定时器
    timer_ = this->create_wall_timer(std::chrono::milliseconds(500),std::bind(&ExampleInterfacesRobot::timer_callback,this));
   
  }

private:
  Robot robot;//实例化机器人
  rclcpp::TimerBase::SharedPtr timer_;//定时器,用于定时发布机器人位置
  rclcpp::Service<example_ros2_interfaces::srv::MoveRobot>::SharedPtr move_robot_server_;//移动机器人服务端
  rclcpp::Publisher<example_ros2_interfaces::msg::RobotStatus>::SharedPtr robot_status_publisher_;//发布机器人位姿的发布者

  /**
   * @brief 500ms 定时回调函数,
   * 
   */
  void timer_callback(){
    //创建消息
    example_ros2_interfaces::msg::RobotStatus message;
    message.status = robot.get_status();
    message.pose = robot.get_current_pose();
    //打印消息和message里面的消息调用了两次get_current_pose(),不会导致两边数据不一样吗?这个设计并不好
    RCLCPP_INFO(this->get_logger(),"Publishing:%f",robot.get_current_pose());
    //发布消息
    robot_status_publisher_->publish(message);
  }

  /**
   * @brief 收到话题数据的回调函数
   * 
   * @param request 请求共享指针,包含移动距离
   * @param response 响应的共享指针,包含当前位置信息
   */
  void handle_move_robot(const std::shared_ptr<example_ros2_interfaces::srv::MoveRobot::Request> request,
    std::shared_ptr<example_ros2_interfaces::srv::MoveRobot::Response> response){
      RCLCPP_INFO(this->get_logger(),"收到请求移动距离:%f,当前位置:%f",request->distance,robot.get_current_pose());
      robot.move_distance(request->distance);
      response->pose = robot.get_current_pose();
    }
};

int main(int argc, char** argv) {
  rclcpp::init(argc, argv);
  auto node = std::make_shared<ExampleInterfacesRobot>("example_interfaces_robot_01");
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

​ 整个代码是挺多的,分析起来也很麻烦的样子。

image-20230219174037520

​ 图虽然夸张了点,但这样就清楚多了,服务是双向的,一对一的,通过/parameter_events建立服务,这个名称在代码中搜不到,不知道是怎么生成出来的,话题可以多对多,但不是实时的,通过/robot_status来沟通,example_interfaces_robot_01发布话题内容,example_interfaces_control_01接收话题

image-20230219174425202

​ 只显示节点的话,看起来清爽一些,意思是一样的。

learning kills.excalidraw

​ 咱就是说,白板确实好用,虽然还是很多细节没有画上去,但整体的脉络已经很清晰了。

​ 在机器人移动期间,控制端就收不到了来自机器人端的实时位置信息的话题发布了。原因是服务端调用机器人移动的时候造成了主线程的阻塞和休眠,只有机器人完成移动后才会退出,造成了发布数据的定时器回调无法正常进行。

Qos

​ 这个部分有篇文章,但是没有实际应用,不太能理解具体效果,贴个网址,网址失效的话去joplin里面的ROS找《一文搞懂Qos》

​ 网址链接

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/359834.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据分析,如何看待我国1400万人忍受极端通勤,单程通勤超60分钟!

女生极限通勤每天来回 6.5 小时&#xff0c;上海某位女士公司离家单程约100公里左右&#xff0c;单程通勤需要3小15分&#xff0c;来回通勤时间为6.5小时。如此长的通勤时间却不是个例&#xff0c;全国有超1400万人正在忍受单程超过60分钟的极端通勤&#xff0c;如何看待我国 1…

将默认安装的 WSL2 迁移至指定目录

将默认安装的 WSL2 迁移至指定目录WSL2 默认安装在 C 盘下&#xff0c;系统盘空间有限&#xff0c;推荐更改安装目录。 1. 默认安装的 WSL2 目录 C:\Users\cheng\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu20.04onWindows_79rhkp1fndgsc\LocalState\ext4.vhdx 2. …

使用Swiper插件实现视频轮播,怎么实现切换自动播放视频?

一、需求分析 这两天讨论了一个项目需求&#xff0c;刚开始是希望&#xff1a;轮播图中嵌入视频&#xff0c;轮播到视频自动播放&#xff0c;播放完毕切换下一张轮播&#xff0c;手动切换时暂停播放视频。后面因为自动播放没有声音&#xff0c;便暂时放弃了这个想法&#xff0…

第2讲-数据库系统的结构抽象与演变(测试题总结)

一、测试题 DBS的三级模式&#xff1a;外模式&#xff08;也叫用户模式或子模式&#xff09;&#xff0c;模式&#xff08;也叫逻辑模式&#xff09;&#xff0c;内模式&#xff08;也叫存储模式&#xff09; 外模式/模式映像 实现了数据的逻辑独立性 模式/内模式映像 实现了…

李宏毅流模型

李宏毅流模型 常见的3中生成模型,Flow-base Model是第四种生成模型 2. normal distribution&#xff1a;正态分布 max object function:最大目标函数 dimention:维度 Inverse function&#xff1a;反函数 如果两个函数本身就互为反函数&#xff0c;那么这两个函数求解得到的两…

一、在没有Vuex如何解决组件间数据传递的问题

Vuex【state、mutations、actions、modules、getters】 一、在没有Vuex如何解决组件间数据传递的问题 举例&#xff1a;点击不同的选项卡来切换页面时 选项卡和页面属于不同的组件&#xff0c;他们都在同一个父组件之下。 选项卡和页面组件的数据都来源于父组件。在选项卡组…

Relational KD(CVPR 2019)原理与代码解析

paper&#xff1a;Relational Knowledge Distillationcode&#xff1a;https://github.com/megvii-research/mdistiller/blob/master/mdistiller/distillers/RKD.py背景本文从语言结构主义的角度来重新审视知识蒸馏&#xff0c;前者主要关注一个符号学系统中的结构关系。索续尔…

Linux系列 linux 常用命令(笔记)

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.linux 常用命令&#xff08;目录文和件基本操作&#xff09; 1.命令的分类…

一文读懂功率放大器(功率放大器的特性是什么意思)

功率放大器是一种电子放大器&#xff0c;旨在增加给定输入信号的功率幅度。功率放大器一般要求得到一定的不失真或者较小失真的输出功率&#xff0c;在大信号状态下进行工作&#xff0c;主要是输出较大功率。功率放大器的特性介绍&#xff1a;1、增益功率放大器的增益主要是指放…

postman利用newman生成测试报告

1.安装nodejs nodejs下载地址&#xff1a;https://nodejs.org/zh-cn/配置环境变量&#xff1a;在path环境变量中增加nodejs的安装路径 安装完成后&#xff0c;在控制台执行node -v检查是否安装成功2.安装newman 以管理员身份打开cmd控制台&#xff0c;执行如下命令安装newma…

测试团队都在用哪些不错的测试用例管理平台?盘点6大主流测试管理系统

测试团队使用的主流测试用例管理平台&#xff1a;1.PingCode &#xff1b;2.TestRail&#xff1b;3.Testlink&#xff1b;4.ZephyrJira&#xff1b;5.TestCenter&#xff1b;6.飞蛾。目前市面上的测试用例管理工具有很多&#xff0c;但由于针对的项目、领域、目标用户&#xff…

速看!!!一套能直接拿捏大厂面试官的软件测试面试宝典

3.5.1、说说你们是怎么做自动化测试的☆☆☆☆☆我们的自动化测试主要是web UI的自动化测试&#xff0c;主要用于冒烟测试和主要功能的回归测试或者主流浏览器的兼容性测试&#xff0c;作为手工测试的一种补充&#xff0c;提高测试效率&#xff0c;减少一些重复性的测试工作。1…

kubectl

目录 一、陈述式资源管理方法 二、基本信息查看 2.1 基本信息查看格式 2.2 查看master节点组件状态 2.3 查看命名空间 2.4 创建/查看命名空间 2.5 删除&#xff08;重启&#xff09;命名空间/pod 2.6 查看资源的详细信息 2.7 创建副本控制器来启动Pod 2.8 查看指定命…

Linux-0.11 文件系统buffer.c详解

Linux-0.11 文件系统buffer.c详解 buffer_init void buffer_init(long buffer_end)该函数的作用主要是初始化磁盘的高速缓冲区。 刚开始使用h指针指向了start_buffer的位置。 struct buffer_head * h start_buffer; void * b; int i;start_buffer定义为end的位置&#xff…

数据结构与算法基础(王卓)(11):栈的定义及其基础操作(顺序表和链表的初始化、求长度,是否为空,清空和销毁、出栈、压栈)

栈的定义&#xff1a; stack&#xff1a;一堆&#xff0c;一摞;堆&#xff1b;垛; 顺序栈和链栈的设计参考&#xff1a; 数据结构与算法基础&#xff08;王卓&#xff09;&#xff08;7&#xff09;&#xff1a;小结&#xff1a;关于链表和线性表的定义及操作_宇 -Yu的博客-C…

【免费教程】 SWMM在城市水环境治理中的应用及案例分析

SWMMSWMM&#xff08;storm water management model&#xff0c;暴雨洪水管理模型&#xff09;是一个动态的降水-径流模拟模型&#xff0c;主要用于模拟城市某一单一降水事件或长期的水量和水质模拟。EPA&#xff08;Environmental Protection Agency&#xff0c;环境保护署&am…

QTCreator 设置编码格式

显示文件编码格式 选择“工具>首选项>文本编辑器>显示>显示文件编码” 全局设置 选择“工具>首选项>文本编辑器>行为>文件编码” 将文件编码设置为utf-8&#xff0c;UTF-8 BOM 选择存在则保留&#xff0c;最后选择apply。 打开项目设置 选择“项目&…

解析HTTP/2如何提升网络速度

我们知道HTTP/1.1 为网络效率做了大量的优化&#xff0c;最核心的有如下三种方式&#xff1a; 增加了持久连接&#xff1b;浏览器为每个域名最多同时维护 6 个 TCP 持久连接&#xff1b;使用 CDN 的实现域名分片机制。 虽然 HTTP/1.1 采取了很多优化资源加载速度的策略&#x…

[学习笔记]SQL server完全备份指南

方式一&#xff0c;使用SQL Server Management Studio 准备工作 连接目标数据库服务器 在目标数据库上右键->属性&#xff0c;将数据库的恢复模式设置为“简单”&#xff0c;兼容级别设置为“SQL Server 2016(130)” [可选]将表中将无用的业务数据删除&#xff0c;以减…

Java EE|TCP/IP协议栈之传输层UDP协议详解

文章目录一、对UDP协议的感性认识简介主要特点二、UDP的报文结构协议端格式概览报文结构详解源端口目的端口16位UDP报文长度16位校验和参考一、对UDP协议的感性认识 简介 UDP&#xff0c;是User Datagram Protocol的简称&#xff0c;中文名是用户数据报协议&#xff0c;是OSI…