ROS2基础入门:节点、功能包、工作空间与其编译运行

ROS2基础入门:节点、功能包、工作空间与其编译运行

编码文章call10242025-07-19 22:12:505A+A-

在之前的两篇文章中,我们已经深入探讨了“ROS仿真三幻神,Gazebo、RViz2、RQt”和“TF坐标系管理与可视化工具”,相信各位读者对ROS2已经有了初步的认识和理解。那么,今天我们将继续深入这一领域,为大家带来关于ROS2创建、编译以及运行一个节点的必备知识,同时还会涵盖使用Launch进行节点批量启动的相关内容。(原文链接:https://www.eeworld.com.cn/a0ufTO8

注意:命令行代码默认项目已经source install/setup.bash执行的工作目录为工作空间my_project


从创建一个节点开始



ROS2将节点-Node视为机器人的细胞,每个节点都是一个独立的可执行文件/功能部件比如对于一个摄像头节点,他的职能就是拍摄图片发布到数据空间-DataBus之后,需要这些数据的节点,比如显示屏节点,或者实体分割节点会订阅这些数据进行显示或处理。在ROS2中,每个节点独占一个进程,且由于ROS2的节点管理是基于哈希表名称映射的,所以每个节点必须有一个唯一的名称。

工作空间

工作空间-WorkSpace就类比一个项目目录-ProjectDir或者一个工作台,或者一个工具箱。如果拿其他技术栈来比喻:

ROS2

前端页面

后端服务

游戏开发

建模渲染

智能项目

工作空间

Project

Project

Project

Project

工作空间

功能包

View

App/Service

场景

渲染链

Notebook

节点

Component

Router/API

单个脚本材质贴图等

合成器节点

单模型

即:节点可以独立存在,且可以独立运行,但是得借由多个节点组成的包才是一个完整的系统,一个包可以有一个或多个节点,一个工作空间涵盖了多个包以及其之下的节点。

创建一个工作空间就是新建一个存放此新工程文件的文件夹:

mkdir -p my_project/src    # 之后的命令行操作默认在my_project目录

如果是新工程,则可以colcon build --packages-select=false初始化此my_project工作空间,初始化会创建空的install, build, log目录,并设置初始的install/setup.bash加载此bash环境后,ros2命令将会额外检测此工作空间下定义的消息、节点等等。

source install/setup.bash

如果是旧工程,可以使用ROS2自带的rosdep命令自动安装src代码空间中各功能包的依赖库:

rosdep install --from-paths src --ignore-src -r -y

功能包

一个功能包-Package一般是一个可独立运行的完整组件,内含有一个或者多个节点,比如移动控制、视觉感知、自主导航等等,将完整机器人分割为功能包使用了解耦的思想,功能包可以独立方向至其他机器人项目。

# 创建功能包

ros2 pkg create --build-type <build-type> <package-name>

    build-type: ament_cmake | ament_python,根据适合/喜欢的语言进行选择

    package-name: 功能包名,即src文件夹下将会新建什么名字的文件夹(功能包)

# 比如创建一个位于 my_project/src 下的使用Python风格API的 my_package:

ros2 pkg create --build-type ament_python my_package

一个功能包含有一个描述元信息的package.xml包括包的版权、作者信息、开发日期、功能描述等等。另外还有一个本地化包含有的包构建配置文件,比如Cmake包含有的CMakeLists.txtPython包含有的setup.py其中使用方式与各语言通用包构建发布的配置一致。

节点

节点是一个单独的可执行文件,其中无论是C++还是Py,此文件将会启动一个Ros::Node对象,比如:

from rcipy.node import Node



if __name__ == '__main__':

# 一定注意:节点名称得保证唯一性

    node = Node("node_name")

# 循环等待ROS退出

    rclpy.spin(node)

# 退出后将会销毁节点实例,关闭ROS接口

    node.destory_node()

    rclpy.shutdown()
#include <rclcpp/rclcpp.hpp>



int main(int argc, char* argv[]) {

rclcpp::init(argc, argv);

auto node = rclcpp::Node("node_name");



rclcpp::spin(std::shared_ptr(node));

rclcpp::shutdown();

return0;

}

但是,启动一个空节点没有太大的实际意义,所以一般启动都是实现特定功能的节点子类:

classMyNode(rclpy.Node):

def__init__(self, name: str, ...):

        super.__init__(name)

        ...

    ...
class MyNode: public rclcpp::Node

{

public:

MyNode(conststring &name, ...): Node(name)

        {

            ...

        }

        ...

}


编译运行



启动节点不是单纯的运行写好的节点文件,而是需要合理配置编译选项后,编译,通过ros命令来启动:

ros2 run <工作空间名字><节点启动名>

其中需要设置的编译选项:

## Python: setup.py

...

    entry_points = {        # 设置程序入口

'console_scripts': [

# 格式为:`节点启动名 = 相对于工作空间下的main所在位置-my_node.py`

"node_class_name = my_package.my_node:main"

# 此时启动此节点:ros2 run my_project node_class_name

        ],

    }

...
// Cpp: CMakeLists.txt

...

# 查找依赖的功能包 rclcpp 提供ROS2 C++的基础接口

find_package(rclcpp REQUIRED)



# 添加一个可执行文件,节点启动名字为`emm`,对应源码src/my_node.cpp

add_executable(emm src/my_node.cpp)

# 配置编译时需要链接的库,比如 rclcpp 库:

ament_target_dependencies(emm rclcpp)



# 将编译生成的可执行文件`emm`拷贝到`install/lib`:

install(

    TARGETS

    emm

    DESTINATION lib/${PROJECT_NAME}

)



# 编译后将可以这样运行此节点:ros2 run my_project emm

...

编译操作:

colcon build    # 默认编译,直接编译此工作目录下的所有包的所有节点

colcon build --packages-select <package_name>    # 只编译指定功能包

colcon build --packages-up-to <package_name>    # 编译指定功能包及其依赖

清除编译:

# 直接了当删除除了源码的目录即可

rm -rf install/ build/ log/


到批量启动多个节点



前情提要:节点是一个独立进程 =意味着=> 每启动一个节点都会占用一个shell除非nohup后台启动或者使用容器等等虚拟化手段启动。当你需要启动的节点大于5个时,你会发现tmux也会变得不好使,那么如何使用一个命令行启动一键多个节点?且可以不用每次启动都麻烦的配置每个节点的启动配置?哈哈,ROS人有自己的Makefile:Launch

Launch

Launch,多节点启动与配置脚本,官方仓库Github官方说明design.ros2.orgLaunch启动文件使用Python进行描述,可以配置命令行输入的各项参数,并同时使用Python原有的编程功能。注意:Launch的功能是利用模板生成命令行命令,类似于模板引擎展开为界面代码一样,并不影响节点源码的行为!但是可以通过条件判断来在生成命令的同时更改命令行参数(类似于条件编译),或者使用循环批量生成大量的节点进行启动。

基础使用示例

import os

from launch import LaunchDescription

# 注意:这里的Node是用来生成模板命令字符串的,而不是ros2用于启动的Node

from launch_ros.actions import Node

# 下面导入的这个方法用来查询功能包路径

from ament_index_python.packages import get_package_share_directory



defgenerate_launch_description():

return LaunchDescription([

# 启动第一个Python节点,相当于:ros2 run my_py_package1 my_node1

# 但是指定了别名:my_node1

        Node(

package='my_py_package1',

            executable='my_node1',

            name='my_node1',

            output='screen'

        ),

# 启动第二个Python节点

        Node(

package='my_py_package1',

            executable='my_node2',

            name='my_node2',

            output='screen'

        ),

# 使用循环生成3个节点

        *[Node(

package='my_cpp_package',

            executable='my_node3',

            name=f'cpp_node{i}',

            output='screen'

        ) for i in range(3)],

    ])

Launch的编译和启动:

Launch文件在使用前需要移动至install但是直接在此文件夹下创建launch.py文件,在清理编译文件时会一不小心全删掉,所以一般放在功能包-Package目录下的launch文件夹下,但是如果图省事直接放于功能包-Package根目录,则常习惯命名为xxx.launch.py来表示这是一个launch描述文件,之后配置相应的编译指导文件,将其作为资源文件直接拷贝到install/share即可。

## Python: setup.py

...

data_files = [

# data file将会在编译时被原样拷贝到lib

    (

        os.path.join('share', package_name, 'launch'),

glob(os.path.join('launch', '*.launch.py'))

    ), ...

]

...
// Cpp: CMakeLists.txt

...

install(DIRECTORY

    launch

    DESTINATION share/${PROJECT_NAME}/

)

...
# Launch命令行操作,在编译-build后:

ros2 launch <工作空间名称><launch文件名># eg. ros2 launch my_project emm.launch.py


常用描述方法



综上所述,一个Launch启动文件可以用以下模板描述:

defgenerate_launch_description():

return LaunchDescription([ ... ])



# 即:在文件中定义 `generate_launch_description` 函数,返回启动文件的描述类

其中常用的描述方法有:

from launch import LaunchDescription

from launch.actions import (

    GroupAction,

    IncludeLaunchDescription,

)

from launch_ros.actions import (

    Node,

    PushRosNamespace,

)

from launch.launch_description_sources import PythonLaunchDescriptionSource

from ament_index_python.packages import get_package_share_directory



defgenerate_launch_description():

# 普通的加载此工作空间下节点示例:

    node1 = Node(

        package='my_py_package1',

        executable='my_node1',

    )



# 当某些节点已经被另外一个Launch定义的时候,可以直接加载此Launch定义的节点

    others = IncludeLaunchDescription(

        PythonLaunchDescriptionSource([

            os.path.join(

# get_package_share_directory可以获取环境包所在的目录

                get_package_share_directory("环境中的其他包的包名"),

# 习惯将launch文件放于launch之下,且名字为*.launch.py

"launch", "xxx.launch.py",

            )

        ])

    )



# 通常无法保证外部Launch不与当前的名称冲突,所以常常给外部Launch套一层命名空间:

    other2 = IncludeLaunchDescription(

        PythonLaunchDescriptionSource([

            os.path.join(

                get_package_share_directory("环境中的其他包的包名"),

"launch", "xxx.launch.py",

            )

        ])

    )



    other2_with_ns = GroupAction(

        actions=[

            PushRosNamespace('namespace_233'),

            other2,

        ]

    )



# 最后返回集合后的描述(如果仅仅一个Node,直接返回Node也可)

return LaunchDescription([node1, others, other2_with_ns])

常用参数配置

Node(

    package="节点所在的功能包",

    executable="编译后的可执行文件名",

    namespace="节点所在命名空间",

    name="对节点进行重命名",

    parameters=[...],        # 加载参数 / 参数文件路径

# 资源重映射,比如将话题名为"/emm"映射到话题名为"/asd/qwe"

    remappings=[("原资源路径", "映射资源路径"), ("/emm", "/asd/qwe"), ...],

arguments=['-d', '命令行参数'],

)

使用参数-Parameters:

# 参数指定的可以是yaml文件的路径,也可以是Launch使用DeclareLaunchArgument定义的参数:

from launch.actions import DeclareLaunchArgument

from launch.substitutions import LaunchConfiguration, TextSubstitution



defgenerate_launch_description():

# 以初始的海龟的背景颜色参数为示例

    background_r_arg = DeclareLaunchArgument(

'background_r', default_value=TextSubstitution(text='0')

    )



"""假设剩余的background_g和background_b参数在config/emm.yaml文件:

    /turtlesim2/sim:

        ros__parameters:

background_g: 0

background_b: 0

"""



    config_gb_yaml_path = os.path.join(

        get_package_share_directory("my_project"),

'config', 'emm.yaml'

    )



return LaunchDescription([

        background_r_arg,        # 定义的参数得加入列表才能使用

        Node(

            ...,

            parameters=[

                config_gb_yaml_path,    # yaml文件路径形式加载参数

                {

# 使用之前Launch文件内定义的参数

'background_r': LaunchConfiguration('background_r')

                },

            ]

        ),

    ])


从创建一个简单的节点,到构建功能包和工作空间,再到使用Launch批量启动多个节点,每一步都至关重要。希望这篇文章能帮助你在ROS2开发的道路上迈出坚实的一步。如果你在学习过程中有任何疑问,欢迎留言或点击阅读原文随时交流探讨。让我们一起在机器人开发的世界中不断探索和进步!

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4