通过开发板来学习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;
}
如何给节点名并运行呢?
-
配置rclcpp依赖,此处需要在CMakelist.txt和package.xml文件中填入相关说明
-
在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}
)