通过开发板来学习ROS2 21讲(功能包创建与节点案例)

上一节已经和大家分享了如何在板端配置环境,这一节开始将和大家逐步聊到ros2中关键的一些点。

参考资料

ROS2 21讲视频 :【古月居】古月·ROS2入门21讲 | 带你认识一个全新的机器人操作系统_哔哩哔哩_bilibili

ROS2 21讲图文文档:ROS2入门教程 (guyuehome.com)

环境说明

  • RDK X3/OriginBot,镜像为OriginBot v2.0.2

  • PC端为Windows 11

功能包

关于功能包的创建与说明,在ROS2 21讲已经说的很清楚了,我在此处不过多赘述了,请大家参考 “核心概念/功能包”一讲 https://book.guyuehome.com/ROS2/2.%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5/2.2_%E5%8A%9F%E8%83%BD%E5%8C%85/

节点

在(节点 - ROS2入门教程 (guyuehome.com))这一节的文档中给大家介绍了很多案例,此处我想给大家补充关于C++代码的实现,并贴上文档中的代码作为对比和使用差异说明。

案例一:Hello World节点(面向过程)

这个案例演示了如何通过python以面向过程的形式来实现每隔0.5s打印一次Hello World.

python版本

代码如下:

#!/usr/bin/env python3 
# -*- coding: utf-8 -*-

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2节点示例-发布“Hello World”日志信息, 使用面向过程的实现方式
"""

import rclpy                                     # ROS2 Python接口库
from rclpy.node import Node                      # ROS2 节点类
import time

def main(args=None):                             # ROS2节点主入口main函数
    rclpy.init(args=args)                        # ROS2 Python接口初始化
    node = Node("node_helloworld")               # 创建ROS2节点对象并进行初始化

    while rclpy.ok():                            # ROS2系统是否正常运行
        node.get_logger().info("Hello World")    # ROS2日志输出
        time.sleep(0.5)                          # 休眠控制循环时间

    node.destroy_node()                          # 销毁节点对象    
    rclpy.shutdown()                             # 关闭ROS2 Python接口

大家可以在RDK X3中运行,效果如下图

这里大家会碰到一个问题,当按下ctrl+C推出时会遇到KeyboardInterrupr的报错,显然是小问题了,但是有没有办法解决呢?肯定由的,只需要加上try/expect机制即可啦。

#!/usr/bin/env python3 
# -*- coding: utf-8 -*-

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2节点示例-发布“Hello World”日志信息, 使用面向过程的实现方式
"""

import rclpy                                     # ROS2 Python接口库
from rclpy.node import Node                      # ROS2 节点类
import time

def main(args=None):                             # ROS2节点主入口main函数
    rclpy.init(args=args)                        # ROS2 Python接口初始化
    node = Node("node_helloworld")               # 创建ROS2节点对象并进行初始化
    try:
        while rclpy.ok():                            # ROS2系统是否正常运行
            node.get_logger().info("Hello World")    # ROS2日志输出
            time.sleep(0.5)                          # 休眠控制循环时间
    except:
        pass

    node.destroy_node()                          # 销毁节点对象    
    rclpy.shutdown()                             # 关闭ROS2 Python接口

增加三行代码,然后在终端编译再执行即可。

编译使用我们上节使用的单独编译即可

colcon build --packages-select learning_node

那么ros2是怎么判断如何使用这个节点名称的呢?可以看到setup.py文件

    entry_points={
        'console_scripts': [
         'node_helloworld       = learning_node.node_helloworld:main',
         'node_helloworld_class = learning_node.node_helloworld_class:main',
         'node_object            = learning_node.node_object:main',
         'node_object_webcam     = learning_node.node_object_webcam:main',
        ],
    },

其中 ‘node_helloworld = learning_node.node_helloworld:main‘ 中node_helloworld标识节点名,learning_node.node_helloworld:main表示执行路径。

只需在此文件中声明节点名和路径即可。

C++版本

首先需要创建一个新的功能包,取名为 learning_node_c 或者 learning_node_cpp 都可以,我们此处兼容功能包名称,取名为learning_node_c

#此处不增加rclcpp依赖
ros2 pkg create --build-type ament_cmake learning_node_c
#增加rclcpp依赖
ros2 pkg create --build-type ament_cmake learning_node_c --dependencies rclcpp

在 learningd_node_c/src 中新建一个node_helloworld.cpp文件,并在其中填入以下内容

#include "rclcpp/rclcpp.hpp"


int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);
    auto helloWorldNode = std::make_shared<rclcpp::Node>("node_helloworld");
    while (rclcpp::ok()) {
        RCLCPP_INFO(rclcpp::get_logger("node_helloworld"), "Hello World");
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    // spin代表循环执行helloWorldNode节点
    // rclcpp::spin(helloWorldNode);
    rclcpp::shutdown();
    return 0;
}

如何给节点名并运行呢?

  1. 配置rclcpp依赖,此处需要在CMakelist.txt和package.xml文件中填入相关说明

  2. 在CMakelist.txt文件中说明节点名

具体如下,可看中文说明

cmake_minimum_required(VERSION 3.5)
project(learning_node_c)

# 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)
#rclcpp依赖说明
find_package(rclcpp REQUIRED)

# Add the executable
#增加节点说明,node_helloworld_c相当于别名
add_executable(node_helloworld_c src/node_helloworld.cpp)
#给执行node以依赖
ament_target_dependencies(node_helloworld_c rclcpp)

# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)

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 the executable
#真正编译至install中,变成可执行文件
install(TARGETS
  node_helloworld_c
  DESTINATION lib/${PROJECT_NAME}
)

ament_package()

运行结果如下:

案例二:Hello World节点(面向对象)

面向对象确实是更为常见的代码风格,也更符合C++/Python的设计结构

Python版本

实现代码如下

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
@作者: 古月居(www.guyuehome.com)
@说明: ROS2节点示例-发布“Hello World”日志信息, 使用面向对象的实现方式
"""

import rclpy                                     # ROS2 Python接口库
from rclpy.node import Node                      # ROS2 节点类
import time

"""
创建一个HelloWorld节点, 初始化时输出“hello world”日志
"""
class HelloWorldNode(Node):
    def __init__(self, name):
        super().__init__(name)                       # ROS2节点父类初始化
        try:
            while rclpy.ok():                            # ROS2系统是否正常运行
                self.get_logger().info("Hello World")    # ROS2日志输出
                time.sleep(0.5)                          # 休眠控制循环时间
        except:
            pass

def main(args=None):                                 # ROS2节点主入口main函数
    rclpy.init(args=args)                            # ROS2 Python接口初始化
    node = HelloWorldNode("node_helloworld_class")   # 创建ROS2节点对象并进行初始化
    node.destroy_node()                              # 销毁节点对象
    rclpy.shutdown()                                 # 关闭ROS2 Python接口

setup.py同样设置了 ‘node_helloworld_class = learning_node.node_helloworld_class:main’, 此处不过多赘述

C++版本

实现代码如下:

#include "rclcpp/rclcpp.hpp"

class HelloWorldClassNode : public rclcpp::Node
{
public:
    HelloWorldClassNode() : Node("node_helloworld_class") {
        timer_ = create_wall_timer(
            std::chrono::milliseconds(500),
            [this]() {
                RCLCPP_INFO(get_logger(), "Hello World");
            }
        );
    }

private:
    rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char **argv)
{
    rclcpp::init(argc, argv);

    auto helloWorldClassNode = std::make_shared<HelloWorldClassNode>();

    rclcpp::spin(helloWorldClassNode);

    rclcpp::shutdown();
    return 0;
}

CMakelist.txt中增加以下内容

add_executable(node_helloworld_class_c src/node_helloworld_class.cpp)
ament_target_dependencies(node_helloworld_class_c rclcpp)
install(TARGETS
  node_helloworld_class_c
  DESTINATION lib/${PROJECT_NAME}
)