ROS 2 — Robot Operating System
ROS 2 — Robot Operating System
ROS 2 — это middleware для создания робототехнических систем. Не операционная система, а набор библиотек, инструментов и соглашений, работающих поверх Linux, Windows или macOS.
Архитектура ROS 2
Уровни абстракции
┌─────────────────────────────────────────────┐
│ Application Layer │
│ (Nav2, MoveIt 2, perception, planning) │
├─────────────────────────────────────────────┤
│ ROS 2 Client Libraries │
│ (rclcpp, rclpy, rclrs, rclnodejs) │
├─────────────────────────────────────────────┤
│ ROS 2 Middleware │
│ (rcl, rmw, DDS abstraction) │
├─────────────────────────────────────────────┤
│ DDS Implementation │
│ (Fast DDS, Cyclone DDS, Connext DDS) │
├─────────────────────────────────────────────┤
│ Operating System │
│ (Linux, Windows, macOS, RTOS) │
└─────────────────────────────────────────────┘
Граф вычислений
┌──────────┐
│ /camera │
│ (Node) │
└────┬─────┘
│ /image_raw
▼
┌──────────┐ ┌──────────────┐ ┌──────────┐
│ /lidar │──────│ /perception │──────│ /planner │
│ (Node) │ │ (Node) │ │ (Node) │
└──────────┘ └──────────────┘ └────┬─────┘
│ /scan │ /obstacles │ /cmd_vel
│ │ ▼
│ ┌─────┴─────┐ ┌──────────┐
└───────────►│ /costmap │ │ /driver │
│ (Node) │ │ (Node) │
└───────────┘ └──────────┘
DDS (Data Distribution Service)
Принцип работы
DDS обеспечивает decentralized discovery — узлы находят друг друга без центрального сервера (Master Node из ROS 1).
Процесс:
- Узел объявляет свои топики через multicast
- Другие узлы обнаруживают публикации/подписки
- DDS устанавливает прямые P2P соединения
Quality of Service (QoS)
#include "rclcpp/rclcpp.hpp"
auto qos = rclcpp::QoS(10)
.reliability(RMW_QOS_POLICY_RELIABILITY_RELIABLE)
.durability(RMW_QOS_POLICY_DURABILITY_TRANSIENT_LOCAL)
.history(RMW_QOS_POLICY_HISTORY_KEEP_LAST)
.deadline(std::chrono::milliseconds(100));
auto pub = node->create_publisher<std_msgs::msg::String>("topic", qos);
| Политика | Опции | Применение |
|---|---|---|
| Reliability | RELIABLE / BEST_EFFORT | Команды / Сенсоры |
| Durability | VOLATILE / TRANSIENT_LOCAL | Потоки / Параметры |
| History | KEEP_LAST(n) / KEEP_ALL | Буферизация |
| Deadline | Duration | Real-time |
| Lifespan | Duration | Устаревание данных |
Совместимость QoS
Publisher Subscriber Результат
─────────────────────────────────────
RELIABLE + RELIABLE = ✅ Работает
RELIABLE + BEST_EFFORT = ✅ Работает
BEST_EFFORT + RELIABLE = ❌ Несовместимо!
BEST_EFFORT + BEST_EFFORT = ✅ Работает
Основные концепции
Узлы (Nodes)
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import LaserScan
class LidarProcessor(Node):
def __init__(self):
super().__init__('lidar_processor')
# Параметры
self.declare_parameter('min_range', 0.1)
self.min_range = self.get_parameter('min_range').value
# Подписка
self.sub = self.create_subscription(
LaserScan, '/scan', self.scan_callback, 10)
# Публикация
self.pub = self.create_publisher(LaserScan, '/scan_filtered', 10)
self.get_logger().info('Lidar processor started')
def scan_callback(self, msg: LaserScan):
# Фильтрация близких точек
msg.ranges = [r if r > self.min_range else float('inf')
for r in msg.ranges]
self.pub.publish(msg)
def main():
rclpy.init()
node = LidarProcessor()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
Сервисы (Services)
Request-Response паттерн:
# Сервер
from example_interfaces.srv import AddTwoInts
class AddServer(Node):
def __init__(self):
super().__init__('add_server')
self.srv = self.create_service(
AddTwoInts, 'add_two_ints', self.callback)
def callback(self, request, response):
response.sum = request.a + request.b
return response
# Клиент
class AddClient(Node):
def __init__(self):
super().__init__('add_client')
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
async def call(self, a, b):
req = AddTwoInts.Request()
req.a, req.b = a, b
return await self.cli.call_async(req)
Actions
Для длительных задач с обратной связью:
from nav2_msgs.action import NavigateToPose
from rclpy.action import ActionClient
class Navigator(Node):
def __init__(self):
super().__init__('navigator')
self.client = ActionClient(self, NavigateToPose, 'navigate_to_pose')
def go_to(self, x, y, theta):
goal = NavigateToPose.Goal()
goal.pose.header.frame_id = 'map'
goal.pose.pose.position.x = x
goal.pose.pose.position.y = y
# ... quaternion from theta
self.client.wait_for_server()
future = self.client.send_goal_async(
goal,
feedback_callback=self.feedback_cb)
return future
def feedback_cb(self, feedback_msg):
feedback = feedback_msg.feedback
self.get_logger().info(
f'Distance remaining: {feedback.distance_remaining:.2f}')
Launch-файлы
Python Launch
# my_robot_launch.py
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
def generate_launch_description():
return LaunchDescription([
# Аргументы
DeclareLaunchArgument('use_sim', default_value='false'),
DeclareLaunchArgument('robot_name', default_value='my_robot'),
# Узлы
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': robot_description}],
),
Node(
package='controller_manager',
executable='ros2_control_node',
parameters=[
PathJoinSubstitution([
FindPackageShare('my_robot'),
'config', 'controllers.yaml'
])
],
condition=UnlessCondition(LaunchConfiguration('use_sim')),
),
# Включение других launch-файлов
IncludeLaunchDescription(
PathJoinSubstitution([
FindPackageShare('nav2_bringup'),
'launch', 'navigation_launch.py'
]),
launch_arguments={'use_sim_time': LaunchConfiguration('use_sim')}.items(),
),
])
Запуск
ros2 launch my_robot my_robot_launch.py use_sim:=true robot_name:=bot1
Стандартные пакеты
Навигация (Nav2)
┌─────────────────────────────────────────────────────┐
│ Nav2 Stack │
├──────────┬──────────┬──────────┬───────────────────┤
│ Planner │ Controller│ Recovery │ Behavior Tree │
│ Server │ Server │ Server │ Navigator │
├──────────┴──────────┴──────────┴───────────────────┤
│ Costmap 2D (Global + Local) │
├─────────────────────────────────────────────────────┤
│ AMCL / SLAM Toolbox (Localization) │
├─────────────────────────────────────────────────────┤
│ Map Server / Lifecycle Manager │
└─────────────────────────────────────────────────────┘
Конфигурация (nav2_params.yaml):
controller_server:
ros__parameters:
controller_frequency: 20.0
FollowPath:
plugin: "dwb_core::DWBLocalPlanner"
max_vel_x: 0.5
max_vel_theta: 1.0
planner_server:
ros__parameters:
GridBased:
plugin: "nav2_navfn_planner/NavfnPlanner"
tolerance: 0.5
use_astar: true
Манипуляция (MoveIt 2)
from moveit_msgs.action import MoveGroup
from geometry_msgs.msg import Pose
# Через MoveIt Python API
move_group = MoveGroupInterface(node, "arm")
pose_goal = Pose()
pose_goal.position.x = 0.4
pose_goal.position.y = 0.0
pose_goal.position.z = 0.3
pose_goal.orientation.w = 1.0
move_group.set_pose_target(pose_goal)
success = move_group.go(wait=True)
Инструменты
CLI
# Список узлов, топиков, сервисов
ros2 node list
ros2 topic list -t
ros2 service list
# Просмотр сообщений
ros2 topic echo /scan
ros2 topic hz /scan
# Публикация
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \
"{linear: {x: 0.5}, angular: {z: 0.1}}"
# Параметры
ros2 param list /my_node
ros2 param get /my_node max_speed
ros2 param set /my_node max_speed 1.5
# Запись/воспроизведение
ros2 bag record -a -o my_bag
ros2 bag play my_bag
RViz2
Визуализация:
- TF дерево
- PointCloud2
- LaserScan
- Markers
- Costmaps
- Paths
Gazebo
<!-- robot.urdf.xacro -->
<gazebo>
<plugin filename="libgazebo_ros2_control.so" name="gazebo_ros2_control">
<robot_param>robot_description</robot_param>
<robot_param_node>robot_state_publisher</robot_param_node>
<parameters>$(find my_robot)/config/controllers.yaml</parameters>
</plugin>
</gazebo>
Сборка и пакеты
Структура пакета
my_robot/
├── CMakeLists.txt
├── package.xml
├── config/
│ ├── controllers.yaml
│ └── nav2_params.yaml
├── launch/
│ └── my_robot_launch.py
├── src/
│ └── my_node.cpp
├── scripts/
│ └── my_script.py
├── urdf/
│ └── robot.urdf.xacro
└── worlds/
└── my_world.sdf
Сборка (colcon)
cd ~/ros2_ws
colcon build --packages-select my_robot --symlink-install
source install/setup.bash
Рекомендации
- Используйте композицию — запускайте узлы в одном процессе через Composition
- QoS по назначению — RELIABLE для команд, BEST_EFFORT для сенсоров
- Lifecycle nodes — для контролируемого запуска/остановки
- TF2 — всегда публикуйте трансформации
- Параметры через YAML — не хардкодьте значения
Версии ROS 2
| Дистрибутив | EOL | LTS | Ubuntu |
|---|---|---|---|
| Foxy | 2023 | Да | 20.04 |
| Humble | 2027 | Да | 22.04 |
| Iron | 2024 | Нет | 22.04 |
| Jazzy | 2029 | Да | 24.04 |
| Rolling | — | Dev | Latest |
Рекомендация: Jazzy (LTS) или Rolling для новых проектов.
