ROS2环境搭建

为什么要学 Ros2?因为公司要用,不学会被裁员。开个玩笑,因为项目中要用到。

本文档仅适合 Ubuntu24.04 桌面版

ros2 简介

  • 核心概念
    • 节点:执行计算的过程
    • 话题:发布/订阅模式的通信管道
    • 服务:请求/响应模式的通信
    • 动作:长时间运行的任务接口
    • 参数:可配置的节点设置
  • ros1 和 ros2 的主要区别
    • 架构:ros2 使用 DDS 代替了 ros1 的自定义通信层
    • 实时性:ros2 设计开绿了实时系统需求
    • 跨平台:ros2 原生支持 Windows
    • 安全性:ros2 内置了安全功能

环境搭建

设置编码格式

ros2 需要设置 UTF-8编码格式

sudo apt update && sudo apt install locales
sudo local-gen en_US en_US.UTF-8
sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8

export LANG=en_US.UTF-8

添加软件源

Ubuntu24.04 通常默认启动 Universe 存储库

sudo apt install software-properties-common
sudo add-apt-repository universe
sudo apt update

安装必要的工具

sudo apt install curl -y

设置 ros2 软件源:

  • 下载 ros2 的 GPG 秘钥并添加到系统:
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key  -o /usr/share/keyrings/ros-archive-keyring.gpg
  • 将 ros2 的软件源添加到系统的源列表中:
# 优先使用清华的源,后续安装ros2时更快,ros2需要3GB的下载
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] https://mirrors.tuna.tsinghua.edu.cn/ros2/ubuntu noble main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

# 官方源(用了清华源这里就不需要了),官方源下载很慢。
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/rosarchive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null

安装 ros2

首先更新升级 apt 代码仓库:

sudo apt update
sudo apt upgrade

然后可以开始安装 Desktop版本,里面包含了 Ros、RViz、demos 和教程。

sudo apt install ros-jazzy-desktop

设置环境变量

  • 每次打开终端时,都需要设置 ros2 的环境变量,可以通过如下命令设置:
source /opt/ros/jazzy/setup.bash
  • 为了避免每次都要手动设置,可以将此命令添加到 ~/.bashrc中:
echo "source /opt/ros/jazzy/setup.bash" >> ~/.bashrc

demo 验证

打开一个终端,输入以下命令配置环境,并打开一个 C++talker

ros2 run demo_nodes_cpp talker

你会一直看到 talker 在发布消息,并输出日志:

另外打开一个终端,启动一个 python listener:

ros2 run demo_nodes_py listener

如果可以成功收发,那么 ROS2 就安装成功了!

ros2 机器人开发基础

开发流程

  1. 创建工作空间
  2. 创建功能包
  3. 编写源代码
  4. 编译和调试
  5. 功能运行

创建工作空间

工作空间是 ros2 项目的根目录,用于组织和管理多个功能包。

  • **目的:**为 ros2 项目提供统一的开发环境
  • **结构:**通常包含 src/build/install/log/等目录
  • 命令示例:
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws

创建功能包

功能包时 ros2 的基本代码组织单元,包含节点、消息和服务等。

  • **目的:**模块开发,便于代码复用和维护
  • **包含内容:**源代码,配置文件,依赖关系等
  • 命令示例:
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python my_package

编写源代码

实现具体的机器人功能逻辑

  • 主要内容:
    • 节点:ros2 的基本执行单元
    • 话题:异步通信机制
    • 服务:同步通信机制
    • 动作:长时间运行的任务
  • **编程语言:**主要支持 Python 和 C++

设置编译规则

配置项目的编译和依赖关系

  • 主要文件:
    • package.xml:包的元信息和依赖声明
    • setup.pyCMakeLists.txt:编译配置信息
  • **功能:**定义依赖项、编译选项、安装规则等

编译和调试

将源代码编译成可执行文件并进行测试

  • 编译命令:
cd ~/ros2_ws
colcon build
  • 调试工具:
    • 日志系统
    • RQT 工具
    • 单元测试框架

功能运行

启动和运行已开发的 ros2 节点和系统

  • 运行步骤:
    • 设置环境变量:source ~/ros2_ws/install/setup.bash
    • 启动节点:ros2 run package_name node_name
    • 使用 launch 文件启动多个节点
  • **监控工具:**RQT,rviz2 等可视化工具

这个开发流程是循环迭代的,在实际开发中经常需要在不同阶段之间切换,持续改进和优化机器人系统的功能。

工作空间

工作空间是什么

定义和概念

  • 工作空间:是一个包含ROS2功能包的目录结构,用于组织、构建和管理ROS2项目
  • 作用:提供一个独立的开发环境,隔离不同项目的依赖和配置

目录结构

经典的工作空间结构

ros2_ws/ 														# 工作空间根目录
├── src/ 														# 源代码目录
│ ├── package1/ 										# 功能包1(Python包示例)
│ │ ├── package1/ 									# Python模块目录
│ │ │ ├── __init__.py 							# Python包初始化文件
│ │ │ ├── node1.py 									# 节点源代码
│ │ │ └── module1.py 								# 自定义模块
│ │ ├── resource/ 									# 资源文件目录
│ │ │ └── package1 									# 包标识文件
│ │ ├── test/ 											# 测试文件目录
│ │ │ ├── test_copyright.py 				# 版权测试
│ │ │ ├── test_flake8.py 						# 代码风格测试
│ │ │ └── test_pep257.py 						# 文档字符串测试
│ │ ├── launch/ 										# 启动文件目录
│ │ │ └── package1_launch.py 				# 启动文件
│ │ ├── config/ 										# 配置文件目录
│ │ │ └── params.yaml 							# 参数配置文件
│ │ ├── package.xml 								# 包元信息文件
│ │ ├── setup.py 										# Python包安装配置
│ │ └── setup.cfg 									# 安装配置
│ │
│ ├── package2/ 										# 功能包2(C++包示例)
│ │ ├── src/ 												# C++源代码目录
│ │ │ ├── node2.cpp 								# 节点源代码
│ │ │ └── lib2.cpp 									# 库源代码
│ │ ├── include/ 										# 头文件目录
│ │ │ └── package2/ 								# 包头文件目录
│ │ │ ├── node2.hpp 								# 节点头文件
│ │ │ └── lib2.hpp 									# 库头文件
│ │ ├── launch/ 										# 启动文件目录
│ │ │ └── package2_launch.py 				# 启动文件
│ │ ├── config/ 										# 配置文件目录
│ │ │ └── config.yaml 							# 配置文件
│ │ ├── msg/ 												# 自定义消息定义
│ │ │ └── CustomMsg.msg 						# 消息定义文件
│ │ ├── srv/ 												# 自定义服务定义
│ │ │ └── CustomSrv.srv 						# 服务定义文件
│ │ ├── action/ 										# 自定义动作定义
│ │ │ └── CustomAction.action 			# 动作定义文件
│ │ ├── test/ 											# 测试文件目录
│ │ │ ├── test_node2.cpp 						# 单元测试
│ │ │ └── test_integration.cpp 			# 集成测试
│ │ ├── package.xml 								# 包元信息文件
│ │ └── CMakeLists.txt 							# CMake编译配置
│ │
│ └── interfaces_package/ 					# 接口包示例
│ ├── msg/ 													# 消息定义目录
│ │ ├── Status.msg 									# 状态消息
│ │ └── SensorData.msg 							# 传感器数据消息
│ ├── srv/ 													# 服务定义目录
│ │ └── GetData.srv 								# 获取数据服务
│ ├── action/ 											# 动作定义目录
│ │ └── Navigate.action 						# 导航动作
│ ├── package.xml 									# 包元信息
│ └── CMakeLists.txt 								# CMake配置
│
├── build/ 													# 编译中间文件目录
│
├── install/ 												# 安装文件目录
│
└── log/ 														# 编译日志目录

工作空间类型

  • **Overlay 工作空间:**叠加在已有的工作空间之上
  • **Underlay 工作空间:**底层工作空间(如 ros2 安装目录)

创建工作空间方式

方法一:手动创建

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
cd src

方法二:克隆现有项目

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src

git clone https://github.com/ros2/examples.git

验证工作空间结构

# 检查目录结构
tree ~/ros2_ws

# 或者
ls -la ~/ros2_ws

设置编译环境

工作空间环境设置

# 编译后设置工作空间环境
cd ~/ros2_ws
source install/setup.bash

# 验证包是否可用
ros2 pkg list | grep your_package_name

永久环境配置

# 配置在系统的环境变量中
echo "source ~/ros2_ws/install/setup.bash"

# 重新加载bashrc
source ~/.bashrc

环境变量检查

# 环境变量检查
echo "ROS_DISTRO: $ROS_DISTRO"
echo "ROS_VERSION: $ROS_VERSION"
echo "ROS_DOMAIN_ID: $ROS_DOMAIN_ID"
echo "AMENT_PREFIX_PATH: $AMENT_PREFIX_PATH"

# 检查python路径
echo "PYTHONPATH: $PYTHONPATH"

功能包 机器人功能包分类

定义和概念

  • 功能包:ROS2中代码组织的基本单元,包含实现特定功能的节点、库、配置文件等
  • 模块化设计:每个功能包专注于解决特定问题,便于代码复用和维护
  • 依赖管理:通过功能包管理代码间的依赖关

功能包的作用

功能包的核心作用:
├── 代码组织 		# 将相关功能代码组织在一起
├── 依赖管理 		# 声明和管理外部依赖
├── 接口定义 		# 定义消息、服务、动作接口
├── 资源管理 		# 管理配置文件、启动文件等资源
└── 版本控制 		# 独立的版本管理和发布

功能包类型

  • **可执行包:**包含节点的功能包
  • **库包:**提供可重用的功能包
  • **接口包:**定义消息、服务、动作的功能包
  • **元包:**组织相关功能包的集合

功能包创建

Python 功能包创建

cd ~/ros2_ws/src

# 创建Python功能包
ros2 pkg create --build-type ament_python my_python_pkg

# 带依赖的Python包创建
ros2 pkg create --build-type ament_python my_python_pkg \
  --dependencies rclpy std_msgs sensor_msgs geometry_msgs

  # 创建带节点的Python包
  ros2 pkg create --build-type ament_python my_python_pkg \
    --dependencies rclpy std_msgs \
    --node-name my_node

C++功能包创建

# 创建C++功能包
ros2 pkg create --build-type ament_cmake my_cpp_pkg
# 创建后再当前目录 增加了 my_cpp_pkg 目录

# 带依赖的C++包创建
ros2 pkg create --build-type ament_cmake my_cpp_pkg \
  --dependencies rclcpp std_msgs sensor_msgs geometry_msgs

# 创建带节点的C++包
ros2 pkg create --build-type ament_cmake my_cpp_pkg \
  --dependencies rclcpp std_msgs \
  --node-name my_node

功能包结构

Python 功能包结构

my_python_pkg/ 								# 功能包根目录
├── my_python_pkg/ 						# Python模块目录
│ ├── __init__.py 						# Python包初始化文件
│ ├── my_node.py 							# 节点源文件
│ ├── my_module.py 						# 自定义模块
│ └── submodule/ 							# 子模块目录
│ ├── __init__.py 						# 子模块初始化
│ └── helper.py 							# 辅助功能模块
├── launch/ 									# 启动文件目录
│ ├── my_launch.py 						# Python启动文件
│ └── my_launch.xml 					# XML启动文件
├── config/ 									# 配置文件目录
│ ├── params.yaml 						# 参数配置文件
│ └── robot_config.yaml 			# 机器人配置文件
├── resource/ 								# 资源文件目录
│ └── my_python_pkg 					# 包标识文件
├── test/ 										# 测试文件目录
│ ├── test_copyright.py 			# 版权测试
│ ├── test_flake8.py 					# 代码风格测试
│ ├── test_pep257.py 					# 文档字符串测试
│ └── test_my_node.py 				# 自定义单元测试
├── package.xml 							# 包元信息文件
├── setup.py 									# Python包安装配置
├── setup.cfg 								# 安装配置
└── README.md 								# 包说明文档

setup.py 文件:

from setuptools import setup
package_name = 'my_python_pkg'
setup(
    name=package_name,
    version='0.0.1',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages',
         ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        # 添加启动文件
        ('share/' + package_name + '/launch', ['launch/my_launch.py']),
        # 添加配置文件
        ('share/' + package_name + '/config', ['config/params.yaml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='Your Name',
    maintainer_email='326873713@qq.com',
    description='Python package description',
    license='Apache License 2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'my_node = my_python_pkg.my_node:main',
        ],
    },
)

节点源文件示例:

#!/usr/bin/env python3
# my_python_pkg/my_node.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
from geometry_msgs.msg import Twist
class MyNode(Node):
    def __init__(self):
        super().__init__('my_node')
# 创建发布者
self.publisher = self.create_publisher(String, 'topic', 10)
# 创建订阅者
self.subscription = self.create_subscription(
    Twist, 'cmd_vel', self.listener_callback, 10)
# 创建定时器
timer_period = 0.5
self.timer = self.create_timer(timer_period, self.timer_callback)
self.get_logger().info('Node initialized')
def timer_callback(self):
    msg = String()
msg.data = 'Hello World'
self.publisher.publish(msg)
def listener_callback(self, msg):
    self.get_logger().info(f'Received: {msg}')
def main(args=None):
    rclpy.init(args=args)
node = MyNode()
try:
    rclpy.spin(node)
except KeyboardInterrupt:
pass
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
    main()

C++功能包结构

my_cpp_pkg/ 					# 功能包根目录
├── src/ 						# 源代码目录
│ ├── my_node.cpp 				# 节点源文件
│ ├── my_library.cpp 			# 库源文件
│ └── utils/ 					# 工具类源文件
│ └── helper.cpp 				# 辅助功能实现
├── include/ 					# 头文件目录
│ └── my_cpp_pkg/ 				# 包头文件目录
│ ├── my_node.hpp 				# 节点头文件
│ ├── my_library.hpp 			# 库头文件
│ └── utils/ 					# 工具类头文件
│ └── helper.hpp 				# 辅助功能头文件
├── launch/ 					# 启动文件目录
│ ├── my_launch.py 				# Python启动文件
│ └── my_launch.xml 			# XML启动文件
├── config/ 					# 配置文件目录
│ ├── params.yaml 				# 参数配置文件
│ └── robot_config.yaml 		# 机器人配置文件
├── test/ 						# 测试文件目录
│ ├── test_my_node.cpp 			# 节点单元测试
│ └── test_my_library.cpp 		# 库单元测试
├── package.xml 				# 包元信息文件
├── CMakeLists.txt 				# CMake编译配置
└── README.md 					# 包说明文档

CMakeLists.txt 文件:

cmake_minimum_required(VERSION 3.8)
project(my_cpp_pkg)
# 编译选项
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# 查找依赖包
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
# 包含头文件目录
include_directories(include)
# 创建库
add_library(${PROJECT_NAME}_lib
src/my_library.cpp
src/utils/helper.cpp
)
# 库的依赖
ament_target_dependencies(${PROJECT_NAME}_lib
rclcpp
std_msgs
geometry_msgs
)
# 创建可执行文件
add_executable(my_node src/my_node.cpp)
# 可执行文件的依赖
ament_target_dependencies(my_node
rclcpp
std_msgs
geometry_msgs
)
# 链接库
target_link_libraries(my_node ${PROJECT_NAME}_lib)
# 安装可执行文件
install(TARGETS my_node
DESTINATION lib/${PROJECT_NAME}
)
# 安装库
install(TARGETS ${PROJECT_NAME}_lib
DESTINATION lib
)
# 安装头文件
install(DIRECTORY include/
DESTINATION include
)
# 安装启动文件
install(DIRECTORY launch/
DESTINATION share/${PROJECT_NAME}/launch
)
# 安装配置文件
install(DIRECTORY config/
DESTINATION share/${PROJECT_NAME}/config
)
# 导出依赖
ament_export_include_directories(include)
ament_export_libraries(${PROJECT_NAME}_lib)
ament_export_dependencies(rclcpp std_msgs geometry_msgs)
# 测试
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
# 添加单元测试
find_package(ament_cmake_gtest REQUIRED)
ament_add_gtest(test_my_node test/test_my_node.cpp)
target_link_libraries(test_my_node ${PROJECT_NAME}_lib)
endif()
ament_package()

C++节点源文件示例:

// src/my_node.cpp
#include "my_cpp_pkg/my_node.hpp"
MyNode::MyNode() : Node("my_node")
{
    // 创建发布者
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    // 创建订阅者
    subscription_ = this->create_subscription<geometry_msgs::msg::Twist>(
        "cmd_vel", 10,
        std::bind(&MyNode::listener_callback, this, std::placeholders::_1));
    // 创建定时器
    timer_ = this->create_wall_timer(
        std::chrono::milliseconds(500),
        std::bind(&MyNode::timer_callback, this));
    RCLCPP_INFO(this->get_logger(), "Node initialized");
}
void MyNode::timer_callback()
{
    auto message = std_msgs::msg::String();
    message.data = "Hello World";
    publisher_->publish(message);
}
void MyNode::listener_callback(const geometry_msgs::msg::Twist::SharedPtr msg)
{
    RCLCPP_INFO(this->get_logger(), "Received twist message");
}
int main(int argc, char * argv[])
{
    rclcpp::init(argc, argv);
    rclcpp::spin(std::make_shared<MyNode>());
    rclcpp::shutdown();
    return 0;
}

package.xml 文件(通用)

<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd"
schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>my_cpp_pkg</name>
  <version>0.0.1</version>
  <description>C++ package description</description>
  <maintainer email="326873713@qq.com">Your Name</maintainer>
  <license>Apache License 2.0</license>
  <!-- 构建依赖 -->
  <buildtool_depend>ament_cmake</buildtool_depend>
  <!-- 编译依赖 -->
  <depend>rclcpp</depend>
  <depend>std_msgs</depend>
  <depend>geometry_msgs</depend>
  <!-- 测试依赖 -->
  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>
  <test_depend>ament_cmake_gtest</test_depend>
  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

编译功能包

编译指定功能包

# 编译单个包
colcon build --package-select my_python_pkg

# 编译多个指定包
colcon build --packages-select my_python_pkg my_cpp_pkg

# 编译包及其依赖
colcon build --packages-up-to my_python_pkg

# 编译修改过的包
colcon build --packages-changed

编译所有功能包

# 基础的编译命令
cd ~/ros2_ws
colcon build

# 详细输出编译过程
colcon build --event-handlers console_direct+

# 多核编译
colcon build --parallel-workers 4

# 符号链接安装(开发时推荐)
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Debug

# 编译时显示详细信息
colcon build --event-handlers console_cohesion+ desktop_notification-

清除编译历史

# 删除build install log目录
rm -rf build/ install/ log/

# 删除指定包的编译文件
rm -rf build/my_python_pkg/ install/my_python_pkg/

# 使用colcon清除缓存
colcon build --cmake-clean-cache

# 强制重新编译所有包
colcon build --cmake-clean-first

# 清理Python字节码文件
find . -name "*.pyc" -delete
find . -name "__pycache__" -type d -exec rm -rf {} +

编译验证和测试

# 编译并运行测试
colcon build --packages-select my_cpp_pkg
colcon test --packages-select my_cpp_pkg

# 查看测试结果
colcon test-result --all

# 详细测试结果
colcon test-result --verbose

# 编译时运行代码检查
colcon build --packages-select my_python_pkg --event-handlers console+direct+