前言

主要学习怎么使用客户端库来实现自己的功能,主要介绍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 学习 ,可以从以下几个网站学习

https://github.com/ros/ros_tutorials
https://github.com/ros2/examples
https://github.com/ros2/demos
这边文章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.xmldescription, 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下
    • 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它不是调试工具;它不会帮助解决代码或系统实现方面的错误。