文章目录
前言
主要学习怎么使用客户端库来实现自己的功能,主要介绍C++ 和python 两种,其他的我不太会。。
因为卡面发现dashing
版本有很多命令不能用,我这边切eloquent
来学习
目前ros2 支持的库有以下这么多
- rosidl_generator_java - 生成java的ros接口.
- rosidl_generator_objc - 生成 Objective C 的ros接口.
- rosidl_generator_cpp - 生成c++ 的ros 接口.
- rosidl_generator_c - 生成C 的ros接口
- rosidl - 提供ROS IDL(.msg)定义和代码生成的包。
- rosidl_dds - 生成ROS接口的dds接口.
基于ros_tutorials 学习 ,可以从以下几个网站学习
这边文章40000+字,很长,可以去我的专栏看
也可以跳转都转个文章来看:
- 创建工作空间
- 创建第一个自己的ROS 2 包
- 写一个简单的发布和订阅(C++)
- 写一个简单的发布和订阅(python)
- 写一个简单的服务端和客户端(C++)
- 写一个简单的服务端和客户端(python)
- 创建自定义的ROS2 msg 和 srv 文件
- ros2doctor 入门
1 创建工作空间
① 背景
工作区是-个包含ROS 2包的目录。在使用ROS 2之前,必须在要使用的终端安装ros2的安装工作区(通过 source your_ws/install/setup.bash
)。这样就可以使用ROS2 自带的软件包。
可以自己创建自定义的工作空间,官方推荐,可以创建一个底层工作空间,和业务工作空间,底层包含所有业务的依赖,也就是模块化
② 前提
已经有的不用重复安装
- 安装ros2
- 安装colcon
sudo apt install python3-colcon-common-extensions
- 安装git
sudo apt install git
- 安装小乌龟
sudo apt install ros-<distro>-turtlesim
- 安装rosdep
sudo apt-get install python-rosdep
- 基本的linux 命令,不会的这里查http://www.ee.surrey.ac.uk/Teaching/Unix/
- 文本编辑器,我这边
C++
用的Clion
,python
用的pycharm
③ 任务
Ⅰ安装ros 2 环境
安装ros2 的环境,通过source
在终端执行:
source /opt/ros/<distro>/setup.bash
例子:
source /opt/ros/eloquent/setup.bash
推荐写入启动文件.bashrc
里,这样就不用敲了,也不会忘记
echo "source /opt/ros/eloquent/setup.bash" >> ~/.bashrc
Ⅱ 创建文件夹
每个工作空间搞一个文件夹就行,最好名字显义
官方用的dev_ws
代表development workspace
mkdir dev_ws
mkdir dev_ws/src
cd dev_ws/src
自己的包就搞到src
下面去就行
Ⅲ clone 教程代码
在dev_ws/src
路径下执行命令:git clone https://github.com/ros/ros_tutorials.git -b eloquent-devel
下面这个样子就是下载好了
Ⅳ 解决依赖
以后自己使用别人的包的时候,也是这个解决依赖问题的
在~/dev_ws
路径下执行 sudo rosdep install -i --from-path src --rosdistro eloquent -y
报错的话,按照要求操作就行
sudo rosdep init
rosdep update
init 老是失败的话,自己想办法吧,网不好
Ⅴ 用colcon 编译
在~/dev_ws
路径下执行编译,命令是这个colcon build
colcon build的其他有用参数:
--packages-up-to
生成所需的包及其所有依赖项,但不是整个工作区(节省时间)--symlink-install
避免了每次调整python脚本时都需要重新构建--event-handlers
console_direct+ 在生成时显示控制台输出
Ⅵ 安装自定义工作空间
默认 安装路径的setup.bash
是自动执行的,所以我们只需要执行dev_ws
下面的setup.bash
可以通过两种方式执行
. install/local_setup.bash
source install/local_setup.bash
自定义的工作空间的同名包会覆盖安装路径下的,这个看下研究下setup.bash
就知道了
Ⅶ 修改覆盖成
修改 ~/dev_ws/src/ros_tutorials/turtlesim/src/turtle_frame.cpp
52行,改成setWindowTitle("MyTurtleSim")
然后编译运行
所以环境不一样,执行的结果也不一样,左边是没有执行安装覆盖层的,右边是安装了的
④ 总结
在本教程中,您将主要的ROS 2发行版安装作为基础,并通过在新工作区中克隆和构建软件包来创建了覆盖层(自定义咯)。如您在修改过的turtlesim中所看到的,覆盖层位于路径的前面,并且优先于底层(/opt/ros)。
建议使用覆盖层来处理少量软件包,因此您不必将所有内容都放在同一工作区中,而不必在每编译都很费时间。
2 创建第一个自己的ROS 2 包
① 背景
Ⅰ什么手机ROS 2 包
可以将包视为ROS 2代码的容器。如果您希望能够安装代码或与他人共享代码,则需要将其组织在一个软件包中。使用软件包,您可以发布ROS 2的工作,并允许其他人轻松构建和使用它。
ROS 2中的软件包创建使用ament作为其构建系统,并使用colcon作为其构建工具。您可以使用官方支持的CMake
或Python
创建软件包,尽管确实存在其他构建类型
Ⅱ ROS 2 包有什么组成
cmake 包
package.xml
文件包含这个包里的元信息CMakeLists.txt
怎么去编译这个包
最简单的长这个样子
my_package/
CMakeLists.txt
package.xml
python 包
package.xml
文件包含这个包里的元信息setup.py
如何按爪个软件包setup.cfg
保证ros2 可以找到这个包your_package_name
- ROS 工具能找到这个包
大概这个样子
my_package/
setup.py
package.xml
resource/my_package
Ⅲ ROS 2 包和工作空间的关系
一个工作空间可以包含n个包,但是不推荐放很多个,不然每次rebuild 的时候,就是该出去遛弯的时候了
大概长这个样子
workspace_folder/
src/
package_1/
CMakeLists.txt
package.xml
package_2/
setup.py
package.xml
resource/my_package
...
package_n/
CMakeLists.txt
package.xml
② 前提
准备号工作空间dev_ws
③ 任务
==下面每个都会有C++ 和 python 两种方式 ==
Ⅰ 创建包
确保到dev_ws/src
路径下
一般我们创建包的命令这个样子就行:
- C++
ros2 pkg create --build-type ament_cmake <package_name>
- python
ros2 pkg create --build-type ament_python <package_name>
我们现在加选项 --node-name
可以创建hello world 的包
- C++
ros2 pkg create --build-type ament_cmake --node-name my_node_cpp my_package_cpp
- python
ros2 pkg create --build-type ament_python --node-name my_node_py my_package_py
Ⅱ 编译包
命令是 colcon build
也可以 选择只编译某一个包 colcon build --packages-select my_package_cpp
这里可能会报邮箱格式不正确的错误,把邮箱在xml里改一下,还是不行,就删除build/
文件夹重新来
Ⅲ 安装设置文件(setup.bash)
cd ~/dev_ws
. install/setup.bash
Ⅳ 运行包
因为用的–node_name 创建的,会有hello world出来
- C++
ros2 run my_package_cpp my_node_cpp
- python
ros2 run my_package_py my_node_py
Ⅴ 看看包里面都有啥子
-
C++
-
python
Ⅵ 修改 package.xml
description, license ,maintaine
r需要去改
这个样子
python要改的话,setup.py
要一致和package.xml
④ 总结
通过pkg
来创建包,通过 colcon
来编译,成功之后可以执行
命令的说明
usage: ros2 pkg create [-h] [--package-format {2,3}]
[--description DESCRIPTION] [--license LICENSE]
[--destination-directory DESTINATION_DIRECTORY]
[--build-type {cmake,ament_cmake,ament_python}]
[--dependencies DEPENDENCIES [DEPENDENCIES ...]]
[--maintainer-email MAINTAINER_EMAIL]
[--maintainer-name MAINTAINER_NAME]
[--node-name NODE_NAME] [--library-name LIBRARY_NAME]
package_name
Create a new ROS2 package
positional arguments:
package_name The package name
optional arguments:
-h, --help show this help message and exit
--package-format {2,3}, --package_format {2,3}
The package.xml format.
--description DESCRIPTION
The description given in the package.xml
--license LICENSE The license attached to this package
--destination-directory DESTINATION_DIRECTORY
Directory where to create the package directory
--build-type {cmake,ament_cmake,ament_python}
The build type to process the package with
--dependencies DEPENDENCIES [DEPENDENCIES ...]
list of dependencies
--maintainer-email MAINTAINER_EMAIL
email address of the maintainer of this package
--maintainer-name MAINTAINER_NAME
name of the maintainer of this package
--node-name NODE_NAME
name of the empty executable
--library-name LIBRARY_NAME
name of the empty library
3 写一个简单的发布和订阅(C++)
① 背景
节点是通过ROS graph 进行通信的可执行程序。在本教程中,节点将通过主题以字符串消息的形式相互传递信息。这里使用的示例是一个简单的“talker”和“listener”系统。一个节点发布数据,另一个节点订阅该主题,以便它可以接收该数据
② 前提
创建好工作空间
③ 任务
Ⅰ 创建一个包
创建一个pubsub的包
ros2 pkg create --build-type ament_cmake cpp_pubsub
改 xml文件里的邮箱别忘了
切换到dev_ws/src/cpp_pubsub/src
路径下
Ⅱ 写发布节点
通过命令下载实例文件
wget -O publisher_member_function.cpp https://raw.githubusercontent.com/ros2/examples/master/rclcpp/minimal_publisher/member_function.cpp
很大概率失败。。。这这这
还是直接贴代码吧
就算失败也会有个空文件
#include <chrono>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
/* This example creates a subclass of Node and uses std::bind() to register a
* member function as a callback from the timer. */
class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}
private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}
代码解析
修改 package.xml
添加几行在里面
<depend>rclcpp</depend>
<depend>std_msgs</depend>
修改 CMakeLists.txt
- 在
find_package(ament_cmake REQUIRED)
下面添加
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
- 接着添加可执行文件的名字为
talker
一会用ros2 run
来运行
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
- 最后添加
install(TARGETS…)
把可执行文件安装到install去
install(TARGETS
talker
DESTINATION lib/${PROJECT_NAME})
最后这个样子
cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
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)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
install(TARGETS
talker
DESTINATION lib/${PROJECT_NAME})
ament_package()
可以尝试编译执行一下
Ⅲ 写订阅节点
通过命令下载实例文件,也是在dev_ws/src/cpp_pubsub/src
路径下
wget -O subscriber_member_function.cpp https://raw.githubusercontent.com/ros2/examples/master/rclcpp/minimal_subscriber/member_function.cpp
很大概率失败。。。这这这
还是直接贴代码吧
就算失败也会有个空文件
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;
class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}
private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}
代码解析
修改 package.xml
基于前面的pub没有新增依赖,所以不用修改
修改 CMakeLists.txt
增加可执行文件和Taeget
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)
install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})
最后应是这样:
cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
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)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})
ament_package()
Ⅳ 编译和运行
- 养成好习惯,编译前先解决依赖,切换到
dev_ws
下
sudo rosdep install -i --from-path src --rosdistro eloquent -y
- 只编译新增的包
colcon build --packages-select cpp_pubsub
- 安装环境
. install/setup.bash
- 运行两个节点
ros2 run cpp_pubsub talker
ros2 run cpp_pubsub listener
④ 总结
您创建了两个节点来发布和订阅主题上的数据。在编译和运行它们之前,您已将它们的依赖项和可执行文件添加到包配置文件中。
这些例子中使用的代码可以在这里找到。
4 写一个简单的发布和订阅(python)
① 背景
节点是通过ROS graph 进行通信的可执行程序。在本教程中,节点将通过主题以字符串消息的形式相互传递信息。这里使用的示例是一个简单的“talker”和“listener”系统。一个节点发布数据,另一个节点订阅该主题,以便它可以接收该数据,基于python
② 前提
创建好工作空间
③ 任务
Ⅰ 创建一个包
创建一个pubsub的包
切换到dev_ws/src
路径下
ros2 pkg create --build-type ament_python py_pubsub
Ⅱ 写发布节点
切换到dev_ws/src/py_pubsub/py_pubsub
路径下执行命令下载demo
wget https://raw.githubusercontent.com/ros2/examples/master/rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function.py
代码在这:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MinimalPublisher(Node):
def __init__(self):
super().__init__('minimal_publisher')
self.publisher_ = self.create_publisher(String, 'topic', 10)
timer_period = 0.5 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0
def timer_callback(self):
msg = String()
msg.data = 'Hello World: %d' % self.i
self.publisher_.publish(msg)
self.get_logger().info('Publishing: "%s"' % msg.data)
self.i += 1
def main(args=None):
rclpy.init(args=args)
minimal_publisher = MinimalPublisher()
rclpy.spin(minimal_publisher)
# Destroy the node explicitly
# (optional - otherwise it will be done automatically
# when the garbage collector destroys the node object)
minimal_publisher.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
代码解析
修改 package.xml
修改描述
<description>Examples of minimal publisher/subscriber using rclpy</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>
增加依赖
<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>
修改 setup.py
这个和xml一样的
maintainer='YourName',
maintainer_email='you@email.com',
description='Examples of minimal publisher/subscriber using rclpy',
license='Apache License 2.0',
在 entry_points
下的 console_scripts
里添加,这是个list可以写无数个
entry_points={
'console_scripts': [
'talker = py_pubsub.publisher_member_function:main',
],
},
检查 setup.cfg
是这样
[develop]
script-dir=$base/lib/py_pubsub
[install]
install-scripts=$base/lib/py_pubsub
这个配置是说明 可执行文件是在 lib
下面,ros2 run 的时候会到目录下查找
写订阅节点
在dev_ws/src/py_pubsub/py_pubsub
下载demo
wget https://raw.githubusercontent.com/ros2/examples/master/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_member_function.py
下载失败的话,从我这边粘贴吧
代码:
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber')
self.subscription = self.create_subscription(
String,
'topic',
self.listener_callback,
10)
self.subscription # prevent unused variable warning
def listener_callback(self, msg):
self.get_logger().info('I heard: "%s"' % msg.data)
def main(args=None):
rclpy.init(args=args)
minimal_subscriber = MinimalSubscriber()
rclpy.spin(minimal_subscriber)
# Destroy the node explicitly
# (optional - otherwise it will be done automatically
# when the garbage collector destroys the node object)
minimal_subscriber.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
代码解析
修改 package.xml
基于前面的pub没有新增依赖,所以不用修改
修改 setup.py
在刚刚的entry_points 在加一行变成这样
entry_points={
'console_scripts': [
'talker = py_pubsub.publisher_member_function:main',
'listener = py_pubsub.subscriber_member_function:main',
],
},
Ⅳ 编译和运行
- 养成好习惯,编译前先解决依赖,切换到
dev_ws
下
sudo rosdep install -i --from-path src --rosdistro eloquent -y
- 只编译新增的包
colcon build --packages-select py_pubsub
- 安装环境
. install/setup.bash
- 运行两个节点
ros2 run py_pubsub talker
ros2 run py_pubsub listener
④ 总结
您创建了两个节点来发布和订阅主题上的数据。在编译和运行它们之前,您已将它们的依赖项和可执行文件添加到包配置文件中。
这些例子中使用的代码可以在这里找到。
5 写一个简单的服务端和客户端(C++)
① 背景
当节点使用服务进行通信时,发送数据请求的节点称为客户机节点,响应请求的节点称为服务节点。请求和响应的结构由.srv文件决定。
这里使用的示例是一个简单的整数加法系统;一个节点请求两个整数的和,另一个节点用结果响应。
其实简单得功能函数就可以解决了,这个解决了数据交互的问题,比如服务端返回客户端需要的,但是不用在客户端管理的数据或者状态,还是模块化
② 前提
学会创建工作空间和包
③ 任务
Ⅰ 创建一个包
打开终端,准备好环境,切换到dev_ws/src
路径下来创建包
ros2 pkg create --build-type ament_cmake cpp_srvcli --dependencies rclcpp example_interfaces
然后出一个文件夹
utry@utry:~/dev_ws/src$ tree cpp_srvcli/
cpp_srvcli/
├── CMakeLists.txt
├── include
│ └── cpp_srvcli
├── package.xml
└── src
加了--dependencies rclcpp example_interfaces
之后,创建包的时候,会自动把package.xml
和CMakeLists.txt
依赖加上,省事,example_interfaces
包含例子srv
文件
utry@utry:~/dev_ws/src/cpp_srvcli$ ros2 interface show example_interfaces/
example_interfaces/action/Fibonacci example_interfaces/srv/AddTwoInts
utry@utry:~/dev_ws/src/cpp_srvcli$ ros2 interface show example_interfaces/srv/AddTwoInts
int64 a
int64 b
---
int64 sum
修改 package.xml
这三个部分还是要改一下的,依赖已经在创建的时候解决了,所以,不用关心依赖了
<description>C++ client server tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>
Ⅱ 写服务节点
到 dev_ws/src/cpp_srvcli/src
下新建文件add_two_ints_server.cpp
demo 代码:
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
#include <memory>
void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
{
response->sum = request->a + request->b;
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld",
request->a, request->b);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");
rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service =
node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");
rclcpp::spin(node);
rclcpp::shutdown();
}
代码解析
这个就是add
函数,把request
的两个数加起来,通过response
返回客户端,顺便打了两行日志
void add(const std::shared_ptr<example_interfaces::srv::AddTwoInts::Request> request,
std::shared_ptr<example_interfaces::srv::AddTwoInts::Response> response)
{
response->sum = request->a + request->b;
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld",
request->a, request->b);
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}
初始化
rclcpp::init(argc, argv);
创建node
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_server");
创建一个服务,名字叫add_two_ints
,并通过add
函数发不出去
rclcpp::Service<example_interfaces::srv::AddTwoInts>::SharedPtr service =
node->create_service<example_interfaces::srv::AddTwoInts>("add_two_ints", &add);
例行日志
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add two ints.");
死循环,不能让进程结束了
rclcpp::spin(node);
添加可执行文件
就是修改CMakeLists.txt
编译的选项
add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
rclcpp example_interfaces)
安装的选项,在ament_package()
这行前面
install(TARGETS
server
DESTINATION lib/${PROJECT_NAME})
Ⅲ 写客户端节点
到 dev_ws/src/cpp_srvcli/src
下新建文件add_two_ints_client.cpp
代码这样子:
#include "rclcpp/rclcpp.hpp"
#include "example_interfaces/srv/add_two_ints.hpp"
#include <chrono>
#include <cstdlib>
#include <memory>
using namespace std::chrono_literals;
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
if (argc != 3) {
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_two_ints_client X Y");
return 1;
}
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
request->a = atoll(argv[1]);
request->b = atoll(argv[2]);
while (!client->wait_for_service(1s)) {
if (!rclcpp::ok()) {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
return 0;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
}
auto result = client->async_send_request(request);
// Wait for the result.
if (rclcpp::spin_until_future_complete(node, result) ==
rclcpp::executor::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum);
} else {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_two_ints");
}
rclcpp::shutdown();
return 0;
}
代码解析
创建节点和client
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_two_ints_client");
rclcpp::Client<example_interfaces::srv::AddTwoInts>::SharedPtr client =
node->create_client<example_interfaces::srv::AddTwoInts>("add_two_ints");
创建request
,这个结构体是那个依赖里面的,
auto request = std::make_shared<example_interfaces::srv::AddTwoInts::Request>();
request->a = atoll(argv[1]);
request->b = atoll(argv[2]);
等待 服务起来
while (!client->wait_for_service(1s)) {
if (!rclcpp::ok()) {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
return 0;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
}
发送请求
auto result = client->async_send_request(request);
等待服务端返回,并打印
if (rclcpp::spin_until_future_complete(node, result) ==
rclcpp::executor::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum);
} else {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_two_ints");
}
添加可执行文件
CMakeLists.txt
改成这样:
cmake_minimum_required(VERSION 3.5)
project(cpp_srvcli)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
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_interfaces REQUIRED)
add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
rclcpp example_interfaces)
add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client
rclcpp example_interfaces)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
install(TARGETS
server
client
DESTINATION lib/${PROJECT_NAME})
ament_package()
Ⅳ 编译和运行
- 养成好习惯,编译前先解决依赖,切换到
dev_ws
下
sudo rosdep install -i --from-path src --rosdistro eloquent -y
- 只编译新增的包
colcon build --packages-select cpp_srvcli
- 安装环境
. install/setup.bash
- 运行两个节点
ros2 run cpp_srvcli server
ros2 run cpp_srvcli client 2 3
④ 总结
您创建了两个节点来通过服务请求和响应数据。您将它们的依赖项和可执行文件添加到程序包配置文件中,可以构建和运行它们,并看到的服务/客户端系统运行起来。
6 写一个简单的服务端和客户端(python)
① 背景
当节点使用服务进行通信时,发送数据请求的节点称为客户机节点,响应请求的节点称为服务节点。请求和响应的结构由.srv文件决定。
这里使用的示例是一个简单的整数加法系统;一个节点请求两个整数的和,另一个节点用结果响应。这儿用python
写的
其实简单得功能函数就可以解决了,这个解决了数据交互的问题,比如服务端返回客户端需要的,但是不用在客户端管理的数据或者状态,还是模块化
② 前提
学会创建工作空间和包
③ 任务
Ⅰ 创建一个包
打开终端,准备好环境,切换到dev_ws/src
路径下来创建包
ros2 pkg create --build-type ament_python py_srvcli --dependencies rclpy example_interfaces
然后出一个文件夹
utry@utry:~/dev_ws/src$ tree py_srvcli/
py_srvcli/
├── package.xml
├── py_srvcli
│ └── __init__.py
├── resource
│ └── py_srvcli
├── setup.cfg
├── setup.py
└── test
├── test_copyright.py
├── test_flake8.py
└── test_pep257.py
3 directories, 8 files
加了--dependencies rclcpp example_interfaces
之后,创建包的时候,会自动把package.xml
和CMakeLists.txt
依赖加上,省事,example_interfaces
包含例子srv
文件
utry@utry:~/dev_ws/src/cpp_srvcli$ ros2 interface show example_interfaces/
example_interfaces/action/Fibonacci example_interfaces/srv/AddTwoInts
utry@utry:~/dev_ws/src/cpp_srvcli$ ros2 interface show example_interfaces/srv/AddTwoInts
int64 a
int64 b
---
int64 sum
修改 package.xml
这三个部分还是要改一下的,依赖已经在创建的时候解决了,所以,不用关心依赖了
<description>Python client server tutorial</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>
修改 setup.py
maintainer='Your Name',
maintainer_email='you@email.com',
description='Python client server tutorial',
license='Apache License 2.0',
Ⅱ 写服务节点
到 dev_ws/src/py_srvcli/py_srvcli
下新建文件service_member_function.py
demo 代码:
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
class MinimalService(Node):
def __init__(self):
super().__init__('minimal_service')
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)
def add_two_ints_callback(self, request, response):
response.sum = request.a + request.b
self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))
return response
def main(args=None):
rclpy.init(args=args)
minimal_service = MinimalService()
rclpy.spin(minimal_service)
rclpy.shutdown()
if __name__ == '__main__':
main()
解析代码
导入消息类型,python
模块
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
初始化节点名字minimal_service
,创建服务add_two_ints
绑定回调 add_two_ints_callback
,并发布
def __init__(self):
super().__init__('minimal_service')
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)
这就是那个回调
def add_two_ints_callback(self, request, response):
response.sum = request.a + request.b
self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))
return response
修改setup.py
在console_scripts
list里加上一行
'service = py_srvcli.service_member_function:main',
Ⅲ 写客户端节点
到 dev_ws/src/py_srvcli/py_srvcli
下新建文件client_member_function.py
demo 代码:
import sys
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
class MinimalClientAsync(Node):
def __init__(self):
super().__init__('minimal_client_async')
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('service not available, waiting again...')
self.req = AddTwoInts.Request()
def send_request(self):
self.req.a = int(sys.argv[1])
self.req.b = int(sys.argv[2])
self.future = self.cli.call_async(self.req)
def main(args=None):
rclpy.init(args=args)
minimal_client = MinimalClientAsync()
minimal_client.send_request()
while rclpy.ok():
rclpy.spin_once(minimal_client)
if minimal_client.future.done():
try:
response = minimal_client.future.result()
except Exception as e:
minimal_client.get_logger().info(
'Service call failed %r' % (e,))
else:
minimal_client.get_logger().info(
'Result of add_two_ints: for %d + %d = %d' %
(minimal_client.req.a, minimal_client.req.b, response.sum))
break
minimal_client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
代码解析
import sys
为了获取输入的参数
初始化节点,创建客户端
super().__init__('minimal_client_async')
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
等待服务端启动
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('service not available, waiting again...')
发送请求
minimal_client.send_request()
等待返回,返回之后打印出来,包括了几个异常退出打印
while rclpy.ok():
rclpy.spin_once(minimal_client)
if minimal_client.future.done():
try:
response = minimal_client.future.result()
except Exception as e:
minimal_client.get_logger().info(
'Service call failed %r' % (e,))
else:
minimal_client.get_logger().info(
'Result of add_two_ints: for %d + %d = %d' %
(minimal_client.req.a, minimal_client.req.b, response.sum))
break
修改setup.py
在console_scripts
list里加上一行
'client = py_srvcli.client_member_function:main',
最后这个样子:
from setuptools import setup
package_name = 'py_srvcli'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='utry',
maintainer_email='jinmenglei',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'service = py_srvcli.service_member_function:main',
'client = py_srvcli.client_member_function:main',
],
},
)
Ⅳ 编译和运行
- 养成好习惯,编译前先解决依赖,切换到
dev_ws
下
sudo rosdep install -i --from-path src --rosdistro eloquent -y
- 只编译新增的包
colcon build --packages-select py_srvcli
- 安装环境
. install/setup.bash
- 运行两个节点
ros2 run py_srvcli server
ros2 run py_srvcli client 2 3
④ 总结
您创建了两个节点来通过服务请求和响应数据。您将它们的依赖项和可执行文件添加到程序包配置文件中,可以构建和运行它们,并看到的服务/客户端系统运行起来。
这里面有个注意点就是客户端的调用方式,有两种,一个是同步call(self.req)
一个是异步call_async(self.req)
,同步就是一直阻塞到有结果,很容易就死锁了,推荐异步
7 创建自定义的ROS2 msg 和 srv 文件
① 背景
在以前的教程中,使用消息和服务接口了解主题、服务以及简单的发布者/订阅者和服务/客户端节点。在这些情况下,您使用的接口是系统的。
系统接口可以满足绝大多数的使用场景,但可能还需要定义自己的消息和服务。本教程将介绍创建自定义接口定义的最简单方法。
② 前提
准备ros2 的工作空间
创建新的srv
和msg
,会通过topic
和service
来测试
③ 任务
Ⅰ 创建包
切换到dev_ws/src)
路径下
ros2 pkg create --build-type ament_cmake tutorial_interfaces
切换到dev_ws/src/tutorial_interfaces
路径下创建两个文件夹
mkdir msg
mkdir srv
Ⅱ 创建自定义接口
msg 定义
切换到tutorial_interfaces/msg
路径下创建新文件Num.msg
,就写一行:
int64 num
一个int64
类型的变量,名字num
srv 定义
切换到tutorial_interfaces/srv
路径下创建新文件AddThreeInts.srv
,就写下面这些:
int64 a
int64 b
int64 c
---
int64 sum
sum = a+b+c ,就这个意思
Ⅲ 修改 CMakeLists.txt
把接口转成 C 头文件和py库,需要用到 rosidl_default_generators
,添加:
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Num.msg"
"srv/AddThreeInts.srv"
)
文件内容是这样的:
cmake_minimum_required(VERSION 3.5)
project(tutorial_interfaces)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
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)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Num.msg"
"srv/AddThreeInts.srv"
)
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
Ⅳ 修改 package.xml
前面的Cmakelist
文件是添加了rosidl_default_generators
,需要在xml里声明下
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
这个样子:
<?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>tutorial_interfaces</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="jinmenglei@utry.cn">utry</maintainer>
<license>TODO: License declaration</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
Ⅴ 编译 tutorial_interfaces
在~/dev_ws路径下执行命令:
colcon build --packages-select tutorial_interfaces
Ⅵ 确认下msg和srv
安装环境,dev_ws
路径下执行
. install/setup.bash
通过下面命令来查看刚刚的msg
和 srv
ros2 interface show tutorial_interfaces/msg/Num
ros2 interface show tutorial_interfaces/srv/AddThreeInts
utry@utry:~/dev_ws$ ros2 interface show tutorial_interfaces/msg/Num
int64 num
utry@utry:~/dev_ws$ ros2 interface show tutorial_interfaces/srv/AddThreeInts
int64 a
int64 b
int64 c
---
int64 sum
Ⅶ 测试新的接口
这里面都用之前的包,稍作修改之后测试
使用 pub/sub测试 num.msg
就是改下之前用的 string
,改成 num
C++ 版本
修改 cpp_pubsub
的包
直接贴代码了:
Publisher:
#include <chrono>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/msg/num.hpp" // CHANGE
using namespace std::chrono_literals;
class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<tutorial_interfaces::msg::Num>("topic", 10); // CHANGE
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}
private:
void timer_callback()
{
auto message = tutorial_interfaces::msg::Num(); // CHANGE
message.num = this->count_++; // CHANGE
RCLCPP_INFO(this->get_logger(), "Publishing: '%d'", message.num); // CHANGE
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<tutorial_interfaces::msg::Num>::SharedPtr publisher_; // CHANGE
size_t count_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}
Subscriber:
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/msg/num.hpp" // CHANGE
using std::placeholders::_1;
class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<tutorial_interfaces::msg::Num>( // CHANGE
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}
private:
void topic_callback(const tutorial_interfaces::msg::Num::SharedPtr msg) const // CHANGE
{
RCLCPP_INFO(this->get_logger(), "I heard: '%d'", msg->num); // CHANGE
}
rclcpp::Subscription<tutorial_interfaces::msg::Num>::SharedPtr subscription_; // CHANGE
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}
CMakeLists.txt:
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED) # CHANGE
add_executable(talker src/publisher_member_function.cpp)
ament_target_dependencies(talker rclcpp tutorial_interfaces) # CHANGE
add_executable(listener src/subscriber_member_function.cpp)
ament_target_dependencies(listener rclcpp tutorial_interfaces) # CHANGE
install(TARGETS
talker
listener
DESTINATION lib/${PROJECT_NAME})
ament_package()
package.xml:
<exec_depend>tutorial_interfaces</exec_depend>
编译
colcon build --packages-select cpp_pubsub
安装环境
. install/setup.bash
运行:
ros2 run cpp_pubsub talker
ros2 run cpp_pubsub listener
python 版本
修改 py_pubsub
包
Publisher:
import rclpy
from rclpy.node import Node
from tutorial_interfaces.msg import Num # CHANGE
class MinimalPublisher(Node):
def __init__(self):
super().__init__('minimal_publisher')
self.publisher_ = self.create_publisher(Num, 'topic', 10) # CHANGE
timer_period = 0.5
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0
def timer_callback(self):
msg = Num() # CHANGE
msg.num = self.i # CHANGE
self.publisher_.publish(msg)
self.get_logger().info('Publishing: "%d"' % msg.num) # CHANGE
self.i += 1
def main(args=None):
rclpy.init(args=args)
minimal_publisher = MinimalPublisher()
rclpy.spin(minimal_publisher)
minimal_publisher.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Subscriber:
import rclpy
from rclpy.node import Node
from tutorial_interfaces.msg import Num # CHANGE
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber')
self.subscription = self.create_subscription(
Num, # CHANGE
'topic',
self.listener_callback,
10)
self.subscription
def listener_callback(self, msg):
self.get_logger().info('I heard: "%d"' % msg.num) # CHANGE
def main(args=None):
rclpy.init(args=args)
minimal_subscriber = MinimalSubscriber()
rclpy.spin(minimal_subscriber)
minimal_subscriber.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
修改 package.xml
,增加一行
<exec_depend>tutorial_interfaces</exec_depend>
编译
colcon build --packages-select py_pubsub
安装环境
. install/setup.bash
运行:
ros2 run py_pubsub talker
ros2 run py_pubsub listener
AddThreeInts.srv 通过 service/client 测试
通过改之前的包,这样快一点
C++ 版本
修改的是cpp_srvcli这个包
Service:
#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/srv/add_three_ints.hpp" // CHANGE
#include <memory>
void add(const std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Request> request, // CHANGE
std::shared_ptr<tutorial_interfaces::srv::AddThreeInts::Response> response) // CHANGE
{
response->sum = request->a + request->b + request->c; // CHANGE
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Incoming request\na: %ld" " b: %ld" " c: %ld", // CHANGE
request->a, request->b, request->c); // CHANGE
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "sending back response: [%ld]", (long int)response->sum);
}
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_three_ints_server"); // CHANGE
rclcpp::Service<tutorial_interfaces::srv::AddThreeInts>::SharedPtr service = // CHANGE
node->create_service<tutorial_interfaces::srv::AddThreeInts>("add_three_ints", &add); // CHANGE
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Ready to add three ints."); // CHANGE
rclcpp::spin(node);
rclcpp::shutdown();
}
Client:
#include "rclcpp/rclcpp.hpp"
#include "tutorial_interfaces/srv/add_three_ints.hpp" // CHANGE
#include <chrono>
#include <cstdlib>
#include <memory>
using namespace std::chrono_literals;
int main(int argc, char **argv)
{
rclcpp::init(argc, argv);
if (argc != 4) { // CHANGE
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "usage: add_three_ints_client X Y Z"); // CHANGE
return 1;
}
std::shared_ptr<rclcpp::Node> node = rclcpp::Node::make_shared("add_three_ints_client"); // CHANGE
rclcpp::Client<tutorial_interfaces::srv::AddThreeInts>::SharedPtr client = // CHANGE
node->create_client<tutorial_interfaces::srv::AddThreeInts>("add_three_ints"); // CHANGE
auto request = std::make_shared<tutorial_interfaces::srv::AddThreeInts::Request>(); // CHANGE
request->a = atoll(argv[1]);
request->b = atoll(argv[2]);
request->c = atoll(argv[3]); // CHANGE
while (!client->wait_for_service(1s)) {
if (!rclcpp::ok()) {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Interrupted while waiting for the service. Exiting.");
return 0;
}
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "service not available, waiting again...");
}
auto result = client->async_send_request(request);
// Wait for the result.
if (rclcpp::spin_until_future_complete(node, result) ==
rclcpp::executor::FutureReturnCode::SUCCESS)
{
RCLCPP_INFO(rclcpp::get_logger("rclcpp"), "Sum: %ld", result.get()->sum);
} else {
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"), "Failed to call service add_three_ints"); // CHANGE
}
rclcpp::shutdown();
return 0;
}
修改 CMakeLists.txt
:
...
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(tutorial_interfaces REQUIRED) # CHANGE
add_executable(server src/add_two_ints_server.cpp)
ament_target_dependencies(server
rclcpp tutorial_interfaces) #CHANGE
add_executable(client src/add_two_ints_client.cpp)
ament_target_dependencies(client
rclcpp tutorial_interfaces) #CHANGE
install(TARGETS
server
client
DESTINATION lib/${PROJECT_NAME})
ament_package()
修改package.xml
:
添加依赖
<exec_depend>tutorial_interfaces</exec_depend>
编译:
colcon build --packages-select cpp_srvcli
安装环境:
. install/setup.bash
运行:
ros2 run cpp_srvcli server
ros2 run cpp_srvcli client 2 3 1
python 版本
这个改的是py_pubsub这个包
Service:
from tutorial_interfaces.srv import AddThreeInts # CHANGE
import rclpy
from rclpy.node import Node
class MinimalService(Node):
def __init__(self):
super().__init__('minimal_service')
self.srv = self.create_service(AddThreeInts, 'add_three_ints', self.add_three_ints_callback) # CHANGE
def add_three_ints_callback(self, request, response):
response.sum = request.a + request.b + request.c # CHANGE
self.get_logger().info('Incoming request\na: %d b: %d c: %d' % (request.a, request.b, request.c)) # CHANGE
return response
def main(args=None):
rclpy.init(args=args)
minimal_service = MinimalService()
rclpy.spin(minimal_service)
rclpy.shutdown()
if __name__ == '__main__':
main()
Client:
from tutorial_interfaces.srv import AddThreeInts # CHANGE
import sys
import rclpy
from rclpy.node import Node
class MinimalClientAsync(Node):
def __init__(self):
super().__init__('minimal_client_async')
self.cli = self.create_client(AddThreeInts, 'add_three_ints') # CHANGE
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('service not available, waiting again...')
self.req = AddThreeInts.Request() # CHANGE
def send_request(self):
self.req.a = int(sys.argv[1])
self.req.b = int(sys.argv[2])
self.req.c = int(sys.argv[3]) # CHANGE
self.future = self.cli.call_async(self.req)
def main(args=None):
rclpy.init(args=args)
minimal_client = MinimalClientAsync()
minimal_client.send_request()
while rclpy.ok():
rclpy.spin_once(minimal_client)
if minimal_client.future.done():
try:
response = minimal_client.future.result()
except Exception as e:
minimal_client.get_logger().info(
'Service call failed %r' % (e,))
else:
minimal_client.get_logger().info(
'Result of add_three_ints: for %d + %d + %d = %d' % # CHANGE
(minimal_client.req.a, minimal_client.req.b, minimal_client.req.c, response.sum)) # CHANGE
break
minimal_client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
修改package.xml:
插入依赖
<exec_depend>tutorial_interfaces</exec_depend>
编译:
colcon build --packages-select py_srvcli
安装环境:
. install/setup.bash
运行:
ros2 run py_srvcli server
ros2 run py_srvcli client 2 3 1
④ 总结
在本文章中,学习创建自定义接口,以及如何从其他包中利用这些接口。
这是一种简单的创建和使用接口的方法。ROS 推荐使用rosidl工具,您可以在这里学习。
.action
文件是另一个可以自定义的ROS 2接口。这个以后在学吧。
8 ros2doctor 入门
① 背景
ros2doctor
可以用来ros2的一些问题
ros2doctor
检查ROS 2的所有方面,包括平台,版本,网络,环境,运行的系统等,并警告您可能的错误和问题原因。
还在开发中,谨慎使用,估计会有点问题
② 前提
确保安装了ros2doctor
,ros2cli
的组成部分
使用小乌龟包来测试
③ 任务
Ⅰ 检查设置
这个是ros2doctor
的帮助文档
utry@utry:~/dev_ws$ ros2 doctor -h
usage: ros2 doctor [-h] [--report | --report-failed] [--include-warnings]
Check ROS setup and other potential issues
optional arguments:
-h, --help show this help message and exit
--report, -r Print all reports.
--report-failed, -rf Print reports of failed checks only.
--include-warnings, -iw
Include warnings as failed checks. Warnings are
ignored by default.
执行ros2 doctor
会检查所有的设置模块
utry@utry:~$ ros2 doctor
All 3 checks passed
utry@utry:~$
我的是没啥错
警告的格式是这个样子的:
<path>: <line>: UserWarning: <message>
比如使用不稳定版本
UserWarning: Distribution <distro> is not fully supported or tested. To get more consistent features, download a stable version at https://index.ros.org/doc/ros2/Installation/
把网络关了就这个提示
utry@utry:~/dev_ws$ ros2 doctor
/opt/ros/eloquent/lib/python3.6/site-packages/ros2doctor/api/format.py: 76: UserWarning: Fail to call PlatformCheck class functions.
All 2 checks passed
也可能出现error
1/3 checks failed
Failed modules: network
出现问题解决问题
Ⅱ 检查系统
可以检测正在运行的系统,这里运行小乌龟
ros2 run turtlesim turtlesim_node
ros2 run turtlesim turtle_teleop_key
主要是一些,publisher without subscriber
或者subscriber without publisher
Ⅲ 生成全面的报告
使用这个命令就行
ros2 doctor --report
生成的结果可以复制出来一个一个处理
这个是我生成结果
utry@utry:~$ ros2 doctor --report
NETWORK CONFIGURATION
inet : 192.168.241.131
inet4 : ['192.168.241.131']
ether : 00:0c:29:67:0e:2f
inet6 : ['fe80::89a1:b2ed:4240:258f']
netmask : 255.255.255.0
device : ens33
flags : 4163<UP,BROADCAST,RUNNING,MULTICAST>
mtu : 1500
broadcast : 192.168.241.255
inet : 127.0.0.1
inet4 : ['127.0.0.1']
inet6 : ['::1']
netmask : 255.0.0.0
device : lo
flags : 73<UP,LOOPBACK,RUNNING>
mtu : 65536
PLATFORM INFORMATION
system : Linux
platform info : Linux-5.3.0-45-generic-x86_64-with-Ubuntu-18.04-bionic
release : 5.3.0-45-generic
processor : x86_64
RMW MIDDLEWARE
middleware name : rmw_fastrtps_cpp
ROS 2 INFORMATION
distribution name : eloquent
distribution type : ros2
distribution status : active
release platforms : {'ubuntu': ['bionic']}
TOPIC LIST
topic : /turtle1/cmd_vel
publisher count : 1
subscriber count : 1
topic : /turtle1/color_sensor
publisher count : 1
subscriber count : 1
topic : /turtle1/pose
publisher count : 1
subscriber count : 1
topic : /turtle1/rotate_absolute/_action/feedback
publisher count : 1
subscriber count : 1
topic : /turtle1/rotate_absolute/_action/status
publisher count : 1
subscriber count : 1
utry@utry:~$
④ 总结
ros2doctor会告诉您ROS 2安装和运行系统中的问题。您可以使用–report参数更深入地了解这些警告背后的信息。
请记住,ros2doctor它不是调试工具;它不会帮助解决代码或系统实现方面的错误。