ROS2(Robot Operating System 2)是一个开源的机器人开发框架,旨在简化机器人软件的开发和集成。它是ROS1的继承者,具有改进的性能、更高的可靠性和更灵活的架构。
sudo apt update && sudo apt upgrade -y
# 确保系统支持UTF-8编码
sudo apt install -y locales
sudo locale-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
# 添加ROS2软件源
sudo apt install -y software-properties-common
sudo add-apt-repository -y universe
sudo apt update && sudo apt install -y curl gnupg lsb-release
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-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
# 更新软件包索引
sudo apt update
# 安装ROS2 Humble桌面完整版
sudo apt install -y ros-humble-desktop
# 安装开发工具
sudo apt install -y ros-dev-tools
# 添加到bashrc
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc
# 验证环境变量
echo $ROS_DISTRO
sudo rosdep init
rosdep update
# 检查ROS2版本
ros2 --version
# 检查系统状态
ros2 doctor
# 运行示例程序
ros2 run turtlesim turtlesim_node
ROS2采用分布式架构,基于DDS(Data Distribution Service)标准,支持多进程和多机器通信。
命令 | 功能 |
---|---|
ros2 node list |
列出运行中的节点 |
ros2 topic list |
列出活跃的话题 |
ros2 topic echo |
显示话题消息 |
ros2 service list |
列出可用服务 |
ros2 param list |
列出参数 |
ros2 run |
运行节点 |
# 创建工作空间目录
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
# 初始化工作空间
colcon build
source install/setup.bash
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python my_python_package --dependencies rclpy std_msgs
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MinimalPublisher(Node):
def __init__(self):
super().__init__('minimal_publisher')
self.publisher_ = self.create_publisher(String, 'topic', 10)
timer_period = 0.5 # 0.5秒发送一次
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0
def timer_callback(self):
msg = String()
msg.data = f'Hello ROS2: {self.i}'
self.publisher_.publish(msg)
self.get_logger().info(f'Publishing: "{msg.data}"')
self.i += 1
def main(args=None):
rclpy.init(args=args)
minimal_publisher = MinimalPublisher()
rclpy.spin(minimal_publisher)
minimal_publisher.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber')
self.subscription = self.create_subscription(
String, 'topic', self.listener_callback, 10)
def listener_callback(self, msg):
self.get_logger().info(f'I heard: "{msg.data}"')
def main(args=None):
rclpy.init(args=args)
minimal_subscriber = MinimalSubscriber()
rclpy.spin(minimal_subscriber)
minimal_subscriber.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
from setuptools import setup
package_name = 'my_python_package'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='your_name',
maintainer_email='[email protected]',
description='Example ROS2 Python package',
license='Apache-2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'publisher = my_python_package.publisher:main',
'subscriber = my_python_package.subscriber:main',
],
},
)
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake my_cpp_package --dependencies rclcpp std_msgs
#include
#include
#include
#include
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using namespace std::chrono_literals;
class MinimalPublisher : public rclcpp::Node
{
public:
MinimalPublisher()
: Node("minimal_publisher"), count_(0)
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
timer_ = this->create_wall_timer(
500ms, std::bind(&MinimalPublisher::timer_callback, this));
}
private:
void timer_callback()
{
auto message = std_msgs::msg::String();
message.data = "Hello ROS2 " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalPublisher>());
rclcpp::shutdown();
return 0;
}
#include
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;
class MinimalSubscriber : public rclcpp::Node
{
public:
MinimalSubscriber()
: Node("minimal_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
}
private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MinimalSubscriber>());
rclcpp::shutdown();
return 0;
}
cmake_minimum_required(VERSION 3.8)
project(my_cpp_package)
# 找到依赖
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
# 添加可执行文件
add_executable(publisher src/publisher.cpp)
ament_target_dependencies(publisher rclcpp std_msgs)
add_executable(subscriber src/subscriber.cpp)
ament_target_dependencies(subscriber rclcpp std_msgs)
# 安装可执行文件
install(TARGETS
publisher
subscriber
DESTINATION lib/${PROJECT_NAME})
ament_package()
cd ~/ros2_ws
colcon build
source install/setup.bash
# 终端1:运行发布者
ros2 run my_python_package publisher
# 终端2:运行订阅者
ros2 run my_python_package subscriber
# 终端1:运行发布者
ros2 run my_cpp_package publisher
# 终端2:运行订阅者
ros2 run my_cpp_package subscriber
使用以下命令验证节点通信:
# 查看节点列表
ros2 node list
# 查看话题列表
ros2 topic list
# 查看话题消息
ros2 topic echo /topic
# 查看节点信息
ros2 node info /minimal_publisher
# 发布者示例
publisher = self.create_publisher(String, 'my_topic', 10)
msg = String()
msg.data = 'Hello World'
publisher.publish(msg)
# 订阅者示例
subscription = self.create_subscription(
String, 'my_topic', self.callback, 10)
from rclpy.qos import QoSProfile, QoSReliabilityPolicy, QoSDurabilityPolicy
qos_profile = QoSProfile(
reliability=QoSReliabilityPolicy.RELIABLE,
durability=QoSDurabilityPolicy.TRANSIENT_LOCAL,
depth=10
)
publisher = self.create_publisher(String, 'topic', qos_profile)
from example_interfaces.srv import AddTwoInts
class ServiceServer(Node):
def __init__(self):
super().__init__('service_server')
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)
def add_two_ints_callback(self, request, response):
response.sum = request.a + request.b
self.get_logger().info(f'Request: {request.a} + {request.b} = {response.sum}')
return response
class ServiceClient(Node):
def __init__(self):
super().__init__('service_client')
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('Service not available, waiting...')
def send_request(self, a, b):
req = AddTwoInts.Request()
req.a = a
req.b = b
future = self.cli.call_async(req)
return future
from action_msgs.msg import GoalStatus
from example_interfaces.action import Fibonacci
from rclpy.action import ActionServer
class FibonacciActionServer(Node):
def __init__(self):
super().__init__('fibonacci_action_server')
self._action_server = ActionServer(
self, Fibonacci, 'fibonacci', self.execute_callback)
def execute_callback(self, goal_handle):
self.get_logger().info('Executing goal...')
# 执行任务并发送反馈
feedback_msg = Fibonacci.Feedback()
feedback_msg.partial_sequence = [0, 1]
for i in range(1, goal_handle.request.order):
feedback_msg.partial_sequence.append(
feedback_msg.partial_sequence[i] + feedback_msg.partial_sequence[i-1])
goal_handle.publish_feedback(feedback_msg)
time.sleep(1)
# 返回结果
goal_handle.succeed()
result = Fibonacci.Result()
result.sequence = feedback_msg.partial_sequence
return result
from rclpy.action import ActionClient
class FibonacciActionClient(Node):
def __init__(self):
super().__init__('fibonacci_action_client')
self._action_client = ActionClient(self, Fibonacci, 'fibonacci')
def send_goal(self, order):
goal_msg = Fibonacci.Goal()
goal_msg.order = order
self._action_client.wait_for_server()
self._send_goal_future = self._action_client.send_goal_async(
goal_msg, feedback_callback=self.feedback_callback)
self._send_goal_future.add_done_callback(self.goal_response_callback)
def feedback_callback(self, feedback_msg):
self.get_logger().info(f'Feedback: {feedback_msg.feedback.partial_sequence}')
class ParameterNode(Node):
def __init__(self):
super().__init__('parameter_node')
# 声明参数
self.declare_parameter('my_parameter', 'default_value')
self.declare_parameter('update_rate', 10.0)
# 获取参数
my_param = self.get_parameter('my_parameter').get_parameter_value().string_value
update_rate = self.get_parameter('update_rate').get_parameter_value().double_value
# 参数回调
self.add_on_set_parameters_callback(self.parameter_callback)
def parameter_callback(self, params):
for param in params:
self.get_logger().info(f'Parameter {param.name} changed to {param.value}')
return SetParametersResult(successful=True)
# 列出参数
ros2 param list
# 获取参数值
ros2 param get <node_name> <parameter_name>
# 设置参数值
ros2 param set <node_name> <parameter_name> <value>
# 保存参数到文件
ros2 param dump <node_name> --output-dir ./
# 从文件加载参数
ros2 param load <node_name> <parameter_file>
ROS2支持以下基本数据类型:
# 创建消息接口包
ros2 pkg create --build-type ament_cmake my_interfaces
创建msg/Person.msg
文件:
string name
uint8 age
float64 height
bool is_student
# 固定长度数组
float64[3] position
int32[10] data
# 可变长度数组
string[] names
geometry_msgs/Point[] points
# 嵌套消息
geometry_msgs/Point position
geometry_msgs/Quaternion orientation
find_package(rosidl_default_generators REQUIRED)
# 添加消息文件
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Person.msg"
DEPENDENCIES geometry_msgs std_msgs
)
创建srv/AddTwoInts.srv
文件:
int64 a
int64 b
---
int64 sum
创建srv/GetPersonInfo.srv
文件:
string name
---
bool found
Person person_info
string error_message
创建action/Fibonacci.action
文件:
int32 order
---
int32[] sequence
---
int32[] partial_sequence
创建action/NavigateToGoal.action
文件:
geometry_msgs/PoseStamped target_pose
---
bool success
string result_message
---
geometry_msgs/PoseStamped current_pose
float64 distance_remaining
cmake_minimum_required(VERSION 3.8)
project(my_interfaces)
# 找到依赖
find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(std_msgs REQUIRED)
# 生成接口
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Person.msg"
"srv/AddTwoInts.srv"
"srv/GetPersonInfo.srv"
"action/Fibonacci.action"
"action/NavigateToGoal.action"
DEPENDENCIES geometry_msgs std_msgs
)
ament_package()
<package format="3">
<name>my_interfacesname>
<version>0.0.0version>
<description>Custom interfaces packagedescription>
<maintainer email="[email protected]">Your Namemaintainer>
<license>Apache-2.0license>
<buildtool_depend>ament_cmakebuildtool_depend>
<build_depend>rosidl_default_generatorsbuild_depend>
<build_depend>geometry_msgsbuild_depend>
<build_depend>std_msgsbuild_depend>
<exec_depend>rosidl_default_runtimeexec_depend>
<exec_depend>geometry_msgsexec_depend>
<exec_depend>std_msgsexec_depend>
<member_of_group>rosidl_interface_packagesmember_of_group>
<export>
<build_type>ament_cmakebuild_type>
export>
package>
cd ~/ros2_ws
colcon build --packages-select my_interfaces
source install/setup.bash
from my_interfaces.msg import Person
class PersonPublisher(Node):
def __init__(self):
super().__init__('person_publisher')
self.publisher_ = self.create_publisher(Person, 'person_topic', 10)
self.timer = self.create_timer(1.0, self.publish_person)
def publish_person(self):
msg = Person()
msg.name = 'Alice'
msg.age = 25
msg.height = 1.75
msg.is_student = True
self.publisher_.publish(msg)
#include "my_interfaces/msg/person.hpp"
class PersonPublisher : public rclcpp::Node
{
public:
PersonPublisher() : Node("person_publisher")
{
publisher_ = this->create_publisher<my_interfaces::msg::Person>("person_topic", 10);
timer_ = this->create_wall_timer(
std::chrono::seconds(1), std::bind(&PersonPublisher::publish_person, this));
}
private:
void publish_person()
{
auto msg = my_interfaces::msg::Person();
msg.name = "Alice";
msg.age = 25;
msg.height = 1.75;
msg.is_student = true;
publisher_->publish(msg);
}
rclcpp::Publisher<my_interfaces::msg::Person>::SharedPtr publisher_;
rclcpp::TimerBase::SharedPtr timer_;
};
# 查看接口定义
ros2 interface show my_interfaces/msg/Person
# 列出所有接口
ros2 interface list
# 查看接口包
ros2 interface package my_interfaces
# 创建工作空间
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
# 初始化工作空间
colcon build
source install/setup.bash
ros2_ws/
├── src/ # 源码目录
│ ├── package1/
│ └── package2/
├── build/ # 构建目录
├── install/ # 安装目录
└── log/ # 日志目录
# 设置underlay(基础工作空间)
source /opt/ros/humble/setup.bash
# 创建overlay工作空间
mkdir -p ~/overlay_ws/src
cd ~/overlay_ws
colcon build
source install/setup.bash
my_python_package/
├── package.xml # 包描述文件
├── setup.py # Python包配置
├── setup.cfg # 配置文件
├── my_python_package/ # Python源码目录
│ ├── __init__.py
│ ├── my_node.py
│ └── submodule/
├── resource/ # 资源文件
├── test/ # 测试文件
└── launch/ # 启动文件
my_cpp_package/
├── package.xml # 包描述文件
├── CMakeLists.txt # CMake配置文件
├── src/ # 源码目录
│ ├── my_node.cpp
│ └── my_library.cpp
├── include/ # 头文件目录
│ └── my_cpp_package/
│ └── my_library.hpp
├── launch/ # 启动文件
├── config/ # 配置文件
└── test/ # 测试文件
<package format="3">
<name>my_packagename>
<version>0.0.0version>
<description>My ROS2 packagedescription>
<maintainer email="[email protected]">Your Namemaintainer>
<license>Apache-2.0license>
<buildtool_depend>ament_cmakebuildtool_depend>
<build_depend>rclcppbuild_depend>
<build_depend>std_msgsbuild_depend>
<exec_depend>rclcppexec_depend>
<exec_depend>std_msgsexec_depend>
<test_depend>ament_lint_autotest_depend>
<test_depend>ament_lint_commontest_depend>
<export>
<build_type>ament_cmakebuild_type>
export>
package>
# 构建所有包
colcon build
# 构建特定包
colcon build --packages-select my_package
# 构建并运行测试
colcon build --packages-select my_package
colcon test --packages-select my_package
# 仅构建某个包及其依赖
colcon build --packages-up-to my_package
# 跳过某些包
colcon build --packages-skip my_package
# 并行构建
colcon build --parallel-workers 4
# 详细输出
colcon build --event-handlers console_direct+
# 符号链接安装(开发模式)
colcon build --symlink-install
# 继续构建(忽略失败)
colcon build --continue-on-error
# 使用vcs工具管理多仓库
# 创建.repos文件
repositories:
my_package:
type: git
url: https://github.com/user/my_package.git
version: main
# 导入仓库
vcs import src < my_workspace.repos
# 更新仓库
vcs pull src
# 自动source工作空间
echo "source ~/ros2_ws/install/setup.bash" >> ~/.bashrc
# 使用rosdep管理依赖
rosdep install --from-paths src --ignore-src -r -y
ros2 node
: 节点管理ros2 topic
: 话题操作ros2 service
: 服务调用ros2 param
: 参数管理ros2 bag
: 数据记录回放from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='my_package',
executable='my_node',
name='my_node_instance',
output='screen'
),
Node(
package='another_package',
executable='another_node',
name='another_node_instance',
output='screen'
)
])
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
def generate_launch_description():
return LaunchDescription([
# 声明启动参数
DeclareLaunchArgument(
'node_name',
default_value='my_node',
description='Name of the node'
),
# 使用参数
Node(
package='my_package',
executable='my_executable',
name=LaunchConfiguration('node_name'),
parameters=[{'param1': 'value1'}]
)
])
<launch>
<node pkg="my_package" exec="my_node" name="my_node_instance" output="screen"/>
<node pkg="another_package" exec="another_node" name="another_node_instance" output="screen"/>
launch>
# 运行Launch文件
ros2 launch my_package my_launch.py
# 查看Launch文件参数
ros2 launch my_package my_launch.py --show-args
# 传递参数
ros2 launch my_package my_launch.py node_name:=custom_name
# 节点管理
ros2 node list # 列出所有节点
ros2 node info /node_name # 查看节点信息
ros2 node kill /node_name # 终止节点
# 话题操作
ros2 topic list # 列出所有话题
ros2 topic echo /topic_name # 显示话题消息
ros2 topic info /topic_name # 查看话题信息
ros2 topic hz /topic_name # 查看话题频率
ros2 topic pub /topic_name std_msgs/msg/String "data: hello" # 发布消息
# 服务操作
ros2 service list # 列出所有服务
ros2 service call /service_name service_type request_data # 调用服务
ros2 service type /service_name # 查看服务类型
# Python日志
self.get_logger().debug('Debug message')
self.get_logger().info('Info message')
self.get_logger().warn('Warning message')
self.get_logger().error('Error message')
self.get_logger().fatal('Fatal message')
# 启动RViz2
ros2 run rviz2 rviz2
# 使用配置文件
ros2 run rviz2 rviz2 -d config_file.rviz
# 查看系统状态
ros2 doctor
# 监控资源使用
ros2 topic hz /topic_name
ros2 topic bw /topic_name
官方教程:参考 ROS2 Humble 教程,学习更高级的主题,如动作、参数和自定义消息。
实践项目:尝试使用 turtlesim 或 Gazebo 模拟器开发简单的机器人控制程序。
社区支持:加入 ROS 社区论坛,获取帮助和分享经验。
GitHub 教程:MOGI-ROS 提供的 ROS2 入门教程 涵盖基础到高级内容,适合初学者。
官方文档:ROS2 Humble 教程 提供全面的学习路径。
社区论坛:ROS 社区 是获取帮助和交流经验的平台。
实践平台:The Construct 提供在线模拟环境。
硬件教程:Husarion ROS2 教程 结合 ROSbot 硬件。
ROS2 Humble Tutorials Documentation
MOGI-ROS Week-1-2-Introduction-to-ROS2 GitHub
The Construct ROS2 Tutorials
Husarion ROS2 Tutorials
ROS Community Discourse
ROS2 Service Tutorial