ROS2学习教程——编程基础

ROS2 算法开发编程基础

学习小鱼的ros2教程的,原文链接如下

https://blog.csdn.net/qq_27865227/article/details/131363638

  程序编译需要经过预处理、编译、汇编和链接几个步骤,在实际应用中,有些代码需要反复使用,就把这些代码编译成为库文件。在链接时链接器从库文件取得所需的代码,复制到生成的可执行文件中,这种库成为静态库,特点是可执行文件中包含了库代码的完整copy,缺点是被多次使用就会多分冗余拷贝。还有一种库是在程序开始运行后调用库函数时才被载入,这种库独立于现有的程序,本身不可执行,但包含着程序需要调用的一些函数,这种库成为动态库。

在这里插入图片描述

  这里面静态库、动态库区别来自链接阶段如何处理库,链接成可执行程序。分别成为静态链接方式、动态链接方式。其中windows中的静态链接时.lib,动态是.dll。linux中静态链接是.a,动态链接时.so文件

  这里面静态库、动态库区别来自链接阶段如何处理库,链接成可执行程序。分别成为静态链接方式、动态链接方式。其中windows中的静态链接时.lib,动态是.dll。linux中静态链接是.a,动态链接时.so文件

  我们通过rclcpp来构建一个最基础的节点

#include "rclcpp/rclcpp.hpp>

int main(int argc, char ** argv)
{
	rclcpp::init(argc, argv)
	rclcpp::spin(std::make_shared<rclcpp::Node>("first_node"));
	return 0;
}

  通过g++来编译这个节点,报一下的错误:

root@490925f19143:~/d2lros2/d2lros2/chapt2/basic# g++ first_ros2_node.cpp 
first_ros2_node.cpp:3:10: fatal error: rclcpp/rclcpp.hpp: No such file or directory
    3 | #include "rclcpp/rclcpp.hpp"
      |          ^~~~~~~~~~~~~~~~~~~
compilation terminated.

  找不到这个头文件,根本原因就是没有告诉g++头文件的位置,rclcpp的位置在/opt/ros/humble/include/rclcpp中,所以我们通过-I告诉给编译器

g++ first_ros2_node.cpp -I /opt/ros/humble/include/rclcpp/ 

  继续报错,找不到rcl/guard_condition.h

root@490925f19143:~/d2lros2/d2lros2/chapt2/basic# g++ first_ros2_node.cpp -I/opt/ros/humble/include/rclcpp/ 
In file included from /opt/ros/humble/include/rclcpp/rclcpp/executors/multi_threaded_executor.hpp:25,
                 from /opt/ros/humble/include/rclcpp/rclcpp/executors.hpp:21,
                 from /opt/ros/humble/include/rclcpp/rclcpp/rclcpp.hpp:155,
                 from first_ros2_node.cpp:3:
/opt/ros/humble/include/rclcpp/rclcpp/executor.hpp:30:10: fatal error: rcl/guard_condition.h: No such file or directory
   30 | #include "rcl/guard_condition.h"
      |          ^~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.

  所以我们再次链接一下

g++ first_ros2_node.cpp -I /opt/ros/humble/include/rclcpp/ -I /opt/ros/humble/include/rcl/

  这样继续下去,我们会得到最终的编译指令为

g++ first_ros2_node.cpp \
-I/opt/ros/humble/include/rclcpp/ \
-I /opt/ros/humble/include/rcl/ \
-I /opt/ros/humble/include/rcutils/ \
-I /opt/ros/humble/include/rmw \
-I /opt/ros/humble/include/rcl_yaml_param_parser/ \
-I /opt/ros/humble/include/rosidl_runtime_c \
-I /opt/ros/humble/include/rosidl_typesupport_interface \
-I /opt/ros/humble/include/rcpputils \
-I /opt/ros/humble/include/builtin_interfaces \
-I /opt/ros/humble/include/rosidl_runtime_cpp \
-I /opt/ros/humble/include/tracetools \
-I /opt/ros/humble/include/rcl_interfaces \
-I /opt/ros/humble/include/libstatistics_collector \
-I /opt/ros/humble/include/statistics_msgs

  再次编译,会得到不一样的报错信息,undefined reference to xxxxx,这个和头文件的不同,主要是无法找到库文件的位置,也就是像.so这样的。其实也就这两种,一个是include中的头文件,一个是lib中的库文件。最终的编译命令就变成了:

g++ first_ros2_node.cpp \
-I/opt/ros/humble/include/rclcpp/ \
-I /opt/ros/humble/include/rcl/ \
-I /opt/ros/humble/include/rcutils/ \
-I /opt/ros/humble/include/rmw \
-I /opt/ros/humble/include/rcl_yaml_param_parser/ \
-I /opt/ros/humble/include/rosidl_runtime_c \
-I /opt/ros/humble/include/rosidl_typesupport_interface \
-I /opt/ros/humble/include/rcpputils \
-I /opt/ros/humble/include/builtin_interfaces \
-I /opt/ros/humble/include/rosidl_runtime_cpp \
-I /opt/ros/humble/include/tracetools \
-I /opt/ros/humble/include/rcl_interfaces \
-I /opt/ros/humble/include/libstatistics_collector \
-I /opt/ros/humble/include/statistics_msgs \
-L /opt/ros/humble/lib/ \
-lrclcpp -lrcutils

  当然,我们可以重新命名一下名字,用-o first_node来表示

g++ first_ros2_node.cpp \
-I/opt/ros/humble/include/rclcpp/ \
-I /opt/ros/humble/include/rcl/ \
-I /opt/ros/humble/include/rcutils/ \
-I /opt/ros/humble/include/rmw \
-I /opt/ros/humble/include/rcl_yaml_param_parser/ \
-I /opt/ros/humble/include/rosidl_runtime_c \
-I /opt/ros/humble/include/rosidl_typesupport_interface \
-I /opt/ros/humble/include/rcpputils \
-I /opt/ros/humble/include/builtin_interfaces \
-I /opt/ros/humble/include/rosidl_runtime_cpp \
-I /opt/ros/humble/include/tracetools \
-I /opt/ros/humble/include/rcl_interfaces \
-I /opt/ros/humble/include/libstatistics_collector \
-I /opt/ros/humble/include/statistics_msgs \
-L /opt/ros/humble/lib/ \
-lrclcpp -lrcutils
-o first_node

  刚才说完了g++/gcc来编译的的功能,接下来学习一下通过make编译ROS2节点。make需要先编写Makefile文件,这个是make的一个说明性文件,需要将设定好的规则写到这里面,才能正常使用make的编译功能,我们创建一个Makefile如下

build:
    g++ first_ros2_node.cpp \
    -I/opt/ros/humble/include/rclcpp/ \
    -I /opt/ros/humble/include/rcl/ \
    -I /opt/ros/humble/include/rcutils/ \
    -I /opt/ros/humble/include/rmw \
    -I /opt/ros/humble/include/rcl_yaml_param_parser/ \
    -I /opt/ros/humble/include/rosidl_runtime_c \
    -I /opt/ros/humble/include/rosidl_typesupport_interface \
    -I /opt/ros/humble/include/rcpputils \
    -I /opt/ros/humble/include/builtin_interfaces \
    -I /opt/ros/humble/include/rosidl_runtime_cpp \
    -I /opt/ros/humble/include/tracetools \
    -I /opt/ros/humble/include/rcl_interfaces \
    -I /opt/ros/humble/include/libstatistics_collector \
    -I /opt/ros/humble/include/statistics_msgs \
    -L /opt/ros/humble/lib/ \
    -lrclcpp -lrcutils \
    -o first_node
clean:
    rm first_node

  也就是将之前的g++的命令进行了整合,接着我们可以通过make build(在Makefile同级目录下运行) 就可以成功编译了,用make clean就能够清理掉这些编译的结果

  接下来我们使用第三种编译方式,也就是CMakeLists.txt来进行(cmake工具),它的作用是通过CMakeLists.txt直接生成Makefile。我们新建一个CMakeLists.txt:

cmake_minimum_required(VERSION 3.22)

project(first_node)

#include_directories 添加特定的头文件搜索路径 ,相当于指定g++编译器的-I参数
include_directories(/opt/ros/humble/include/rclcpp/)
include_directories(/opt/ros/humble/include/rcl/)
include_directories(/opt/ros/humble/include/rcutils/)
include_directories(/opt/ros/humble/include/rcl_yaml_param_parser/)
include_directories(/opt/ros/humble/include/rosidl_runtime_c/)
include_directories(/opt/ros/humble/include/rosidl_typesupport_interface/)
include_directories(/opt/ros/humble/include/rcpputils/)
include_directories(/opt/ros/humble/include/builtin_interfaces/)
include_directories(/opt/ros/humble/include/rmw/)
include_directories(/opt/ros/humble/include/rosidl_runtime_cpp/)
include_directories(/opt/ros/humble/include/tracetools/)
include_directories(/opt/ros/humble/include/rcl_interfaces/)
include_directories(/opt/ros/humble/include/libstatistics_collector/)
include_directories(/opt/ros/humble/include/statistics_msgs/)

# link_directories - 向工程添加多个特定的库文件搜索路径,相当于指定g++编译器的-L参数
link_directories(/opt/ros/humble/lib/)

# add_executable - 生成first_node可执行文件 g++ first_ros2_node.cpp -o first_node 
add_executable(first_node first_ros2_node.cpp)

# target_link_libraries - 为first_node(目标) 添加需要动态链接库,相同于指定g++编译器-l参数
# 下面的语句代替 -lrclcpp -lrcutils
target_link_libraries(first_node rclcpp rcutils)

  实际编译时,我们新建一个build目录来存储编译文件,然后使用cmake ..,代表到上级目录去寻找CMakeLists.txt,然后运行make,此时就能够在build目录发现相应的节点了

  理解了其作用后,我们深入学习一下CMakeLists.txt文件,然后简化一下这个文件的书写。像之前的一大片的命令,可以简化问这样的:

cmake_minimum_required(VERSION 3.22)
project(first_node)

find_package(rclcpp REQUIRED)
add_executable(first_node first_ros2_node.cpp)
target_link_libraries(first_node rclcpp::rclcpp)

  然后也是一样的cmake ..和make。那么为什么可以这样缩减指令呢?其实就是find_package的重要作用,find_package查找的环境变量主要包含以下部分:

<package>_DIR
CMAKE_PREFIX_PATH
CMAKE_FRAMEWORK_PATH
CMAKE_APPBUNDLE_PATH
PATH

  我们输出一下系统的PATH变量:

echo $PATH

  观察PATH变量,你会发现/opt/ros/humble/bin赫然在其中,PATH中的路径如果以bin或sbin结尾,则自动回退到上一级目录,接着检查这些目录下的cmake找到这些目录后,会开始依次找Config.cmake或Find.cmake文件。找到后即可执行该文件并生成相关链接信息。打开/opt/ros/humble/share/rclcpp/cmake你会发现rclcppConfig.cmake就在其中。也就能够找到include和lib目录下的文件了

  在ros2中,python程序也是经常会遇到的,我们需要了解这里里面的依赖的搜索过程

import rclcpp
from rclpy.node import Node
rclpy.init()
rclpy.spin(Node("second_node")

  这里面的import rclcpp,是从$PYTHONPATH这个环境变量中去获取的,我们打印这个变量就可以看到/opt/ros/humble/lib/python3.10/site-packages:/opt/ros/humble/local/lib/python3.10/dist-packages 这样的内容,它包含了ros中的python的环境内容。所以对于这些内容,只需要能够在这个环境变量指向的目录获取依赖即可,如果没有,那就像里面再添加需要的即可。

  ros2中的python是需要使用编译环境来编译一次的,最主要的就是Setup打包工具,我们通过setup.py来打包程序

from setuptools import setup, find_packages

setup(
    # 指定项目名称,我们在后期打包时,这就是打包的包名称,当然打包时的名称可能还会包含下面的版本号哟~
    name="mytest",
    # 指定版本号
    version="1.0",
    author="flp",
    author_email="flepeng@163.com",
    # 这是对当前项目的一个描述
    description="这只是一次测试",

    # 项目主页
    url="http://iswbm.com/", 

    # 你要安装的包,通过 setuptools.find_packages 找到当前目录下有哪些包
    packages=find_packages()
    
    # 指定包名,即你需要打包的包名称,要实际在你本地存在哟,它会将指定包名下的所有"*.py"文件进行打包哟,但不会递归去拷贝所有的子包内容。
    # 综上所述,我们如果想要把一个包的所有"*.py"文件进行打包,应该在packages列表写下所有包的层级关系哟~这样就开源将指定包路径的所有".py"文件进行打包!
    packages=['devops', "devops.dev", "devops.ops"],
)

  参数说明如下

name	包名称
version	包版本
author	程序的作者
author_email	程序的作者的邮箱地址
maintainer	维护者
maintainer_email	维护者的邮箱地址
url	程序的官网地址
license	程序的授权信息
description	程序的简单描述
long_description	程序的详细描述
platforms	程序适用的软件平台列表
classifiers	程序的所属分类列表
keywords	程序的关键字列表
packages	需要处理的包目录(通常为包含 init.py 的文件夹)
py_modules	需要打包的 Python 单文件列表
download_url	程序的下载地址
cmdclass	添加自定义命令
package_data	指定包内需要包含的数据文件
include_package_data	自动包含包内所有受版本控制(cvs/svn/git)的数据文件
exclude_package_data	当 include_package_data 为 True 时该选项用于排除部分文件
data_files	打包时需要打包的数据文件,如图片,配置文件等
ext_modules	指定扩展模块
scripts	指定可执行脚本,安装时脚本会被安装到系统 PATH 路径下
package_dir	指定哪些目录下的文件被映射到哪个源码包
entry_points	动态发现服务和插件,下面详细讲
python_requires	指定运行时需要的Python版本
requires	指定依赖的其他包
provides	指定可以为哪些模块提供依赖
install_requires	应用于指定项目正确运行所需的最低要求
extras_require	当前包的高级/额外特性需要依赖的分发包
tests_require	在测试时需要使用的依赖包
setup_requires	指定运行 setup.py 文件本身所依赖的包
dependency_links	指定依赖包的下载地址
zip_safe	不压缩包,而是以目录的形式安装
posted @ 2024-12-25 14:55  白云千载尽  阅读(87)  评论(0)    收藏  举报  来源