🤖 ROS 2

Урок 3.4 | Level 3: Инженер

Robot Operating System 2

Что такое ROS?

НЕ операционная система в обычном смысле!

ROS — это:

  • 🔧 Middleware (связующее ПО)
  • 📦 Набор инструментов
  • 📚 Библиотеки для робототехники
  • 🌐 Экосистема пакетов

Почему ROS 2?

ROS 1ROS 2
Только UbuntuLinux, Windows, macOS
Один мастер-процессДецентрализованный (DDS)
Не для реального времениReal-time поддержка
Устарел (до 2025)Активно развивается

Вывод: Учим сразу ROS 2! 🚀

Архитектура ROS 2

Основные концепции

┌───────────────────────────────────────────────────────┐
│                       ROS 2 Graph                     │
│                                                       │
│  ┌──────────┐  topic   ┌──────────┐  topic  ┌──────┐ │
│  │   Node   │─────────►│   Node   │────────►│ Node │ │
│  │ (Camera) │          │(Detector)│         │(Motor)│ │
│  └──────────┘          └────┬─────┘         └──────┘ │
│                             │                        │
│                        service                       │
│                             │                        │
│                       ┌─────▼─────┐                  │
│                       │   Node    │                  │
│                       │(Calibrate)│                  │
│                       └───────────┘                  │
└───────────────────────────────────────────────────────┘

Node (Узел)

Узел — независимый процесс, выполняющий одну задачу:

import rclpy
from rclpy.node import Node

class MyRobot(Node):
    def __init__(self):
        super().__init__('my_robot_node')
        self.get_logger().info('Узел запущен!')

def main():
    rclpy.init()
    node = MyRobot()
    rclpy.spin(node)  # Крутим event loop
    rclpy.shutdown()

if __name__ == '__main__':
    main()

Один узел = одна ответственность

Topic (Топик)

Топик — именованный канал для данных:

Publisher ──► /camera/image ──► Subscriber
             (Image msg)

Publisher ──► /cmd_vel ──► Subscriber
             (Twist msg)

Модель: Pub/Sub (издатель/подписчик)

Пример Publisher

from geometry_msgs.msg import Twist

class VelocityPublisher(Node):
    def __init__(self):
        super().__init__('velocity_publisher')
        
        # Создаём издателя
        self.publisher = self.create_publisher(
            Twist,          # Тип сообщения
            '/cmd_vel',     # Имя топика
            10              # Размер очереди
        )
        
        # Таймер для публикации
        self.timer = self.create_timer(0.1, self.publish_velocity)
    
    def publish_velocity(self):
        msg = Twist()
        msg.linear.x = 0.5   # Вперёд 0.5 м/с
        msg.angular.z = 0.1  # Поворот 0.1 рад/с
        self.publisher.publish(msg)

Пример Subscriber

from sensor_msgs.msg import LaserScan

class LidarSubscriber(Node):
    def __init__(self):
        super().__init__('lidar_subscriber')
        
        # Создаём подписчика
        self.subscription = self.create_subscription(
            LaserScan,          # Тип сообщения
            '/scan',            # Имя топика
            self.scan_callback, # Callback-функция
            10
        )
    
    def scan_callback(self, msg):
        # Обрабатываем данные лидара
        min_distance = min(msg.ranges)
        self.get_logger().info(f'Ближайший объект: {min_distance:.2f} м')

Service и Action

Service (Сервис)

Запрос → Ответ (синхронно):

Client ──► /calibrate_imu ──► Server
           (Request)
           
Client ◄── (Response) ◄─── Server

Пример: калибровка, сброс, получение параметра

Пример Service Server

from std_srvs.srv import Trigger

class CalibrationService(Node):
    def __init__(self):
        super().__init__('calibration_service')
        
        self.srv = self.create_service(
            Trigger,              # Тип сервиса
            '/calibrate',         # Имя сервиса
            self.calibrate_callback
        )
    
    def calibrate_callback(self, request, response):
        self.get_logger().info('Калибрую...')
        # ... выполняем калибровку ...
        
        response.success = True
        response.message = 'Калибровка завершена!'
        return response

Action (Действие)

Для долгих задач с обратной связью:

Client ──► Goal ──► Server
           
Client ◄── Feedback ◄── Server (прогресс...)
Client ◄── Feedback ◄── Server
Client ◄── Feedback ◄── Server
           
Client ◄── Result ◄─── Server (готово!)

Пример: навигация к точке, захват объекта

Диаграмма типов коммуникации

┌─────────────────────────────────────────────────────┐
│                                                     │
│  Topic (Pub/Sub):      A ───────► B                │
│                        Асинхронно, много получателей│
│                                                     │
│  Service (Req/Res):    A ◄──────► B                │
│                        Синхронно, один ответ       │
│                                                     │
│  Action (Goal/FB/Res): A ◄═══════► B               │
│                        Асинхронно, с прогрессом    │
│                                                     │
└─────────────────────────────────────────────────────┘

Практика: первый робот на ROS 2

Установка ROS 2 Humble

# Ubuntu 22.04
sudo apt update && sudo apt install locales
sudo locale-gen en_US en_US.UTF-8

# Добавляем репозиторий
sudo apt install software-properties-common
sudo add-apt-repository universe
sudo apt update && sudo apt install curl -y

curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
  | sudo apt-key add -

sudo sh -c 'echo "deb http://packages.ros.org/ros2/ubuntu jammy main" \
  > /etc/apt/sources.list.d/ros2-latest.list'

sudo apt update
sudo apt install ros-humble-desktop

Настройка окружения

# Добавляем в ~/.bashrc
echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc

# Проверяем
ros2 --version

Создание пакета

# Создаём workspace
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src

# Создаём пакет
ros2 pkg create --build-type ament_python my_robot \
    --dependencies rclpy geometry_msgs

# Структура:
# my_robot/
# ├── my_robot/
# │   └── __init__.py
# ├── package.xml
# ├── setup.py
# └── setup.cfg

Пишем простого робота

# my_robot/simple_robot.py
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan

class SimpleRobot(Node):
    def __init__(self):
        super().__init__('simple_robot')
        
        # Издатель скорости
        self.cmd_pub = self.create_publisher(Twist, '/cmd_vel', 10)
        
        # Подписчик лидара
        self.scan_sub = self.create_subscription(
            LaserScan, '/scan', self.scan_callback, 10)
        
        self.min_distance = float('inf')
        
        # Таймер управления
        self.timer = self.create_timer(0.1, self.control_loop)
    
    def scan_callback(self, msg):
        # Берём только фронтальные лучи (±30°)
        front_ranges = msg.ranges[len(msg.ranges)//2 - 30:
                                  len(msg.ranges)//2 + 30]
        self.min_distance = min(front_ranges)
    
    def control_loop(self):
        cmd = Twist()
        
        if self.min_distance > 0.5:
            # Свободно — едем вперёд
            cmd.linear.x = 0.3
        else:
            # Препятствие — поворачиваем
            cmd.angular.z = 0.5
        
        self.cmd_pub.publish(cmd)

Сборка и запуск

# Сборка
cd ~/ros2_ws
colcon build --packages-select my_robot

# Активация
source install/setup.bash

# Запуск
ros2 run my_robot simple_robot

Полезные команды

# Список узлов
ros2 node list

# Список топиков
ros2 topic list

# Просмотр топика
ros2 topic echo /cmd_vel

# Инфо о топике
ros2 topic info /scan

# Визуализация графа
ros2 run rqt_graph rqt_graph

# Запись данных (rosbag)
ros2 bag record /scan /cmd_vel

# Воспроизведение
ros2 bag play my_recording

Launch-файлы

Зачем?

Запускать много узлов вручную — неудобно!

# Без launch:
ros2 run my_robot lidar_node &
ros2 run my_robot camera_node &
ros2 run my_robot motor_node &
ros2 run my_robot brain_node &
# ... и так далее

Python Launch

# launch/robot_launch.py
from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='my_robot',
            executable='lidar_node',
            name='lidar',
            parameters=[{'scan_frequency': 10.0}]
        ),
        Node(
            package='my_robot',
            executable='motor_node',
            name='motors',
            remappings=[('/cmd_vel', '/robot/velocity')]
        ),
        Node(
            package='my_robot',
            executable='brain_node',
            name='brain'
        ),
    ])

Запуск

ros2 launch my_robot robot_launch.py

Все узлы стартуют одной командой! 🎉

Экосистема ROS 2

ПакетНазначение
Nav2Автономная навигация
MoveIt2Планирование движений манипуляторов
Gazebo3D-симулятор
RViz2Визуализация данных
ros2_controlУправление оборудованием
micro-ROSROS для микроконтроллеров

Практическое задание

Создай ROS 2 пакет для робота:

  1. Узел Publisher: публикует случайные координаты цели
  2. Узел Subscriber: подписывается на цель и логирует
  3. Сервис: возвращает расстояние до цели
  4. Launch-файл: запускает всё вместе

Контрольные вопросы

  1. Чем Topic отличается от Service?
  2. Когда использовать Action вместо Service?
  3. Что такое DDS в ROS 2?
  4. Зачем нужен colcon build?

Подробнее в справочнике

📚 ROS 2 установка
📚 Пакеты ROS 2
📚 Nav2 навигация
📚 Gazebo симулятор

Что дальше?

🎓 Готов к соревнованиям!

Теперь у тебя есть фундамент для:

  • 🏆 WRO (World Robot Olympiad)
  • 🤖 RoboCup
  • 🚁 Drone Racing
  • 🏭 Промышленные проекты

Удачи в сопротивлении восстанию роботов! 💪🤖

Завершение Level 3

         ╔═══════════════════════════════════╗
         ║                                   ║
         ║   🎖️  УРОВЕНЬ 3 ПРОЙДЕН!  🎖️     ║
         ║                                   ║
         ║   Кинематика         ✓           ║
         ║   Sensor Fusion      ✓           ║
         ║   SLAM               ✓           ║
         ║   ROS 2              ✓           ║
         ║                                   ║
         ║   Статус: ИНЖЕНЕР-РОБОТОТЕХНИК   ║
         ║                                   ║
         ╚═══════════════════════════════════╝

← Вернуться к списку уровней