ROS2 Jazzy 基础

系统环境

  • 使用 Ubuntu24.04 LTS 系统
    Mac 系统:操作差别不大,可参考本文继续配置、使用 ROS2
    Windows 系统:请先安装 Ubuntu24.04 LTS 虚拟系机,或安装 Ubuntu24.04 LTS(双)系统,WSL 没有经过测试, 但确定的是不支持 Windows 系统。
  • 创建并使用使用虚拟环境,Python=3.12
  • 请使用vscode编辑器
  • 以下教程基于 bash, 使用其他 shell 的读者请注意甄别.

安装 ROS2 Jazzy

  • 更新安装器 apt
1sudo apt update
2sudo apt upgrade
3sudo apt install software-properties-common
4sudo add-apt-repository universe
  • 确定环境支持 UTF-8
1locale # 检查环境是否支持UTF-8
2# 若返回不是UTF-8,请执行以下命令
3
4sudo apt install locales # 安装或更新locale
5sudo locale-gen en_US en_US.UTF-8
6sudo update-locale LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8
7export LANG=en_US.UTF-8
8
9locale # 再次检查
  • apt 添加 ROS2 的包路径(使 apt 可以获取到 ROS2 安装包)
1sudo apt install curl -y # 安装或更新curl
2export ROS_APT_SOURCE_VERSION=$(curl -s https://api.github.com/repos/ros-infrastructure/ros-apt-source/releases/latest | grep -F "tag_name" | awk -F\" '{print $4}')
3curl -L -o /tmp/ros2-apt-source.deb "https://github.com/ros-infrastructure/ros-apt-source/releases/download/${ROS_APT_SOURCE_VERSION}/ros2-apt-source_${ROS_APT_SOURCE_VERSION}.$(. /etc/os-release && echo ${UBUNTU_CODENAME:-${VERSION_CODENAME}})_all.deb"
4sudo dpkg -i /tmp/ros2-apt-source.deb
  • 再次更新 apt
1sudo apt update
2sudo apt upgrade
  • 安装 ros-dev-tool 以及 ROS2 Jazzy
1sudo apt install ros-dev-tools
2sudo apt install ros-jazzy-desktop
3# 此处的ros选择desktop版本(操作简便,有GUI),
4# 但base版本也是受欢迎的,假如你认可自己,请安装ros-jazzy-base
5# 机器人本身运行的是base版本
  • 初始化 ROS2
1souce /opt/ros/jazzy/setup.bash
2# 将.bash换成你使用的SHELL

创建 ROS2 项目

  • 建立工作空间
1cd <你的工作目录> # 工作目录一般英文为 workspace, 请务必认识
2mkdir -p <你的项目名称>/src
3cd <你的项目名称>/src
  • 配置,使用包(packages)
    选择以下两种类型的一种包创建
  1. 创建一个Cmake包(适用于纯C++或者python与C++都有的包)
1# 用pkg工具创建一个包
2ros2 pkg create --build-type ament_cmake <包名称>
3# 执行成功后你将会看到一个包目录,里面包含基本包信息
4
5# 证书默认是Apache-2.0
  1. 创建一个python包(适用于纯python的包)
1# 用pkg工具创建一个包
2ros2 pkg create --build-type ament_python <包名称>
3# 执行成功后你将会看到一个包目录,里面包含基本包信息
4# 证书默认是Apache-2.0
5
6# 安装pytest依赖
7pip install pytest
  • 包信息检查,编辑(Cmake与python包均需要)
1# 确保package.xml文件中依赖声明包含以下配置信息
2# 添加在<license><test_depend>之间,与其他组分空行隔开
3<depend>rclcpp</depend>
4<depend>std_msgs</depend>
5
6# 输入你自己可用的邮箱,名字随意,但是只用一个名字不换
7# 如果你有使用github,请使用你github绑定的邮箱和昵称
8<maintainer email="<你的邮箱>"><你的名字></maintainer>

python包需要修改setup.py

1# setup.py
2
3# 修改这两行
4maintainer='<你的名字>',
5maintainer_email='<你的邮箱>',
  • 空包测试(可以跳过)
1cd .. # 回到你的项目目录<你的项目名称>
2colcon build --packages-select cpp_pubsub
3# 如果成功,说明包结构正确

VSCode 依赖库环境变量设置

  • 解决库被标红问题(C++)

vscode 中,Ctrl+Shift+P, 搜索并选择C/C++: Edit Configurations(JSON),在includePath内,原有配置基础上添加配置信息:

1"includePath": [
2    "/opt/ros/**/include/**",
3    "/usr/include/**",
4    "/usr/include/c++/**"
5],

建立发布者,订阅者通信

注意区分你的包是什么类型的

Cmake包(纯C++示例)

  1. 创建发布者,订阅者节点
1cd src/<包名称>/src # 进入包内src文件
2touch <发布者名称.cpp> # 创建c++文件,作为一个简单的发布者节点
3touch <订阅者名称.cpp> # 创建c++文件,作为一个简单的订阅者节点

注:此处可以直接使用命令code .,在该文件下打开 vscode 进行创建编辑工作

 1.
 2└── <你的项目名称>/
 3    ├── .vscode
 4    ├── build
 5    ├── install
 6    ├── log
 7    └── src/
 8        └── <包名称(Cmake)>/
 9            ├── include/
10            │   └── <包名称(Cmake)>
11            ├── src/
12            │   ├── <订阅者名称>.cpp
13            │   └── <发布者名称>.cpp
14            ├── CMakeList.txt
15            └── package.xml
  1. 编写发布者节点
 1// <发布者名称.cpp>
 2
 3#include "rclcpp/rclcpp.hpp"
 4#include "std_msgs/msg/string.hpp"
 5
 6using namespace std::chrono_literals;
 7
 8class SimplePublisher : public rclcpp::Node {
 9 public:
10  SimplePublisher() : Node("<发布者节点名称>"), count_(0) {
11    publisher_ = this->create_publisher<std_msgs::msg::String>("<话题名称>", 10);
12    timer_ = this->create_wall_timer(500ms, std::bind(
13    &SimplePublisher::timer_callback,
14    this));
15  }
16
17 private:
18  void timer_callback() {
19    auto message = std_msgs::msg::String();
20    message.data = "Hello, world! " + std::to_string(count_++);
21    RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
22    publisher_->publish(message);
23  }
24  rclcpp::TimerBase::SharedPtr timer_;
25  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
26  size_t count_;
27};
28
29int main(int argc, char * argv[]) {
30  rclcpp::init(argc, argv);
31  rclcpp::spin(std::make_shared<SimplePublisher>());
32  rclcpp::shutdown();
33  return 0;
34}
  1. 编写订阅者节点
 1// <订阅者名称.cpp>
 2
 3#include "rclcpp/rclcpp.hpp"
 4#include "std_msgs/msg/string.hpp"
 5
 6class SimpleSubscriber : public rclcpp::Node {
 7 public:
 8  SimpleSubscriber() : Node("<订阅者节点名称>") {
 9    subscription_ = this->create_subscription<std_msgs::msg::String>(
10      "<话题名称>", 10, std::bind(&SimpleSubscriber::topic_callback, this, std::placeholders::_1));
11  }
12
13 private:
14  void topic_callback(const std_msgs::msg::String::SharedPtr msg) {
15    RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
16  }
17  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
18};
19
20int main(int argc, char * argv[]) {
21  rclcpp::init(argc, argv);
22  rclcpp::spin(std::make_shared<SimpleSubscriber>());
23  rclcpp::shutdown();
24  return 0;
25}
  1. 编辑 Cmakelist.txt
 1cmake_minimum_required(VERSION 3.8)
 2project(<包名称>)
 3
 4if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
 5  add_compile_options(-Wall -Wextra -Wpedantic)
 6endif()
 7
 8# find dependencies
 9find_package(ament_cmake REQUIRED)
10find_package(rclcpp REQUIRED)     # <- Don's forget this
11find_package(std_msgs REQUIRED)   # <- Don's forget this
12# uncomment the following section in order to fill in
13# further dependencies manually.
14# find_package(<dependency> REQUIRED)
15
16# Add the C++ executable (your listener node)
17add_executable(<发布者节点名称> src/<发布者名称>.cpp)
18# Link the executable to the required ROS 2 libraries
19ament_target_dependencies(<发布者节点名称>
20  rclcpp
21  std_msgs
22)
23
24add_executable(<订阅者节点名称> src/<订阅者名称>.cpp)
25ament_target_dependencies(<订阅者节点名称>
26  rclcpp
27  std_msgs
28)
29
30install(TARGETS
31  <发布者节点名称>
32  <订阅者节点名称>
33  DESTINATION lib/${PROJECT_NAME}
34)
35
36if(BUILD_TESTING)
37  find_package(ament_lint_auto REQUIRED)
38  # the following line skips the linter which checks for copyrights
39  # comment the line when a copyright and license is added to all source files
40  set(ament_cmake_copyright_FOUND TRUE)
41  # the following line skips cpplint (only works in a git repo)
42  # comment the line when this package is in a git repo and when
43  # a copyright and license is added to all source files
44  set(ament_cmake_cpplint_FOUND TRUE)
45  ament_lint_auto_find_test_dependencies()
46endif()
47
48ament_package()
  1. 使用 colcon 构建(编译)项目
1cd <你的工作目录>/<你的项目名称>/ # 回到你的项目根目录
2colcon build --symlink-install --packages-select <包名称>
3# --symlink-install 一种不需要反复构建的构建方式
4# --packages-skip <package1> <package2>跳过包的构建(编译)
5# --packages-select <package1> <package2>选择包进行构建(编译)
6# 直接colcon build可以构建(编译)全部

Python包

1cd src/<包名称>/<包名称> # 进入包内<包名称>文件
2touch <发布者名称.py> # 创建py文件,作为一个简单的发布者节点
3touch <订阅者名称.py> # 创建py文件,作为一个简单的订阅者节点
 1.
 2└── <你的项目名称>/
 3    ├── .vscode
 4    ├── build
 5    ├── install
 6    ├── log
 7    └── src/
 8        └── <包名称(python)>/
 9            ├── <包名称(python)>/
10            │   ├── __init__.py
11            │   ├── <订阅者名称>.py
12            │   └── <发布者名称>.py
13            ├── resource/
14            │   └── <包名称(python)>_py
15            ├── test/
16            │   ├── test_copyright.py
17            │   ├── test_flake8.py
18            │   └── test_pep257.py
19            ├── package.xml
20            ├── setup.cfg
21            └── setup.py
  1. 编写发布者节点
 1# <发布者名称>.py
 2
 3import rclpy
 4from rclpy.node import Node
 5from std_msgs.msg import String # Standard ROS 2 message type for plain text
 6
 7class SimplePublisher(Node):
 8  """
 9  A simple node that publishes messages to a topic.
10  """
11  def __init__(self):
12    # Initialize the node with the name 'simple_talker'
13    super().__init__('<发布者名称>')
14
15    # 1. Create the Publisher
16    # Publishes String messages on the topic 'topic' with a queue size of 10.
17    self.publisher_ = self.create_publisher(String, '<话题>', 10)
18
19    # 2. Set up a Timer
20    # The timer calls the timer_callback function every 0.5 seconds (0.5s period).
21    timer_period = 0.5  # seconds
22    self.timer = self.create_timer(timer_period, self.timer_callback)
23
24    # 3. Initialize the counter
25    self.i = 0
26
27    # Log that the publisher has started
28    self.get_logger().info('Simple Talker Node has started publishing.')
29
30  def timer_callback(self):
31    """
32    Callback function executed by the timer to publish a new message.
33    """
34    # Create a new String message
35    msg = String()
36    msg.data = f'Hello World: {self.i}'
37
38    # Publish the message
39    self.publisher_.publish(msg)
40
41    # Log the published message
42    self.get_logger().info(f'Publishing: "{msg.data}"')
43
44    # Increment the counter
45    self.i += 1
46
47
48def main(args=None):
49  # Initialize the rclpy client library
50  rclpy.init(args=args)
51
52  # Create the node instance
53  publisher_node = SimplePublisher()
54
55  # Spin the node to keep it running and execute the timer callback
56  # continuously until the process is manually terminated (Ctrl+C).
57  try:
58    rclpy.spin(publisher_node)
59  except KeyboardInterrupt:
60    pass # Catch Ctrl+C
61
62  # Destroy the node explicitly
63  publisher_node.destroy_node()
64
65  # Shutdown rclpy
66  rclpy.shutdown()
67
68if __name__ == '__main__':
69  main()
  1. 编写订阅者节点
 1# <订阅者名称>.py
 2
 3import rclpy
 4from rclpy.node import Node
 5from std_msgs.msg import String # Standard ROS 2 message type for plain text
 6
 7class SimpleSubscriber(Node):
 8  """
 9  A simple node that subscribes to a topic and processes received messages.
10  """
11  def __init__(self):
12    # Initialize the node with the name 'simple_listener'
13    super().__init__('<订阅者名称>')
14
15    # 1. Create the Subscriber
16    # Subscribes to String messages on the topic 'topic'.
17    # The callback function self.listener_callback is executed when a message arrives.
18    self.subscriber = self.create_subscription(
19      String,             # Message type
20      '<话题>',            # Topic name (MUST match the publisher)
21      self.listener_callback, # Callback function
22      10                  # QoS history depth
23    )
24
25    # Prevent unused variable warning
26    self.subscriber
27
28    # Log that the subscriber has started
29    self.get_logger().info('Simple Listener Node has started listening.')
30
31  def listener_callback(self, msg):
32    """
33    Callback function executed when a message is received.
34    """
35    # Log the received message data
36    self.get_logger().info(f'I heard: "{msg.data}"')
37
38
39def main(args=None):
40  # Initialize the rclpy client library
41  rclpy.init(args=args)
42
43  # Create the node instance
44  subscriber_node = SimpleSubscriber()
45
46  # Spin the node. It waits for messages and calls the callback function.
47  # It runs continuously until the process is manually terminated (Ctrl+C).
48  try:
49      rclpy.spin(subscriber_node)
50  except KeyboardInterrupt:
51      pass # Catch Ctrl+C
52
53  # Destroy the node explicitly
54  subscriber_node.destroy_node()
55
56  # Shutdown rclpy
57  rclpy.shutdown()
58
59if __name__ == '__main__':
60  main()
  1. 编辑 setup.py
 1# setup.py
 2
 3from setuptools import find_packages, setup
 4
 5package_name = 'pubsub_py'
 6
 7setup(
 8    name=package_name,
 9    version='0.0.0',
10    packages=find_packages(exclude=['test']),
11    data_files=[
12        ('share/ament_index/resource_index/packages',
13            ['resource/' + package_name]),
14        ('share/' + package_name, ['package.xml']),
15    ],
16    install_requires=['setuptools'],
17    zip_safe=True,
18    maintainer='<你的名字>',
19    maintainer_email='<你的邮箱>',
20    description='TODO: Package description',
21    license='TODO: License declaration',
22    extras_require={
23        'test': [
24            'pytest',
25        ],
26    },
27    entry_points={
28        'console_scripts': [
29            '<发布者节点名称> = <包名称>.<发布者名称>:main',
30            '<订阅者节点名称> = <包名称>.<订阅者名称>:main',
31        ],
32    },
33)
  1. 使用 colcon 构建(编译)项目
1cd <你的工作目录>/<你的项目名称>/ # 回到你的项目根目录
2colcon build --symlink-install --packages-select <包名称>
3# --symlink-install 一种不需要反复构建的构建方式
4# --packages-skip <package1> <package2>跳过包的构建(编译)
5# --packages-select <package1> <package2>选择包进行构建(编译)
6# 直接colcon build可以构建(编译)全部

运行,测试

  • 环境初始化
1# 开启两个终端,一个作为发布者,一个作为订阅者,都需要进行环境初始化
2cd <你的项目名称> # 进入你的项目根目录
3source install/setup.bash
  • 运行发布者节点
1# 一个终端执行
2ros2 run <包名称> <发布者节点名称>
1# 另一个终端执行
2ros2 run <包名称> <订阅者节点名称>

结果

若一切正常,你将看到第一个终端向第二个终端发送信息。

任务

请在你的设备上实现上述基本通信过程(语言选择其一即可),将结果截图后发送到邮箱:1751613346@qq.com