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)
选择以下两种类型的一种包创建
- 创建一个Cmake包(适用于纯C++或者python与C++都有的包)
1# 用pkg工具创建一个包
2ros2 pkg create --build-type ament_cmake <包名称>
3# 执行成功后你将会看到一个包目录,里面包含基本包信息
4
5# 证书默认是Apache-2.0
- 创建一个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++示例)
- 创建发布者,订阅者节点
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// <发布者名称.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// <订阅者名称.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}
- 编辑 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()
- 使用 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# <发布者名称>.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# <订阅者名称>.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()
- 编辑 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)
- 使用 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