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

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

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

报错放在另一个文章中。

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》

网址链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值