ROS工作空间、功能包、节点创建 接着前面的博客我们先看下如何创建工作空间开始。一. 工作空间我们可以手动或者在终端中以命令行方式去创建工作空间类似于pycharm的一个工程文件夹visual studio解决方案所在文件夹方便我们对后面的功能包节点做整体管理。然后再执行命令colcon build可看到一个空文件夹在生成后自动生成了build、install、log文件夹。我们也可以用命令行方式去创建文件夹mkdir -p DemoWorkspace然后我们还是继续命令colcon build可看到一个工作空间就创建完毕了。注意我们还需在这个文件夹下手动创建src文件夹用来存放一些后面要写的代码脚本需手动存放到此。后面我们所有工作都在DemoWorkspace工作空间下进行。build文件夹是编译过程中产生的中间临时文件几乎不用管install文件夹是编译后真正能用的文件ROS2 运行时加载的就是这里的文件里面包含可执行文件节点、命令库文件.so/.dll配置文件、消息 / 服务定义启动文件launch环境脚本setup.bashlog文件夹是编译日志文件方便排查问题几乎不用管一个工作空间中可以有一堆包每个包可以按照不同功能来区分所有包都必须直接放在src文件夹下面并列排布不可以嵌套。执行一次colcon build 能够对所有包进行编译。一个功能包中可以写任意数量的可执行文件exe, 每个exe又能单/多节点二. 功能包1. 我们创建一个python示例包1终端执行如下语句ros2 pkg create pythonpackagedemo1 --build-type ament_python --dependencies rclpy --node-name pythonexecute1执行后可看到包已经创建ros2 pkg create是官方命令用于创建新的功能包--dependencies rcply 声明依赖rcply是ros2的python客户端库必须依赖用于编写节点--dependencies rclpy声明依赖rclpy是 ROS2 的 Python 客户端库必须依赖用于编写节点。2. 我们再创建一个C版本的功能包执行如下语句ros2 pkg create cpp_packagedemo1 --build-type ament_cmake --dependencies rclcpp --node-name cppexecute1执行后可看到包已经被创建三. 节点接下来我们要去在各包中去创建程序在程序中实现具体的功能。1. 以前面创建的第一个python包为例, 我们用pycharm打开如下自动创建的文件pythonexecute1.py将其中代码改为如下import rclpy # ROS2 Python接口库 from rclpy.node import Node # ROS2 节点类 import time 创建一个pyHelloNode1节点 class pyHelloNode1(Node): def __init__(self, name): super().__init__(name) # ROS2节点父类初始化 while rclpy.ok(): # ROS2系统是否正常运行 self.get_logger().info(Hello python node1) # ROS2日志输出 time.sleep(1) # 休眠控制循环时间 def main(argsNone): # ROS2节点主入口main函数 rclpy.init(argsargs) # ROS2 Python接口初始化 node pyHelloNode1(Hello_python_node1) # 创建ROS2节点对象并进行初始化 rclpy.spin(node) # 循环等待ROS2退出 node.destroy_node() # 销毁节点对象 rclpy.shutdown() # 关闭ROS2 Python接口可看到在这个Py文件中仅创建了一个节点。如果想要在此包下增加一个可执行文件则可以在这个目录下再手动添加一个py文件pythonexecute2_includetwonode_parallel.py然后进行编辑。#!/usr/bin/env python3 import rclpy from rclpy.node import Node from rclpy.executors import MultiThreadedExecutor # 节点 1 class Node1(Node): def __init__(self): super().__init__(node1) self.create_timer(1.0, self.callback) def callback(self): self.get_logger().info(节点 1 运行中) # 节点 2 class Node2(Node): def __init__(self): super().__init__(node2) self.create_timer(1.0, self.callback) def callback(self): self.get_logger().info(节点 2 运行中) # 主函数同时运行 2 个节点 def main(argsNone): rclpy.init(argsargs) node1 Node1() node2 Node2() # 多线程让两个节点同时跑 executor MultiThreadedExecutor() executor.add_node(node1) executor.add_node(node2) try: executor.spin() finally: executor.shutdown() node1.destroy_node() node2.destroy_node() rclpy.shutdown() if __name__ __main__: main()在这个目录下再手动添加一个py文件pythonexecute2_includetwonode_sequence.py然后进行编辑。#!/usr/bin/env python3 import rclpy from rclpy.node import Node from rclpy.executors import MultiThreadedExecutor # 节点 1 class Node1(Node): def __init__(self): super().__init__(node1) self.create_timer(1.0, self.callback) def callback(self): self.get_logger().info(节点 1 运行中) # 节点 2 class Node3(Node): def __init__(self): super().__init__(node3) self.create_timer(1.0, self.callback) def callback(self): self.get_logger().info(节点 3 运行中) # 主函数同时运行 2 个节点 def main(argsNone): rclpy.init(argsargs) # 串行执行先运行节点1 node1 Node1() print(开始运行节点1运行3秒后自动关闭) rclpy.spin_once(node1, timeout_sec3.0) # 运行3秒 node1.destroy_node() # 再运行节点2 node3 Node3() print(开始运行节点3运行3秒后自动关闭) rclpy.spin_once(node3, timeout_sec3.0) # 运行3秒 node3.destroy_node() # 以上是自动串行适合自动化、批处理任务 # 以下是手动串行适合调试、分步执行 # 运行节点1 node1 Node1() print( 节点1 运行中按 CtrlC 停止节点1 ) try: rclpy.spin(node1) except KeyboardInterrupt: pass node1.destroy_node() # 运行节点2 node3 Node3() print(\n 节点3 运行中按 CtrlC 停止节点3 ) try: rclpy.spin(node3) except KeyboardInterrupt: pass node3.destroy_node() # 关闭 rclpy.shutdown() if __name__ __main__: main()这几个文件在文件夹中的目录如下接下里要在自动生成的setup.py里注册这两个新可执行文件博主将setup.py文件修改成如下from setuptools import find_packages, setup package_name pythonpackagedemo1 setup( namepackage_name, version0.0.0, packagesfind_packages(exclude[test]), data_files[ (share/ament_index/resource_index/packages, [resource/ package_name]), (share/ package_name, [package.xml]), ], install_requires[setuptools], zip_safeTrue, maintainerjetson, maintainer_emailyahboom168.com, descriptionTODO: Package description, licenseTODO: License declaration, extras_require{ test: [ pytest, ], }, entry_points{ console_scripts: [ pythonexecute_onenode pythonpackagedemo1.pythonexecute1:main, pythonexecute_twonodeparallel pythonpackagedemo1.pythonexecute2_includetwonode_parallel:main, pythonexecute_twonodesequence pythonpackagedemo1.pythonexecute2_includetwonode_sequence:main, ], }, )2. 我们接下来再去配置下上面用c方式创建的包中节点修改cppexecute1.cpp中代码如下#include rclcpp/rclcpp.hpp // 节点 1 class Node1 : public rclcpp::Node { public: Node1() : Node(cpp_node1) { // 创建 1s 定时器 timer_ this-create_wall_timer( std::chrono::seconds(1), std::bind(Node1::callback, this)); RCLCPP_INFO(this-get_logger(), cpp节点1 已启动); } private: void callback() { RCLCPP_INFO(this-get_logger(), cpp节点1 运行中...); } rclcpp::TimerBase::SharedPtr timer_; }; // 节点 2 class Node2 : public rclcpp::Node { public: Node2() : Node(cppnode2) { timer_ this-create_wall_timer( std::chrono::seconds(1), std::bind(Node2::callback, this)); RCLCPP_INFO(this-get_logger(), cpp节点2 已启动); } private: void callback() { RCLCPP_INFO(this-get_logger(), cpp节点2 运行中...); } rclcpp::TimerBase::SharedPtr timer_; }; // 主函数 int main(int argc, char * argv[]) { rclcpp::init(argc, argv); // 同时运行两个节点并行 auto node1 std::make_sharedNode1(); auto node2 std::make_sharedNode2(); // 多线程执行器和 Python 的 MultiThreadedExecutor 一样 rclcpp::executors::MultiThreadedExecutor executor; executor.add_node(node1); executor.add_node(node2); executor.spin(); // 启动 rclcpp::shutdown(); return 0; }CMakeLists.txt中语句中语句如下cmake_minimum_required(VERSION 3.8) project(cpp_packagedemo1) 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) add_executable(cppexecute1 src/cppexecute1.cpp) target_include_directories(cppexecute1 PUBLIC $BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include $INSTALL_INTERFACE:include) target_compile_features(cppexecute1 PUBLIC c_std_99 cxx_std_17) # Require C99 and C17 ament_target_dependencies( cppexecute1 rclcpp ) install(TARGETS cppexecute1 DESTINATION lib/${PROJECT_NAME}) if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) # the following line skips the linter which checks for copyrights # comment the line when a copyright and license is added to all source files set(ament_cmake_copyright_FOUND TRUE) # the following line skips cpplint (only works in a git repo) # comment the line when this package is in a git repo and when # a copyright and license is added to all source files set(ament_cmake_cpplint_FOUND TRUE) ament_lint_auto_find_test_dependencies() endif() ament_package()后面博主主要以python方式作为后续的演示。3. 编译功能包以生成可执行文件如果指定仅编译某个包可执行如下语句colcon build --packages-select cpp_packagedemo1可看到此时install文件夹下只有一个cpp的可执行文件可在终端运行该可执行程序执行效果如下按ctrl c即可停止运行。执行如下语句即可以对工作空间里的所有包进行编译colcon build即可对完成对所有库的编译可看到python版本的可执行文件其实是py脚本我们人选其中一个打开看下。#!/usr/bin/python3 # EASY-INSTALL-ENTRY-SCRIPT: pythonpackagedemo10.0.0,console_scripts,pythonexecute_onenode import re import sys # for compatibility with easy_install; see #2198 __requires__ pythonpackagedemo10.0.0 try: from importlib.metadata import distribution except ImportError: try: from importlib_metadata import distribution except ImportError: from pkg_resources import load_entry_point def importlib_load_entry_point(spec, group, name): dist_name, _, _ spec.partition() matches ( entry_point for entry_point in distribution(dist_name).entry_points if entry_point.group group and entry_point.name name ) return next(matches).load() globals().setdefault(load_entry_point, importlib_load_entry_point) if __name__ __main__: sys.argv[0] re.sub(r(-script\.pyw?|\.exe)?$, , sys.argv[0]) sys.exit(load_entry_point(pythonpackagedemo10.0.0, console_scripts, pythonexecute_onenode)())注意colcon build过程并不会去清空install文件夹内内容再重新生成如下可看到pythonexecute_onenode仍存在。注节点可看成是类对象节点自己不是可执行程序不会自己运行起来其是存在于可执行程序中。很多同学都会混淆这边博主要说明下能运行的只有可执行程序不是节点。节点是程序运行后在内存里创建的对象 / 实例运行可执行程序 → 程序内部创建节点 → 节点才活了节点是运行后的产物不能直接运行四. 设置环境变量编译成功后为了让系统能够找到我们的功能包和可执行文件还需要设置环境变量。ROS2 需要环境变量来找到自己的组件、你的功能包、以及运行所需的全部配置不设置环境变量ROS2 根本无法正常工作。它能告诉系统ROS2 的可执行文件在哪里ROS2 的库文件在哪里你的功能包放在哪里使用哪个版本的 ROS2如 Humble / Iron没有这个 “导航地图”系统就会报错command not found或package not found。1. 若想仅在当前终端生临时效能够找到可执行文件的位置则执行如下命令source install/setup.bash没有执行如上命令之前我们使用如下命令ros2 pkg executables查不到刚生成的可执行文件一旦执行了source install/setup.bash 命令再去查找时便能找到了但此时重新打开一个终端执行如上查找可执行程序命令发现又找不到了2. 若避免每次打开新终端后都要去更新一下想要永久生效可执行如下命令在里面将包的安装文件夹路径填进去别忘了执行如下命令以生效。source ~/.bashrc后面每次再打开新终端执行查找可执行程序命令时就能查找带了不必每个新终端都要去source install/setup.bash五. 运行可执行程序以及一些常用的ROS2查找功能语句1. 例举ROS2已安装或编译的包ros2 pkg list2. 查指定包的可执行程序ros2 pkg executables 包名3. 查所有可执行程序ros2 pkg executables4. 查看正在运行的节点ros2 node list5. 运行节点ros2 run 包名 可执行程序名6. 若想按顺序运行某个包里的所有节点执行如下命令for exe in $(ros2 pkg executables 包名); do ros2 run 包名 $exe; done这边运行下如上几个包中的可执行程序看下效果下一篇博客我们再继续深入此篇暂就到这儿。