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 修改文件权限
能够设置组,对组进行权限设置,然后后面的文件挂在这个组别下,就可以依照这个组的权限来使用。
安装软件
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命令可以查看程序的库依赖:
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的结果:
简化后cmake …和make的结果:
这里用cmake…的原因是Cmakelist.text在上一级目录,这样生成的链接库可以放在build下面
生成了这么一堆东西。
tree
查看文件目录结构:
tree
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
列出所有的包
ros2 pkg list
输出某个包所在路径的前缀
ros2 pkg prefix <package-name>
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/
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;
}
//声明话题发布者指针
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。
//声明定时器指针
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;
}
// 声明一个订阅者
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
cd chapt3/chapt3_ws/
colcon build --packages-select example_topic_rclcpp
source install/setup.bash
ros2 run example_topic_rclcpp topic_subscribe_01
服务
服务命令
启动一个示例服务节点:
ros2 run examples_rclpy_minimal_service service
查看服务列表
ros2 service list
手动调用服务
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 5,b: 10}"
查看服务接口类型
ros2 service type /add_two_ints
查找使用某一接口的服务
ros2 service find example_interfaces/srv/AddTwoInts
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;
}
- 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;
}
创建客户端就要简单得多,后面两个参数老朋友了,忽略就行,就需要输入第一个参数,订阅的服务名称。
//声明客户端
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client_;
//创建客户端
client_ = this->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints_srv");
订阅add_two_ints_srv服务。
这是重载是吧,记不清了,反正就三种实现方式。
- 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里面的特性。
原址链接
反正就是get能获取到结果,然后放到了response里面,然后下面那个语句打印出去了。
auto response = result_future.get();
RCLCPP_INFO(this->get_logger(),"计算结果:%ld",response->sum);
这个wait_for_service()不是在rclcpp::Client中定义的,而是在它的父类rclcpp::ClientBase里面定义的
参数就一个,等待的时间,返回值是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结束进程,就得到下面效果:
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
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
接口
除了参数之外,话题、服务和动作(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
这些接口会被转换为头文件使用
创建接口
这一段有个很坑的大错,所以这一段我都截下来,并修改:
创建功能包,功能包类型必须为: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
查看某一个接口详细内容
ros2 interface show std_msgs/msg/String
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;
}
整个代码是挺多的,分析起来也很麻烦的样子。
图虽然夸张了点,但这样就清楚多了,服务是双向的,一对一的,通过/parameter_events建立服务,这个名称在代码中搜不到,不知道是怎么生成出来的,话题可以多对多,但不是实时的,通过/robot_status来沟通,example_interfaces_robot_01发布话题内容,example_interfaces_control_01接收话题
只显示节点的话,看起来清爽一些,意思是一样的。
咱就是说,白板确实好用,虽然还是很多细节没有画上去,但整体的脉络已经很清晰了。
在机器人移动期间,控制端就收不到了来自机器人端的实时位置信息的话题发布了。原因是服务端调用机器人移动的时候造成了主线程的阻塞和休眠,只有机器人完成移动后才会退出,造成了发布数据的定时器回调无法正常进行。
Qos
这个部分有篇文章,但是没有实际应用,不太能理解具体效果,贴个网址,网址失效的话去joplin里面的ROS找《一文搞懂Qos》
网址链接