[ros2学习]-Client Libraries

文章目录

前言

主要学习怎么使用客户端库来实现自己的功能,主要介绍C++ 和python 两种,其他的我不太会。。
因为卡面发现dashing版本有很多命令不能用,我这边切eloquent来学习
目前ros2 支持的库有以下这么多

基于ros_tutorials 学习 ,可以从以下几个网站学习

这边文章40000+字,很长,可以去我的专栏
也可以跳转都转个文章来看:

  1. 创建工作空间
  2. 创建第一个自己的ROS 2 包
  3. 写一个简单的发布和订阅(C++)
  4. 写一个简单的发布和订阅(python)
  5. 写一个简单的服务端和客户端(C++)
  6. 写一个简单的服务端和客户端(python)
  7. 创建自定义的ROS2 msg 和 srv 文件
  8. 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作为其构建工具。您可以使用官方支持的CMakePython创建软件包,尽管确实存在其他构建类型

Ⅱ 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 ,maintainer需要去改
在这里插入图片描述
这个样子
在这里插入图片描述

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
udo 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="[email protected]">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='[email protected]',
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
udo 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.xmlCMakeLists.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="[email protected]">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
udo 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.xmlCMakeLists.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="[email protected]">Your Name</maintainer>
<license>Apache License 2.0</license>

修改 setup.py

maintainer='Your Name',
maintainer_email='[email protected]',
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
udo 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 的工作空间
创建新的srvmsg,会通过topicservice来测试

③ 任务

Ⅰ 创建包

切换到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="[email protected]">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

通过下面命令来查看刚刚的msgsrv

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的所有方面,包括平台,版本,网络,环境,运行的系统等,并警告您可能的错误和问题原因。

还在开发中,谨慎使用,估计会有点问题

② 前提

确保安装了ros2doctorros2cli的组成部分
使用小乌龟包来测试

③ 任务

Ⅰ 检查设置

这个是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它不是调试工具;它不会帮助解决代码或系统实现方面的错误。

发布了99 篇原创文章 · 获赞 577 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/weixin_36628778/article/details/105291726