ROS2 на базе робота TurtleBro
Данный курс направлен на изучение базовых аспектов фреймворка ROS (Robot Operating System) версии jazzy (ROS2). Курс разработан для самостоятельного освоения.
Для успешного прохождения курса рекомендуется использовать робот TurtleBro. Материал курса адаптирован специально для этого оборудования.
Для прохождения курса вам понадобятся базовые знания языка python и операционной системы linux.
Каждый модуль курса включает в себя теоретическую часть и практическое задание, направленное на закрепление полученных знаний и навыков.
Желаем вам успешного прохождения курса

Что такое ROS
Что такое ROS и зачем его изучать
Robot Operating System (ROS) - это гибкий фреймворк для написания программного обеспечения для роботов. Это набор инструментов, библиотек и рекомендаций, которые направлены на упрощение задачи создания сложных, но надежных роботов на самых разных роботизированных платформах.
Почему создание роботов сложная задача? Потому что создать действительно надежное, универсальное программное обеспечение для робота трудно. С точки зрения робота, проблемы, которые кажутся тривиальными для людей, часто меняются и сильно зависят от задач и сред. Работа с этими вариациями настолько сложна, что ни один человек, лаборатория или компания не могут сделать это самостоятельно.
В результате ROS был построен с нуля, чтобы стимулировать совместную разработку программного обеспечения для робототехники. Например, одна лаборатория могла иметь специалистов по созданию карт в помещениях и могла бы внести свой вклад в создание системы карт. Другая группа могла состоять из экспертов по использованию карт для навигации, и еще одна группа могла специализироваться на компьютерном зрении, которое хорошо работает для распознавания небольших беспорядочно расположенных объектов. Совместив результаты работы этих групп мы можем получить передовое навигационное программное обеспечение для роботов. ROS и был разработан специально для таких групп, чтобы сотрудничая и опираясь на работу друг друга, они могли бы разрабатывать программное обеспечение мирового уровня.
Целью создания ROS является создание единой платформы, которая позволяет разработчикам роботов сотрудничать на глобальном уровне.
В данный момент ROS можно считать отраслевым стандартом индустрии. ROS используют в своих проектах такие компании как Bosch, BMW, Airbus, Amazon Robotics и NASA
ROS распространяется по модели open source, с лицензией BSD.
Понятие платформы обычно разделяют на программную платформу и аппаратную. Программная платформа для роботов включает в себя набор инструментов, которые используются для разработки ПО.
Можно выделить типовые задачи программной платформы:
- работа с низкоуровневыми устройствами (датчики, моторы);
- аппаратная абстракция;
- коммуникация различный устройств;
- высокоуровневые системы (навигация, распознавание образов);
- управление и установка пакетов и зависимостей;
- подключение системных библиотек;
- инструменты для отладки и разработки.
Аппаратные платформы, включают в себя готовые исследовательские и образовательные устройства. В каталоге роботов используемых ROS (https://robots.ros.org/) сейчас предоставлено более 130 устройств. В данном курсе мы изучим робот семейства Turtle (черепаха) -- робот TurtleBro.
Важно отметить, что аппаратные платформы совместимы с программными платформами, что позволяет разрабатывать прикладные программы не имея опыта работы с оборудованием и не тратя время на его разработку. Совместимость интерфейсов и методов взаимодействия с оборудованием, позволило огромному количеству разработчиков ПО внести свой вклад в развитие робототехники.
Унифицированные интерфейсы и методы работы с устройствами позволяют накапливать и обмениваться готовыми программными решениями всему сообществу специалистов ROS. \
Именно это делает навыки работы с ROS чрезвычайно востребованными, и открывающими новые возможности для разработчиков.
Стоить изучать ROS, если вы
- Хотите построить карьеру в робототехнике, компьютерном зрении или смежных областях.
- Планируете работать в исследовательском проекте, связанном с автономными системами.
- Хотите иметь доступ к огромной экосистеме готовых решений и инструментов.
- Стремитесь понять полный стек технологий современной робототехники.
Курс ROS
Изучение ROS комплексная и сложная учебная задача. В данном курсе мы постараемся сконцентрироваться на подходе, который позволит получить большую часть знаний за минимальное время:
- Изучение только самых важных частей ROS;
- Избегание не нужных вещей, и вещей требующих дополнительной подготовки;
- Проработанные и детальные примеры для конкретного робота;
- Много практики.
Основная цель этого курса
Цель этого курса - дать вам основные инструменты и знания, чтобы вы могли создать базовый проект на ROS.
Вы научитесь: перемещать робота, читать данные датчиков, выполнять комплексные задачи, работать с визуальным представления данных (Pointcloud, LaserScan, IMU), запускать и отлаживать программы.
Курс позволит вам понять как работать с пакетами, которые сделали другие разработчики, а также каким образом возможно их модифицировать.
Во время прохождения курса вы начнете работать с официальной документацией ROS, что позволит вам в дальнейшем самостоятельно искать решения для ваших задач.
Робот TurtleBro
Что вы узнаете в этом разделе:
- Основные характеристики робота TurtleBro
- Как правильно включить и проверить работу робота
- Как контролировать уровень заряда батареи
- Правила техники безопасности при работе с роботом
Знакомство с роботом TurtleBro
Учебный курс предполагает, что вы проходите обучение на уже готовом и настроенном колесном роботе TurtleBro. Такой подход позволит вам начать с изучения ROS наиболее эффективно, от простого к сложному, не тратя время на сборку и создание собственного робота.
Робот TurtleBro создан специально для изучения ROS и прототипирования алгоритмов робототехники.
Документация пользователя к роботу, находится на официальном сайте https://ros2.turtlebro.ru/
Внешний вид робота TurtleBro

Техника безопасности
important
Внимательно изучите раздел техника безопасности при работе с роботом. https://ros2.turtlebro.ru/nachalo-raboty/10-safety
Включение робота
Подключите источник питания или аккумулятор к роботу, согласно инструкции из раздела Первое включение.
Визуальный контроль разряда батареи
Выше чеки безопасности находятся 4 светодиода, сигнализирующие о заряде аккумуляторной батареи. Каждый горящий светодиод сигнализирует о 100% / 75% / 50% / 25% зарядки аккумулятора. При достижении минимального уровня напряжения, робот автоматически выключается. Полного заряда аккумулятора хватает на 2-6 часов работы робота в зависимости от интенсивности движения.
Работа от блока питания
Если не предполагается, что робот будет перемещаться, то лучше подключить робота к стационарному питанию. Для этого необходимо подключить блок питания к сети 220 В., и "запитать" робота через разъем DC-IN.
Настройка рабочего места
Что вы узнаете в этом разделе:
- Какие программы необходимы для работы с ROS
- Как установить SSH-клиент для вашей операционной системы
- Как установить и настроить редактор кода VSCode
- Как настроить сеть для работы с роботом
Ваше рабочее место
Для прохождения базового курса ROS возможно использовать любой компьютер с установленной на нем ОС Windows/Linux/MacOS
Для обучения достаточно установить несколько программ, которые существуют для любой ОС.
Клиент SSH
SSH-клиент — это программа для удалённого подключения к компьютерам. С ее помощью можно управлять компьютерами, без физического доступа к ним. Именно через нее мы будем запускать программы на роботе.
Для Windows Установите Termius или PuTTY
Также возможно использовать встроенные возможности командной строки Windows. Для этого откройте командную строку Win-R и введите cmd и нажмите Enter

Для MacOS
Установите программу iTerm2, или запустите встроенную программу Terminal.

OS базе Linux (Ubuntu, Debian)
Нужно воспользоваться встроенным терминалом SSH.

Для удобства, прикрепите программу Terminal в левое меню программ Pin to Dash
Редактор кода VSCode
Visual Studio Code (VSCode) — это бесплатный и очень популярный редактор кода от Microsoft. Это лёгкий, но невероятно мощный и настраиваемый редактор, в котором удобно работать над проектами любого размера.
Его часто выбирают за идеальный баланс между простотой и функциональностью.
Скачать VSCode можно под любые платформы на сайте https://code.visualstudio.com
Настройка и подключение сети
Мы рекомендуем использовать единую сеть для всех устройств входящих в учебный класс. Желательно наличие интернета в этой сети.
Все роботы по умолчанию настроены для работы в WiFi и Ethernet в режиме клиента с получением настроек по DHCP.
Настройки подключения к WiFi и сети доступны на странице инструкции Настройки сети и SSH
Подключение к роботу
Что вы узнаете в этом разделе:
- Как определить уникальный номер вашего робота
- Как подключиться к роботу по SSH через терминал
- Как узнать IP-адрес робота
- Как использовать web-редактор VSCode для работы с роботом
- Как подключиться к роботу через нативный VSCode
- Как получить доступ к web-панели управления роботом
Идентификация робота
Каждый робот имеет свой уникальный номер. Стикер с номером находится на Ethernet разъёме платы RaspberryPi. На фото ниже указано, что номер робота 16.

Подключение к роботу
Включите робота, и подождите 2 минуты, для того чтобы робот загрузился. Проверьте что вы находитесь в одной сети с роботом. Для надежного соединения, можете подключить робота Ethernet кабелем.
Подключение к роботу по имени
Самый простой способ подключиться к роботу, это подключение к роботу по имени. Для робота с наклейкой 16, имя робота будет turtlebro16.local. Маска имени робота turtlebroNN.local, где NN номер стикера.
Вы можете подключиться к роботу по SSH в программе терминала, или консоли набрав
ssh pi@turtlebro16.local
Если подключение верное, и вы подключаетесь первый раз, то вы увидите предупреждение ОС о первом подключении.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
Необходимо ответить yes.
После этого необходимо указать пароль от SSH робота. Стандартный пароль brobro
Далее вы увидите подключение, похожее на экран ниже.

Для дальнейшей работы с роботом, удобно использовать его IP адрес. Чтобы его узнать наберите в подключенном ssh терминале
ip a
Вы увидите вывод команды

Раздел eth0, inet 192.168.51.83, это IP адрес в Ethernet подключении.
Раздел wlan0, inet 192.168.51.84 это IP адрес в WiFi подключении.
Если не работает подключение по имени
Если подключится к роботу по имени не получилось, вы можете зайти панель управления роутера и посмотреть подключения к сети, чтобы узнать IP вашего робота.
Например для роутера ASUS

Подключение к web-редактору кода VSCode
Для задач быстрого прототипирования, есть возможность использовать web-редактор VSCode, который установлен на образ робота.
Для этого необходимо зайти в браузере на страницу http://192.168.51.83:8090 (указав IP вашего робота).
Пароль для доступа к редактору brobro

Во внешнем виде WEB-редактора VSCode мы видим структуру файлов робота. Файлы, созданные в редакторе, будут создаваться в файловой системе робота.
Подключение через "нативный" VSCode
Если функционал web редактора не устраивает, то есть возможность подключится к роботу через VSCode по ssh.

Запустите VSCode. Нажмите на синии скобочки в низу редактора, выберите Connect to Host.
Укажите ssh хост робота, например pi@192.168.51.81 Дождитесь установки плагина, нажмите "Open Folder", выберите директорию которую необходимо подключить, для доступа к файлам.
Также в разделе расширений VSCode проверьте, что установлено расширение для работы с python непосредственно для подключения. Некоторые расширения требуют дополнительной установки на удаленные системы.
Пример подключения расширения python. Мы видим подсказку для методов python для работы с ROS.

important
Если у вас используется RaspberryPi с менее чем 4Gb памяти, для включения расширения python необходимо подключить дополнительную память, активировав swap . Это можно сделать командой
sudo ./extra/scripts/swap_on.sh
Подключение к web панели робота
Для доступа к камере, и управлением роботом, можно зайти на web интерфейс робота, http://192.168.51.81:8080 указав IP вашего робота.
Дополнительные материалы
Для успешного прохождения учебного курса по ROS, вам необходимо научиться работать с Linux.
Есть множество курсов и книг по изучению этой операционной системы. Мы рекомендуем пройти один из предложенных курсов. Начать прохождение курсов по Linux можно одновременно с учебной программой ROS.
Вы можете выбрать любой курс, все курсы бесплатные:
Подготовка к базовому курсу ROS
http://learn.voltbro.ru/free/ros-intro/
Учебные материалы максимально адаптированные для начала изучения ROS, обязательные для изучения.
Основы командной строки, компании hexlet
https://ru.hexlet.io/courses/cli-basics
Командная строка — это первое, с чем сталкивается программист, работающий на *NIX системах, например, Linux или Macos. Ее значение невозможно переоценить, она является основным способом взаимодействия с системой и способом управления множеством программ.
Время прохождения курса: около 30 часов
Online курс "Введение в Linux" на stepik.org
Время прохождения курса: 14 часов, курс содержит видеоматериалы.
Практические задания
Задание 1: Настройка рабочего места
Шаги:
- Установите VSCode и SSH-клиент для вашей ОС
- Включите робота и дождитесь загрузки (2 минуты)
- Определите IP робота через панель роутера или по имени
turtlebroNN.local - Откройте web-интерфейс:
http://IP_РОБОТА:8080
Задание 2: Подключение к роботу
Шаги:
- Подключитесь по SSH:
ssh pi@turtlebroNN.local(пароль:brobro) - Откройте web-редактор:
http://IP_РОБОТА:8090(пароль:brobro) - Подключитесь через VSCode по SSH (расширение Remote-SSH)
Задание 3: Работа с файлами
Шаги:
- Создайте файл
/home/pi/myrobot.txtс IP-адресом робота - Выполните
ros2 topic listи сохраните вывод вtopics.txt
Система сообщений ROS
Что вы узнаете в этом разделе:
- Что такое сообщения в ROS и зачем они нужны
- Какие типы сообщений доступны в ROS
- Как просмотреть список всех доступных сообщений
- Как изучить структуру конкретного сообщения
- Как создавать и использовать сообщения в Python
Сообщения ROS
Первое фундаментальное понятие, с которым нам надо познакомиться, это Сообщение. Создавая любую программу, мы рано или поздно начинаем сталкиваться с проблемой обмена данных между разными частями программ или разными программами.
В ROS - данные, которые мы хотим передавать или получать, объединяются одной конкретной сущностью - Сообщением (message).
Аналогия из жизни: Сообщение в ROS можно сравнить с письмом или посылкой. Как письмо имеет определенный формат (конверт, адрес, содержимое), так и сообщение ROS имеет строго определенную структуру и тип данных. Например, сообщение о температуре — это как термометр, который показывает только температуру, а не влажность или давление. Каждое сообщение имеет свой "формат" (структуру), который все участники системы понимают одинаково.
Например, если мы говорим "мы получаем данные датчика температуры", то в логике ROS это означает, что мы получаем сообщение, содержащее данные о температуре.
Важно уточнить, что любое сообщение описывает определенный и заранее заданный набор данных - структуру, и их тип. Для сообщений от датчика температуры мы можем представить данные и их тип как одну переменную типа float. В общем случае сообщения могут содержать довольно сложные структуры данных, и также включать в себя "другие" сообщения.
Типовые сообщения ROS, находятся в пакете std_msgs. Мы можем посмотреть все типы сообщений этого пакета.
ros2 interface package std_msgs
====
std_msgs/msg/MultiArrayLayout
std_msgs/msg/Byte
std_msgs/msg/MultiArrayDimension
......
std_msgs/msg/Float64MultiArray
std_msgs/msg/UInt8
std_msgs/msg/Int16
std_msgs/msg/UInt32
std_msgs/msg/Float32
Необходимое нам сообщение имеет имя std_msgs/msg/Float32
Если рассмотреть код на python, то создание сообщения будет выглядеть вот так
from std_msgs.msg import Float32
msg = Float32()
msg.data = 22.10
Все сообщения доступные в системе, мы можем посмотреть командой
ros2 interface list
Посмотреть информацию о структуре сообщения возможно используя параметр show и имя типа сообщения. Для данных датчика IMU (инерциальный датчик) мы получим структуру сообщения как представлено ниже.
ros2 interface show sensor_msgs/msg/Imu
====
std_msgs/Header header
builtin_interfaces/Time stamp
int32 sec
uint32 nanosec
string frame_id
geometry_msgs/Quaternion orientation
float64 x 0
float64 y 0
float64 z 0
float64 w 1
float64[9] orientation_covariance # Row major about x, y, z axes
geometry_msgs/Vector3 angular_velocity
float64 x
float64 y
float64 z
float64[9] angular_velocity_covariance # Row major about x, y, z axes
geometry_msgs/Vector3 linear_acceleration
float64 x
float64 y
float64 z
float64[9] linear_acceleration_covariance # Row major x, y z
Мы видим тут отдельный блоки данных header, orientation, angular_velocity, linear_acceleration содержащий более 20 переменных.
Топик, Издатель (Publisher)
Что вы узнаете в этом разделе:
- Что такое топики, издатели и подписчики в ROS
- Как работать с топиками через консоль (CLI)
- Как просмотреть список всех топиков робота
- Как публиковать сообщения в топики через командную строку
- Как получать и просматривать данные из топиков
- Основные команды утилиты
ros2 topic
Базовые сущности ROS
В ROS программы (Ноды), отправляющие сообщения, принято называть Издатель (Publisher), а программы, которые получают данные, принято называть Подписчик (Subscriber). При этом в архитектуре ROS - Подписчик и Издатель могут быть как разные программы, запущенные на одном компьютере, так и разные программы, запущенные на разных устройствах.
Связь подписчика и издателя происходит через единый для них Топик (Topic) (тема), в который издатель отправляет сообщения, а подписчик получает. Один топик от другого отличается именем, и типами сообщений, которые топики могут передавать.
Аналогия из жизни: Представьте топик как почтовый ящик или канал на YouTube. Издатель (Publisher) — это тот, кто кладет письма в ящик или публикует видео на канале. Подписчик (Subscriber) — это тот, кто забирает письма из ящика или подписывается на канал и смотрит видео. Несколько подписчиков могут читать одни и те же письма или смотреть одно и то же видео. Топик — это сам ящик или канал, который имеет название (например, "/температура" или "/скорость") и определенный тип контента (только письма о температуре, только видео о скорости).
Иллюстрация работы Издателя, Подписчика и Топика.

Работа с топиками через консоль (cli)
Познакомиться с работой топиков проще всего через консоль, используя специальные утилиты. Такой способ взаимодействия обычно называют cli (command line interface).
CLI (Command Line Interface) — интерфейс командной строки, текстовый пользовательский интерфейс, который используется для взаимодействия с операционной системой или программным обеспечением компьютера путём ввода команд в консоли или терминале
Для работы с топиками, в ROS есть утилита ros2 topic. Основные команды
ros2 topic list – показать все существующие топики;
ros2 topic pub – ручная публикация сообщений;
ros2 topic echo – «эхо», то есть прослушивание топика в реальном времени;
Если выполнить команду ros2 topic list, то мы получим список топиков робота (не забудьте подключиться по ssh к роботу)
ros2 topic list
====
/backlight/all
/backlight/array
/bat
/client_count
/cmd_vel
...
/pose2d
/robot_description
/rosout
/scan
/tf
/tf_static
Публикация сообщений в топики
Для публикации данных в топик, воспользуемся утилитой
ros2 topic pub <имя_топика> <тип_сообщения> <данные_сообщения>
Запустим команду на роботе
ros2 topic pub /temp std_msgs/msg/Float32 "{data: '21.10'}"
И увидим вывод работы программы, что говорит нам о том, что данные начали публиковаться.
publisher: beginning loop
publishing #1: std_msgs.msg.Float32(data=21.1)
publishing #2: std_msgs.msg.Float32(data=21.1)
publishing #3: std_msgs.msg.Float32(data=21.1)
publishing #4: std_msgs.msg.Float32(data=21.1)
Получение сообщений
Для получения данных из топика, воспользуемся командой ros2 topic echo <имя_топика>
Запустим в новом окне терминала команду подписки на топик /temp. Новое окно терминала нам необходимо для того, чтобы мы могли запустить две команды Издателя и Подписчика одновременно.
ros2 topic echo /temp
Получим вывод работы программы
data: 21.100000381469727
---
data: 21.100000381469727
---
data: 21.100000381469727
---
data: 21.100000381469727
Этим примером мы показали как просто используя консоль создавать Издателей и Подписчиков, и проверять данные которые публикуются в топик.
tip
Для завершения работы программы, нажмите Ctrl+C.
Утилита ros2 topic
Для получения справки и параметров утилиты ros2 topic необходимо запустить утилиту без параметров. Например получить список всех команд и их краткое описание.
ros2 topic
====
usage: ros2 topic [-h] [--include-hidden-topics] Call `ros2 topic <command> -h` for more detailed usage. ...
Various topic related sub-commands
options:
-h, --help show this help message and exit
--include-hidden-topics
Consider hidden topics as well
Commands:
bw Display bandwidth used by topic
delay Display delay of topic from timestamp in header
echo Output messages from a topic
find Output a list of available topics of a given type
hz Print the average receiving rate to screen
info Print information about a topic
list Output a list of available topics
pub Publish a message to a topic
type Print a topic's type
Call `ros2 topic <command> -h` for more detailed usage.
Для получения справки команды, необходимо указать название команды без параметра. Например
ros2 topic pub
====
usage: ros2 topic pub [-h] [--stdin] [-r N] [-p N] [-1 | -t TIMES] [-w WAIT_MATCHING_SUBSCRIPTIONS] [--max-wait-time-secs MAX_WAIT_TIME_SECS] [--keep-alive N]
[-n NODE_NAME]
[--qos-profile {unknown,default,system_default,sensor_data,services_default,parameters,parameter_events,action_status_default,best_available}]
[--qos-depth N] [--qos-history {system_default,keep_last,keep_all,unknown}]
[--qos-reliability {system_default,reliable,best_effort,unknown,best_available}]
[--qos-durability {system_default,transient_local,volatile,unknown,best_available}]
[--qos-liveliness {system_default,automatic,manual_by_topic,unknown,best_available}]
[--qos-liveliness-lease-duration-seconds QOS_LIVELINESS_LEASE_DURATION_SECONDS] [--spin-time SPIN_TIME] [-s]
topic_name message_type [values]
ros2 topic pub: error: the following arguments are required: topic_name, message_type
Более подробно о топиках, можно посмотреть в официальной документации Understanding-ROS2-Topics
Топик, Издатель (Python)
Что вы узнаете в этом разделе:
- Как создать программу-издатель на Python
- Структуру базовой ROS2 программы на Python
- Как читать данные из системных файлов (температура CPU)
- Как публиковать данные в топик через Python
- Как использовать таймеры для периодической публикации
- Как проверить работу программы через консольные утилиты
Программируем на Python
Для быстрого старта мы начнем писать программы, без создания специального ROS-пакета, создание пакетов и "упаковывание" в них наши программ, мы рассмотрим позже.
Мы будем разрабатывать программы на языке
Python. Это один из основных языков разработки фреймворка ROS, который позволяет довольно легко создавать собственные программы для ROS.
Ранее мы обсудили некоторую условность, что все данные передаются через сообщения, в специальные топики. Давайте создадим программу Издатель, которая будет получать данные о температуры нашего процессора и передавать их в топик. Пускай это будет топик temp
Перед началом работы создайте на роботе папку ros2-base, в которой будете сохранять поурочно программы, которые будете изучать.
Создадим программу, которая публикует данные о температуре CPU. За основу возьмем пример официальной документации
#! /usr/bin/env python3
import rclpy
from rclpy.node import Node
from std_msgs.msg import Float32
class MinimalPublisher(Node):
def __init__(self):
super().__init__('minimal_publisher')
self.publisher = self.create_publisher(Float32, 'temp', 10)
timer_period = 0.5 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
def timer_callback(self):
msg = Float32()
msg.data = self.getCPUTemp()
self.publisher.publish(msg)
self.get_logger().info(f'Publishing CPU temp: {msg.data}')
def getCPUTemp(self):
data = open('/sys/class/thermal/thermal_zone0/temp', 'r').read()
return round(float(int(data)/1000.0),1)
def main(args=None):
rclpy.init(args=args)
minimal_publisher = MinimalPublisher()
try:
rclpy.spin(minimal_publisher)
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()
Пока не особо вдаваясь в подробности как именно работает программа, скопируем ее код в Visual Studio Code и сохраним его на вашем роботе (именно на нем мы хотим считать температуру) в файл/home/pi/ros2-base/chapter2/temp_topic_publisher.py
Запустим .py файл через ssh
ssh pi@turtlebro01.local
cd ros2-base/chapter2
python3 temp_topic_publisher.py
===
python3 publisher.py
[INFO] [1756736307.190868093] [minimal_publisher]: Publishing CPU temp: "52.600000"
[INFO] [1756736307.608446372] [minimal_publisher]: Publishing CPU temp: "52.100000"
[INFO] [1756736308.108523745] [minimal_publisher]: Publishing CPU temp: "52.100000"
Мы видим в логах программы, что происходит публикация данных о температуре в топик.
Не закрывая программу, запустим новое подключение по ssh к роботу и удостоверимся, что данные действительно публикуются.
Первым делом выполним команду ros2 topic list
ros2 topic list
===
/backlight/all
/backlight/array
/bat
-----
/rosout
/scan
/temp
/tf
/tf_static
Мы видим топик temp. Уже хорошо. Посмотрим что в нем
ros2 topic echo /temp
====
data: 52.599998474121094
---
data: 51.599998474121094
Мы видим данные, которые публикует программы, и видим изменение температуры в каждом сообщении. Значит у нас получилось правильно запустить и создать программу Издатель.
tip
Для завершения работы программы, нажмите Ctrl+C.
Разбор программы Издателя
Получение данных температуры
Значение температуры Raspberry Pi "публикует" в "специальный" файл:/sys/class/thermal/thermal_zone0/temp Для того, чтобы получить это значение мы должны прочитать его из этого файла стандартными средствами Python.
data = open('/sys/class/thermal/thermal_zone0/temp', 'r').read()
Значение температуры в данном файле записано в миллиградусах, поэтому, чтобы получить более привычные нам градусы Цельсия, разделим это значение на 1000.
Поэтому, чтобы получить температуру, нам необходимо прочитать файл и перевести единицы измерения. Данные код реализован в функции getCPUTemp()
Разбор программы Издателя
Сначала, рассмотрим "скелет" программы. Это то, что нам понадобится в любой программе на ROS.
import rclpy
from rclpy.node import Node
from std_msgs.msg import Float32
class MinimalPublisher(Node):
#ОСНОВНОЙ КОД
def main(args=None):
rclpy.init()
minimal_publisher = MinimalPublisher()
try:
rclpy.spin(minimal_publisher)
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()
Это относительно "стандартная" обертка для инициализации ROS Ноды.
#Подключение стандартных библиотек, и типов данных Float32
import rclpy
from rclpy.node import Node
from std_msgs.msg import Float32
Создание класса нашей ноды, через наследование от rclpy.node
class MinimalPublisher(Node):
Создание ROS ноды и ожидание прерывания выполнения от пользователя, выполняет код:
#Инициализация библиотеки rclpy
rclpy.init()
#создание объекта "рабочей" ноды
minimal_publisher = MinimalPublisher()
#блок ожидания прерывания от пользователя (для контролируемого выхода из программы) и передача управления рабочего цикла ноды.
try:
rclpy.spin(minimal_publisher)
except KeyboardInterrupt:
pass
Соберем этот код в функции main(), и запустим функцию main() при запуске python скрипта.
Такой способ запуска созданных классов, рекомендуется стандартами python, и будет нам необходим, когда мы будем создавать пакеты ROS.
if __name__ == '__main__':
main()
Основная "смысловая" работа с ROS происходит в блоке двух методов. Методе инициализации при создании класса -- метод __init__(self).
def __init__(self):
#инициализация ноды, через инициализацию родительского класса.
super().__init__('minimal_publisher')
#создание объекта publisher, которые будет публиковать данные.
# в парамерах мы указываем тип сообщения, имя топика и размер очереди
self.publisher = self.create_publisher(Float32, 'temp', 10)
#создаем таймер, который с определенной частотой
#вызывает функцию, которая получает данные температуры
#и публикует данные в топик timer_callback.
timer_period = 0.5 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
Подробная документация по методам create_publisher и create_timer из официальной API документации.
И функции, которая непосредственно публикует данные timer_callback(self)
def timer_callback(self):
#создание пустого сообщения
msg = Float32()
#чтение данных о температуры CPU
msg.data = self.getCPUTemp()
#публикация данных в топик
self.publisher.publish(msg)
# Вывод данных через систему логов
self.get_logger().info(f'Publishing CPU temp: {msg.data}')
Финальный пример программы, доступен в разделе примеры на GitHubturtlebro2-examples
Официальную инструкцию к пакету rclpy, можно посмотреть на сайте https://docs.ros.org/en/rolling/p/rclpy/.
Примеры использования библиотек https://github.com/ros2/examples/tree/humble
Практические задания
Задание 1: Работа с сообщениями
Шаги:
- Используйте
ros2 interface listдля поиска подходящих сообщений - Для каждого устройства подберите тип сообщения:
- Датчик давления воздуха (в Паскалях, с точностью до 3-го знака после запятой);
- Ультразвукового сонара (в мм);
- Управление сервоприводом (углы в градусах);
- Цвет RGB светодиода
Задание 2: Топик, Издатель (CLI)
Шаги:
- Изучите команды:
ros2 topic list,ros2 topic echo,ros2 topic info - Просмотрите данные топиков:
/imu,/scan,/bat - Измените частоту в примере урока:
timer_period = 0.2(5 Гц)
Задание 3: Топик, Издатель (Python)
Шаги:
- Создайте скрипт
counter_publisher.pyс таймером 0.2 сек - Используйте
std_msgs/msg/Int32, счетчик увеличивайте вtimer_callback - Запустите скрипт и проверьте:
ros2 topic echo /counter - Проверьте частоту:
ros2 topic hz /counter(должно быть ~5 Гц)
Передвижение робота
Что вы узнаете в этом разделе:
- Как проверить работоспособность робота через web-интерфейс
- Как управлять роботом через топик
/cmd_vel - Структуру сообщения
geometry_msgs/msg/Twist - Как заставить робота двигаться прямо, поворачивать и останавливаться
- Как использовать автодополнение при работе с командами ROS2
- Правила безопасности при управлении роботом
Используем web-интерфейс
Для начала, начнем с самого простого. Проверим что робот вообще может двигаться.
Проще всего это сделать, если зайти на web-сервер робота и при помощи клавиш AWSD "поездить" роботом.
Веб интерфейс доступен в браузере, по IP адресу вашего робота на порту 8080 например:
http://192.168.1.100:8080/
Как управлять роботом
У нашего робота (и многих других), управление перемещением происходит при помощи публикации специального сообщения, в котором мы указываем параметры желаемой скорости, а именно ее угловую и линейную составляющую. Эти данные из топика получает Подписчик, который работает на системной плате робота, и далее отдает команды на моторы колес. Для того чтобы наш робот начал движение, нам достаточно опубликовать правильное сообщение в нужный топик.
important
Робот продолжает выполнять последнюю полученную команду до момента, пока не получит новую. Даже если вы остановите публикацию в топик, робот продолжит движение согласно последней полученной команде.
Для остановки робота необходимо отправить данные с нулевыми скоростями, или нажать на роботе кнопку stop на плате, рядом с правым колесом
В инструкции указано, что робот выполняет команды, которые публикуются в топик /cmd_vel. Давайте поймем какой вид сообщения нам необходимо сформировать для этого топика.
Выполним команду
ros2 topic info /cmd_vel
====
Type: geometry_msgs/msg/Twist
Publisher count: 0
Subscription count: 1
Мы видим, что для движения робота необходимо использовать сообщение типа geometry_msgs/msg/Twist
Сообщение geometry_msgs/msg/Twist
Управление роботом, происходит при помощи публикации в топик /cmd_vel сообщений типа geometry_msgs/msg/Twist содержащих два трехмерных вектора (x,y,z), вектор линейной скорости (скорости движения точки центра робота) и угловой скорости (скорости вращения робота вокруг оси проходящей через центр лидара робота).
Такое сообщение подходит для управления большинством колесных роботов под управлением ROS.
Давайте посмотрим какие параметры содержит это сообщение.
Выполним команду:
ros2 interface show geometry_msgs/msg/Twist
===
Vector3 linear
float64 x
float64 y
float64 z
Vector3 angular
float64 x
float64 y
float64 z
Мы видим в сообщении два вектора linear и angular. Значение переменной linear.x соответствует линейной скорости робота по оси X (движение прямо). Значение переменной angular.z соответствует вращению робота вокруг оси Z.
Для определения направления вращения робота, нужно пользоваться правилом буравчика. Также на рисунке ниже показаны направления осей X Y Z робота.

Для поворота робота против часовой стрелки, значение угловой скорости по оси Z должно быть положительным, для поворота по часовой стрелке - отрицательным.
Движение робота
Проще всего заставить двигаться робота -- это воспользоваться командой topic pub утилиты ros2, которая позволяет просто отправлять сообщения в топик ROS прямо из терминала.
Выполним команду в терминале робота:
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "linear:
x: 0.1
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.0"
Мы увидим вывод работы программы, а робот начнет движение прямо по оси X
publisher: beginning loop
publishing #1: geometry_msgs.msg.Twist(linear=geometry_msgs.msg.Vector3(x=0.1, y=0.0, z=0.0), angular=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.0))
publishing #2: geometry_msgs.msg.Twist(linear=geometry_msgs.msg.Vector3(x=0.1, y=0.0, z=0.0), angular=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.0))
publishing #3: geometry_msgs.msg.Twist(linear=geometry_msgs.msg.Vector3(x=0.1, y=0.0, z=0.0), angular=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.0))
publishing #4: geometry_msgs.msg.Twist(linear=geometry_msgs.msg.Vector3(x=0.1, y=0.0, z=0.0), angular=geometry_msgs.msg.Vector3(x=0.0, y=0.0, z=0.0))
Если все правильно -- робот начнет движение вперед со скоростью 0.1 м/с.
Для остановки робота, нужно снова отправить "скорость", но с нулевыми значениями.
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "linear:
x: 0.0
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.0"
Также можно использовать "укороченный" синтаксис, указав только необходимые параметры
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "{linear: {x: 0.0}}"
Для того чтобы повторно не вбивать команду, достаточно нажать на клавиатуре кнопку "вверх" и отредактировать параметры предыдущей команды. {% endhint %}
Также на роботе находится "чека" в виде небольшого провода красного цвета, извлечение чеки отключает питание от моторов. Использовать чеку необходимо в экстренных случаях.
Для движения робота по кругу, нам необходимо одновременно задать линейную и угловую скорости.
Выполним команду:
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist "linear:
x: 0.0
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 1.0"
Робот начнет вращение вокруг своей оси.
Использование автодополнения (autocomplete)
Вы можете использовать автодополнение (autocomplete) при работе с утилитой ros2 topic pub
Подстановка подходящего имени топика используя <TAB>
ros2 topic pub /cm<TAB> -> ros2 topic pub /cmd_vel
Подстановка типа сообщения <TAB>
ros2 topic pub /cmd_vel g<TAB> -> ros2 topic pub /cmd_vel geometry_msgs/msg/Twist
Подстановка структуры необходимого сообщения \'<TAB>
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist \'<TAB> ->
ros2 topic pub /cmd_vel geometry_msgs/msg/Twist 'linear:
x: 0.0
y: 0.0
z: 0.0
angular:
x: 0.0
y: 0.0
z: 0.0
'
Передвижение робота(Python)
Что вы узнаете в этом разделе:
- Как создать программу для управления движением робота на Python
- Как правильно остановить робота при завершении программы
- Как работать со временем в ROS2 программах
- Как заставить робота двигаться в течение заданного времени
- Как обрабатывать прерывания программы (Ctrl+C)
Пример программы движения робота
В прошлой главе, мы создали Издателя, который публиковал данные температуры. Также мы знаем, что для управления роботом, необходимо отправить сообщение Twist в топик cmd_vel
Рассмотрим простой пример программы, которая заставляет робота передвигать прямо.
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from geometry_msgs.msg import Twist
import time
class MoveStraightNode(Node):
def __init__(self):
super().__init__('move_straight_node')
# Создаем publisher для топика /cmd_vel
self.publisher = self.create_publisher(Twist, '/cmd_vel', 10)
# Создаем таймер для периодической отправки команд
timer_period = 0.5 # секунды (2 Гц)
self.timer = self.create_timer(timer_period, self.timer_callback)
self.get_logger().info('Move straight node started')
def timer_callback(self):
# Создаем сообщение Twist для движения прямо
msg = Twist()
# Линейная скорость по оси X (вперед)
msg.linear.x = 0.2 # м/с - скорость движения вперед
msg.linear.y = 0.0
msg.linear.z = 0.0
# Угловая скорость (вращение) - 0 для движения прямо
msg.angular.x = 0.0
msg.angular.y = 0.0
msg.angular.z = 0.0
# Публикуем сообщение
self.publisher.publish(msg)
# Логируем отправленную команду (опционально)
self.get_logger().info(f'Moving straight with speed: {msg.linear.x} m/s')
def main(args=None):
rclpy.init(args=args)
move_straight_node = MoveStraightNode()
try:
rclpy.spin(move_straight_node)
except (KeyboardInterrupt):
pass
if __name__ == '__main__':
main()
Сохраним программу в файл /home/pi/ros2-base/chapter3/publisher_cmd_vel.py и запустим его.
python3 publisher_cmd_vel.py
[INFO] [1757341419.088200400] [move_straight_node]: Move straight node started
[INFO] [1757341419.572211300] [move_straight_node]: Moving straight with speed: 0.2 m/s
[INFO] [1757341420.072226781] [move_straight_node]: Moving straight with speed: 0.2 m/s
[INFO] [1757341420.572120985] [move_straight_node]: Moving straight with speed: 0.2 m/s
[INFO] [1757341421.072242984] [move_straight_node]: Moving straight with speed: 0.2 m/s
[INFO] [1757341421.572182373] [move_straight_node]: Moving straight with speed: 0.2 m/s
[INFO] [1757341422.072247891] [move_straight_node]: Moving straight with speed: 0.2 m/s
Мы видим что робот начал движение прямо.
Остановка робота
Если мы прервем работу программы, робот продолжит движение. Это не очень удобно.
Мы ожидаем, что если мы остановили программу движения робота, то робот тоже должен остановиться.
Для начала, нам необходима функция остановки робота, которая установит все скорости робота в нулевые значения.
Например, функция может выглядеть так
def stop_robot(self):
self.get_logger().info('Stopping robot')
#Остановить робота, опубликовать пустое сообщение
stop_msg = Twist()
self.publisher.publish(stop_msg)
И второе, нам необходимо "поймать" выход из программы и перед ее завершением вызвать функцию stop_robot
Для этого нем необходимо немного переписать блок инициализации ноды, отключив "стандартный" обработчик выхода, указав signal_handler_options для метода инициализации rclpy.init()
def main(args=None):
rclpy.init(args=args, signal_handler_options=SignalHandlerOptions.NO)
move_straight_node = MoveStraightNode()
try:
rclpy.spin(move_straight_node)
except (KeyboardInterrupt):
move_straight_node.get_logger().info('Program interruption by user')
# Остановим робота
move_straight_node.stop_robot()
finally:
# Уничтожаем узел
move_straight_node.destroy_node()
# Завершаем работу ROS2
rclpy.try_shutdown()
Работа со временем
Часто нам необходимо, чтобы робот выполнял какую-либо работу по времени. Например, мы хотим чтобы робот ехал прямо 5 секунд и после останавливался. Останавливаться мы уже умеем, значит нам необходимо "правильно" посчитать время.
Для начала, создадим две переменных. В одной будем хранить время старта программы (self.start_time), а в другой необходимое время движения (self.duration).
import time
def __init__(self):
####
self.start_time = time.time()
self.duration = 5.0
Также изменим функцию timer_callback, добавив в нее логику проверки времени.
def timer_callback(self):
# Создаем сообщение Twist для движения прямо
msg = Twist()
#Проверяем время, если условие выполняется, продолжаем движение
#Если нет, то выполняем остановку робота и выход из программы
if time.time() - self.start_time < self.duration:
msg.linear.x = 0.2 # м/с - скорость движения вперед
self.publisher.publish(msg)
self.get_logger().info(f'Moving straight with speed: {msg.linear.x} m/s')
else:
self.get_logger().info(f'Stoping robot after {self.duration} sec mooving')
self.stop_robot()
rclpy.try_shutdown()
Финальный примеры программы, доступны в разделе примеры на GitHub turtlebro2-examples
Практические задания
Задание 1: Движение по кругу
Шаги:
-
Создайте
cmd_vel1_publisher.pyс одновременной линейной (0.16 м/с) и угловой (2 рад/с) скоростью -
Запустите программу и засеките время полного круга
-
Добавьте остановку робота после завершения
-
Напишите программу на
python(cmd_vel3_publisher.py), которая заставит робота двигаться по заданному алгоритму:- Движение вперед в течение 5 секунд со скоростью 0.1 м/с
- Разворот на 180 градусов (рассчитать время разворота робота)
- Движение в течение 5 секунд вперед (обратно к точке старта)
- Разворот на 180 градусов (в стартовое положение)
- Выход из программы
В итоге робот должен оказаться около точки старта в том же положении в котором стартовал.
Подписчик (Python)
Что вы узнаете в этом разделе:
- Как создать программу-подписчик на Python
- Как подписаться на топик и получать данные
- Структуру программы-подписчика в ROS2
- Как обрабатывать входящие сообщения в callback-функции
- Как проверить работу подписчика вместе с издателем
- Разницу между издателем и подписчиком
Программа подписчик на Python
В прошлой части, мы создали пример на python программы Издателя. В этой части мы разберем как самостоятельно создать программу Подписчика.
Аналогия из жизни: Если издатель — это радиовещательная станция, которая постоянно передает сигнал, то подписчик — это радиоприемник, который настраивается на нужную частоту и слушает передачи. Подписчик не может контролировать, когда придут данные, он просто ждет и обрабатывает их, когда они появляются. Это похоже на подписку на новостную рассылку: вы подписываетесь один раз, а затем автоматически получаете все новые письма.
Мы создадим нашу программу на основе официальной документации. Наш подписчик подпишется на топик temp и выведет полученную информацию на экран.
Сохраним код из примера в файл в файл /home/pi/ros2-base/chapter4/temp_topic_subscriber.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import Float32
class MinimalSubscriber(Node):
def __init__(self):
super().__init__('minimal_subscriber')
self.subscription = self.create_subscription(
Float32,
'temp',
self.listener_callback,
10)
def listener_callback(self, msg):
self.get_logger().info(f'CPU temp: {msg.data}')
def main(args=None):
rclpy.init(args=args)
minimal_subscriber = MinimalSubscriber()
try:
rclpy.spin(minimal_subscriber)
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()
Запустим программу Подписчик
ssh pi@turtlebro01.local
cd ros2-base/chapter4
python3 temp_topic_subscriber.py
Мы увидим, что ничего не происходит (данные не выводятся). Но в топик temp никто не публикует данные, поэтому запустим пример Издателя из прошлого урока.
ssh pi@turtlebro01.local
cd ros2-base/chapter2
python3 temp_topic_publisher.py
После запуска Издателя, в окне терминала с Подписчиком, начнут выводится полученные данные
[INFO] [1756820712.936384050] [minimal_subscriber]: I heard: "50.099998474121094"
[INFO] [1756820712.939289173] [minimal_subscriber]: I heard: "50.599998474121094"
[INFO] [1756820713.391311101] [minimal_subscriber]: I heard: "50.099998474121094"
[INFO] [1756820713.890021376] [minimal_subscriber]: I heard: "50.099998474121094"
[INFO] [1756820714.390069102] [minimal_subscriber]: I heard: "50.099998474121094"
[INFO] [1756820714.890540083] [minimal_subscriber]: I heard: "51.099998474121094"
[INFO] [1756820715.390026962] [minimal_subscriber]: I heard: "49.70000076293945"
Мы убедились, что наш пример работает. При этом у нас получилось, что одна наша программа передает данные температуры, а другая программа их получает и выводит на экран.
Также мы можем опубликовать данные в топик через консольную утилиту, и убедиться что наш Подписчик также получит данные.
ros2 topic pub /temp std_msgs/msg/Float32 "data: 55.0"
Аналогичным образом функционирует большинство сложных программ на роботе. Один программы получают данные и передают их другим программам, для дальнейшей обработки. И так много-много раз.
Разбор программы Подписчика
Представленный код, очень похож на пример Издателя, который мы разбирали в прошлом части. Блок "скелета" приложения ROS вообще не отличается.
Рассмотрим только функции __init__(self): и listener_callback(self, msg)
Первая, это код инициализации ноды подписчика __init__(self):
#инициализация ноды, через инициализацию родительского класса.
#в параметрах, указываем имя ноды.
super().__init__('minimal_subscriber')
#создание подписчика. В атрибутах мы указываем, Float32 -- тип сообщения
#temp -- название топика
#self.listener_callback -- функция которая должна вызываться когда в топике
#появились данные. self. в начале, указывает что функция принадлежит классу
#10 -- размер очереди
self.subscription = self.create_subscription(
Float32,
'temp',
self.listener_callback,
10)
Подробная документация по методам create_subscription из официальной API документации.
Функция обработчик полученных данных из топика listener_callback(self, msg)
# Параметре функции msg находится сообщение из топика
# необходимого нам типа
def listener_callback(self, msg):
# выводим сообщение о температуре через сообщение в логи
self.get_logger().info(f'CPU temp: {msg.data}')
Финальный пример программы, доступен в разделе примеры на GitHub turtlebro2-examples
Дополнительные материалы
Подробнее о примере Издателя и Подписчика, в официальной документации: http://docs.ros.org/en/lunar/api/rospy/html/rospy.topics.Subscriber-class.html
Официальной инструкции по работе с топиками.
Официальную инструкцию к пакету rclpy https://docs.ros.org/en/rolling/p/rclpy/.
Примеры использования библиотек https://github.com/ros2/examples/tree/humble
Положение робота
Что вы узнаете в этом разделе:
- Что такое одометрия и зачем она нужна
- Какие датчики используются для определения положения робота
- Структуру сообщения
nav_msgs/msg/Odometry - Как получить данные о текущем положении и ориентации робота
- Как интерпретировать данные одометрии из топика
/odom
Оценка положения робота (в английской литературе — Localization) — это одна из ключевых проблем в робототехнике. Существует несколько методов, которые можно разделить на две большие группы: методы, основанные на относительных измерениях (одометрия), и методы, основанные на абсолютных измерениях (например система GPS).
Одометрия
Одометрия робота — это процесс оценки изменения положения робота во времени относительно некоторой начальной точки. По сути, это ответ на вопросы: "Где я нахожусь?", "Куда и как далеко я переместился?" и "Куда я повернут?".
Робот делает это, непрерывно отслеживая и интегрируя (суммируя) данные о своем движении. Основной источник этих данных — датчики движения:
- Энкодеры на колесах: измеряют, скорость каждого колеса.
- IMU (инерциальный измерительный модуль) : содержит гироскоп (для измерения угловой скорости поворота) и акселерометр (для измерения ускорения). Также некоторые модули содержат магнитометр.
Собрав данные, например, о том, что "левое колесо повернулось на 10 оборотов, а правое на 9.5 оборотов", робот с помощью математической модели вычисляет, что он:
- Проехал примерно 3 метра.
- Повернул направо на 15 градусов.
- И теперь находится в новой точке с новыми координатами (X, Y, Θ).
Важный нюанс: Одометрия — это именно оценка положения, а не его точное измерение. Она подвержена накоплению ошибок, особенно при поворотах робота.
Системная плата робота turtlebro, имеет необходимую программу для расчета положение робота. Данные о положении робота публикуются в топик /odom. Формируются они на основе энкодеров, установленных на моторах, и IMU датчика, расположенного в центре робота под лидаром.
Давайте посмотрим на это данные. Сначала определим тип этого сообщения:
ros2 topic info /odom
===
Type: nav_msgs/msg/Odometry
Publisher count: 1
Subscription count: 0
Сообщение nav_msgs/msg/Odometry
Для данных одометрии в ROS существует стандартное сообщение. Большинство роботов использует именно этот тип сообщений, что позволяет унифицировать подходы по работе с данными между роботами разных производителей.
Мы видим, что тип сообщения nav_msgs/msg/Odometry. Посмотрим на его структуру:
ros2 interface show nav_msgs/msg/Odometry
====
# Includes the frame id of the pose parent.
std_msgs/Header header
builtin_interfaces/Time stamp
int32 sec
uint32 nanosec
string frame_id
# Frame id the pose points to. The twist is in this coordinate frame.
string child_frame_id
# Estimated pose that is typically relative to a fixed world frame.
geometry_msgs/PoseWithCovariance pose
Pose pose
Point position
float64 x
float64 y
float64 z
Quaternion orientation
float64 x 0
float64 y 0
float64 z 0
float64 w 1
float64[36] covariance
# Estimated linear and angular velocity relative to child_frame_id.
geometry_msgs/TwistWithCovariance twist
Twist twist
Vector3 linear
float64 x
float64 y
float64 z
Vector3 angular
float64 x
float64 y
float64 z
float64[36] covariance
Довольно серьезный набор данных:
-
Блок
std_msgs/Header headerсодержит стандартный набор параметров, который очень часто используется в системных сообщениях, который содержит время создания сообщения и объект, относительно которого создано сообщение; -
Блок
geometry_msgs/PoseWithCovariance poseсодержит данные о положении робота в пространстве. Сообщение содержит данные:geometry_msgs/Point position;geometry_msgs/Quaternion orientation;covariance
positionэто переменные расположения робота в осях XYZ, где Z это высота;orientation— для определения ориентации (углов) объектов в трехмерном пространстве в робототехнике используется система кватернионов. Далее мы приведем примеры, как эти комплексные значения можно перевести в более привычные углы Эйлера;
- Блок
geometry_msgs/TwistWithCovariance twistсодержит текущую линейную и угловую скорость.
Получение данных Одометрии
Выполним команду получения данных из топика
ros2 topic echo /odom
С полученными данными работать не очень удобно, мы не видим "основных" данных, а видим только "хвост" сообщения с данными ковариации. Добавим параметр --flow-style или --no-arr чтобы сократить вывод.
ros2 topic echo /odom --no-arr
===
---
header:
stamp:
sec: 1756898569
nanosec: 769793908
frame_id: odom
child_frame_id: base_footprint
pose:
pose:
position:
x: 0.0
y: 0.0
z: 0.0
orientation:
x: 0.0
y: -0.0
z: -0.28215259313583374
w: -0.9593695402145386
covariance: '<array type: double[36]>'
twist:
twist:
linear:
x: 0.0
y: 0.0
z: 0.0
angular:
x: 0.002222222276031971
y: 0.0
z: 0.0
covariance: '<array type: double[36]>'
Из сообщения видно, что положение текущее положение робота
pose:
pose:
position:
x: 0.0
y: 0.0
z: 0.0
А ориентация робота
pose:
pose:
orientation:
x: 0.0
y: -0.0
z: -0.28215259313583374
w: -0.9593695402145386
Пока мы не можем управлять колесами, но мы можем взять робота в руки и покрутить. Данные о его ориентации будут меняться.
Преобразование углов
Что вы узнаете в этом разделе:
- Что такое кватернионы и углы Эйлера
- Зачем нужно преобразовывать кватернионы в углы
- Как преобразовать кватернион в угол
thetaдля 2D навигации - Как использовать библиотеку
tf_transformationsдля работы с углами - Готовые функции для преобразования углов в Python
Преобразование в углы Эйлера
Важная часть практического задания, это перевод углового положения робота из системы кватернионов в более понятные для движения в одной плоскости углы Эйлера.
Теоретическая часть этой задачи, довольно сложная и потребует углубленных знаний математики, материалы для изучения находятся по ссылкам в конце страницы.
Мы же поступим очень просто, возьмем готовые функции и поверим, что они делают то что нам нужно;
Обычно в двухмерной плоскости угол положения робота называется theta (тэтта). Для того чтобы перевести угол из кватерниона в угол theta, можно воспользоваться готовой функцией на python:
import math
from geometry_msgs.msg import Quaternion
def quaternion_to_theta(orientation: Quaternion):
t1 = +2.0 * (orientation.w * orientation.z + orientation.x * orientation.y)
t2 = +1.0 - 2.0 * (orientation.y ** 2 + orientation.z**2)
return math.atan2(t1, t2)
q = Quaternion(x=0, y=0, z=-0.282, w=-0.959)
print(quaternion_to_theta(q))
Если вам необходимы все углы Эйлера (углы крена, тангажа и рыскания), лучше воспользоваться библиотекой tf_transformations, которая также содержит дополнительные методы.
from tf_transformations import quaternion_from_euler, euler_from_quaternion
# Преобразование из Эйлера в кватернион
quaternion = quaternion_from_euler(0, 0, 3.14)
# Преобразование из кватерниона в углы Эйлера
euler = euler_from_quaternion(quaternion)
print(euler)
Дополнительные материалы
- https://ru.wikipedia.org/wiki/Кватернионы_и_вращение_пространства
- https://www.youtube.com/watch?v=d4EgbgTm0Bg&t=1398s
- https://habr.com/ru/post/426863/
- Описание структуры данных Odometry
Практические задания
Шаги:
- Создайте программу Подписчик
odom_subscriber.py- Подпишитесь на топик
/odom - Выведите данные о положении робота в терминале:
- Данные положения робота в формате: X,Y
- Данные об ориентации робота
thetaв градусах. - Запустите пример движение робота по окружности из прошлого урока, и продемонстрируйте что данные одометрии меняются.
- Подпишитесь на топик
Подписчик и Издатель (Вариант1)
Что вы узнаете в этом разделе:
- Как создать программу, которая одновременно является и издателем, и подписчиком
- Как обрабатывать входящие данные и сразу публиковать результат
- Что такое "синхронный" подход обработки данных
- Как создать комплексную программу, работающую с несколькими топиками
- Преимущества и ограничения синхронного подхода
Программа Подписчика и Издателя
Ранее мы создавали отдельно программы Подписчики и программы Издатели. На этом уроке мы попрактикуемся создавать комплексные примеры, в которых мы будем использовать сразу оба метода коммуникации.
Давайте создадим программу, которая будет принимать данные из одного топика и передавать в другой. Например, мы будем получать в топик /name данные типа String с именем человека, и возвращать приветствие в виде "Hello, Имя" в топик /greeting
Файл chapter5/pub_sub1.py
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class GreetingNode(Node):
def __init__(self):
super().__init__('greeting_node')
# Создаем подписчика на топик /name
self.subscription = self.create_subscription(
String,
'/name',
self.name_callback,
10 # Размер очереди
)
# Создаем издателя для топика /greeting
self.publisher = self.create_publisher(
String,
'/greeting',
10 # Размер очереди
)
self.get_logger().info('Greeting node started!')
def name_callback(self, msg):
name = msg.data
if name: # Проверяем, что имя не пустое
# Формируем приветствие
greeting = f"Hello, {name}"
# Создаем сообщение для отправки
greeting_msg = String()
greeting_msg.data = greeting
# Публикуем приветствие
self.publisher.publish(greeting_msg)
# Логируем для отладки
self.get_logger().info(f'Received: "{name}" -> Sent: "{greeting}"')
else:
self.get_logger().warn('Received empty name!')
def main(args=None):
rclpy.init(args=args)
greeting_node = GreetingNode()
try:
rclpy.spin(greeting_node)
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()
Запустим пример
python3 pub-sub1.py
[INFO] [1757345256.008554993] [greeting_node]: Greeting node started!
Как мы видим, логика нашей программы довольно простая. Мы создаем один Подписчик self.subscription и один Издатель self.publisher. Как только в подписчике появляется новое сообщение, то формируется ответ (в функции name_callback) и отправляется через созданный Издатель.
Поэтому, пока данные не придут в топик /name, программа ничего не выводит.
Отправим данные в топик через утилиту ros2 topic pub
ros2 topic pub /name "std_msgs/msg/String" "data: 'Petr'"
В выводе программы начнут выводиться приветственные сообщения
[INFO] [1757345323.974932442] [greeting_node]: Received: "Petr" -> Sent: "Hello, Petr"
[INFO] [1757345324.875742728] [greeting_node]: Received: "Petr" -> Sent: "Hello, Petr"
[INFO] [1757345325.875639273] [greeting_node]: Received: "Petr" -> Sent: "Hello, Petr"
Также мы можем это проверить, посмотрев топик /greeting
ros2 topic echo /greeting
===
data: Hello, Petr
---
data: Hello, Petr
---
data: Hello, Petr
Данный "стиль" обработки данных, можно назвать "синхронным". Как только данные появились, мы их обработали и моментально опубликовали.
Такой подход имеет ряд особенностей. Например, если данные не приходят, то и ответ программы отсутствует. Также мы не можем изменить частоту публикации ответов, она всегда будет равна частоте входящего топика.
Такой подход не подойдет, когда обработка данных будет занимать больше времени, чем есть у программы до получения следующего сообщения.
Подписчик и Издатель (Вариант2)
Что вы узнаете в этом разделе:
- Что такое "асинхронный" подход обработки данных
- Как хранить состояние программы между вызовами функций
- Как использовать таймеры для независимой публикации данных
- Разницу между синхронным и асинхронным подходами
- Когда использовать каждый из подходов
Программа Подписчика и Издателя
Рассмотрим подход, который можно назвать "асинхронным". Входящие топики никак не влияют на работу выходящих топиков, обработка данных и публикация происходит по таймеру с необходимой нам частотой.
Для этого добавим в программу переменную "хранилище" имени пользователя, так как нам необходимо хранить "состояние" программы.
# Хранилище для имени пользователя
self.user_name = "Nobody" # Значение по умолчанию
Функцию обработки данных от Подписчика, упростим. Она будет проверять и обновлять переменную с именем пользователя
#Обновляем имя пользователя, когда мы его получили
def name_callback(self, msg):
name = msg.data
if name: # Проверяем, что имя не пустое
self.user_name = name
self.get_logger().info(f'User name updated to: {name}')
А для публикации данных, воспользуемся таймером, с частотой 2 герца и функцией публикации данных publish_greeting
self.timer = self.create_timer(0.5, self.publish_greeting)
......
def publish_greeting(self):
#Публикует приветствие с текущим именем пользователя
greeting = f"Hello, {self.user_name}"
greeting_msg = String()
greeting_msg.data = greeting
self.publisher.publish(greeting_msg)
self.get_logger().info(f'Published: "{greeting}"')
Полный код получившейся "асинхронной" программы
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class GreetingNode(Node):
def __init__(self):
super().__init__('greeting_node')
# Хранилище для имени пользователя
self.user_name = "Nobody" # Значение по умолчанию
# Создаем подписчика на топик /name
self.subscription = self.create_subscription(
String,
'/name',
self.name_callback,
10 # Размер очереди
)
# Создаем издателя для топика /greeting
self.publisher = self.create_publisher(
String,
'/greeting',
10 # Размер очереди
)
# Создаем таймер для публикации приветствия каждые 0.5 секунды
self.timer = self.create_timer(0.5, self.publish_greeting)
self.get_logger().info('Greeting node started!')
#Обновляем имя пользователя, когда мы его получили
def name_callback(self, msg):
name = msg.data
if name: # Проверяем, что имя не пустое
self.user_name = name
self.get_logger().info(f'User name updated to: {name}')
def publish_greeting(self):
#Публикует приветствие с текущим именем пользователя
greeting = f"Hello, {self.user_name}"
greeting_msg = String()
greeting_msg.data = greeting
self.publisher.publish(greeting_msg)
self.get_logger().info(f'Published: "{greeting}"')
def main(args=None):
rclpy.init(args=args)
greeting_node = GreetingNode()
try:
rclpy.spin(greeting_node)
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()
Запустим ее и сразу увидим что начинаются публиковаться сообщения с заданной частотой. В самом начале работы программы, имя пользователя Nobody
python3 pub-sub2.py
[INFO] [1757346870.166230525] [greeting_node]: Greeting node started!
[INFO] [1757346870.650511581] [greeting_node]: Published: "Hello, Nobody"
[INFO] [1757346871.150480584] [greeting_node]: Published: "Hello, Nobody"
[INFO] [1757346871.650390068] [greeting_node]: Published: "Hello, Nobody"
[INFO] [1757346872.150407664] [greeting_node]: Published: "Hello, Nobody"
Опубликуем новое имя
ros2 topic pub /name "std_msgs/msg/String" 'data: 'Иван''
[INFO] [1757346877.562927934] [greeting_node]: User name updated to: Иван
[INFO] [1757346877.650443192] [greeting_node]: Published: "Hello, Иван"
[INFO] [1757346878.150455047] [greeting_node]: Published: "Hello, Иван"
Программа работает, как мы и ожидали. Постоянно идут данные, а при поступлении нового имени приветствие меняется.
Финальный примеры программы, доступны в разделе примеры на GitHub turtlebro2-examples
Практические задания
Задание 1: Движение туда-обратно (синхронный подход)
Шаги:
- Создайте
move_straight.pyс подпиской на/odomи издателем/cmd_vel - Сохраните стартовую позицию при запуске
- В callback одометрии проверяйте расстояние до цели (1 м)
- При достижении цели развернитесь и вернитесь в стартовую точку
- Остановите робота при возврате
Задание 2: Движение по квадрату (асинхронный подход)
Шаги:
- Создайте
move_square.pyс таймером для проверки положения - Реализуйте логику движения к 4 вершинам квадрата (1×1 м)
- В таймере проверяйте достижение каждой вершины
- После 4-й вершины вернитесь в стартовую точку
Задание 3: Анализ точности
Шаги:
- Запустите программы несколько раз и зафиксируйте ошибки
- Проанализируйте причины: накопление ошибок одометрии, проскальзывание колес
- Предложите решения: коррекция по датчикам, уменьшение скорости, использование IMU
Сервисы
Что вы узнаете в этом разделе:
- Что такое сервисы в ROS и чем они отличаются от топиков
- Модель взаимодействия Request-Response
- Что такое клиент и сервер сервиса
- Как работать с сервисами через консоль (CLI)
- Как просмотреть список доступных сервисов
- Как получить информацию о сервисе и вызвать его
Сервис (Service)
Ранее мы познакомились с такой сущностью как топики. По сути, работа с ними, это широко используемая Publisher-Subscriber модель коммуникации. Одни программы постоянно публикуют данные, другие эти данные получают. Использовать ее удобно, когда у нас есть постоянный поток данных (например данные датчиков и тп).
Но это не единственная модель коммуникации. Сегодня мы поговорим об еще одной модели взаимодействия между модулями в ROS, это модель Сервис (Service), которая использует паттерн взаимодействия Request-Response.
Эта модель коммуникации очень похожа на "удаленный" вызов функции. Одна программа вызывает функцию создавая Запрос (ServiceRequest), а другая программа получая запрос, формирует Ответ (ServiceResponse), и возвращает ее программе вызвавшей сервис.
Аналогия из жизни: Сервис можно сравнить с походом в ресторан. Клиент (Service client) делает заказ (запрос) официанту: "Принесите мне пиццу". Официант (сервер, Service server) передает заказ на кухню, получает готовое блюдо (ответ) и приносит его клиенту. Это взаимодействие "вопрос-ответ": клиент задает вопрос и ждет конкретный ответ. В отличие от топиков, где данные "текут" постоянно, сервис работает по принципу "спросил-получил ответ". Это как звонок в службу поддержки: вы звоните, задаете вопрос, получаете ответ и разговор заканчивается.
Запрос и Ответ обычно содержат переменные, необходимы для работы программы, но также они могут быть пустыми.
Мы можем выделить еще два определения, которые касаются Сервисов
- Клиент (Service client) -- это программа, которая создает Запрос, и отправляет его на Сервер.
- Сервер (Service server) -- это программа, которая ожидает Запрос от Клиента, производит вычисления и отправляет результат вычислений Ответ (в том числе и "пустым" ответом).

Работа с сервисами через cli
Для работы с сервисами есть утилита ros2 service
Запустим команду и посмотрим вывод основных параметров
ros2 service
===
Основные команды:
call Вызов сервиса
info Получение информации о сервисе
list Список доступных сервисов
Получение списка доступных сервисов
Выполним команду
ros2 service list
===
/board_info
....
/camera/list_parameters
...
/domain_id
/power/reset
/reset
...
/start_motor
/stop_motor
Получение информации о сервисе
Мы получили список названий доступных сервисов (запущенных). Разобраться, какие сервисы что делают, можно в инструкции к Роботу или в описании установленных и запущенных пакетов.
Например, в инструкции указано, что сервис /start_motor (std_srvs/srv/Empty) запустит мотор лидара.
Посмотрим информацию о сервисе.
ros2 service info /stop_motor
===
Type: std_srvs/srv/Empty
Clients count: 0
Services count: 1
Мы получили тип сообщения std_srvs/srv/Empty (пустое сообщение) которое нам необходимо использовать для вызова сервиса.
Вызов сервиса
Вызовем сервис остановки мотора лидара с "пустым" сообщением.
ros2 service call /stop_motor std_srvs/srv/Empty "{}"
===
waiting for service to become available...
requester: making request: std_srvs.srv.Empty_Request()
response:
std_srvs.srv.Empty_Request()
При выполнении команды мы видим создание пустого запроса std_srvs.srv.Empty_Request() и получение ответа std_srvs.srv.Empty_Request(). После получения ответа, лидар должен перестать вращаться.
В данном случае сервис используется без параметров (у функции остановить мотор нет параметров), также сервису нечего сообщить о результате своей работы.
Рассмотрим пример вызова сервиса, которые вернет параметры системной платы Turtlebro.
Для этого вызовем сервис /board_info
ros2 service call /board_info turtlebro/srv/BoardInfo "request: {}"
waiting for service to become available...
requester: making request: turtlebro.srv.BoardInfo_Request(request=std_msgs.msg.Empty())
response:
turtlebro.srv.BoardInfo_Response(mcu_id=std_msgs.msg.String(data='002300533034510A30323536'), firmware_version=std_msgs.msg.String(data='TB2_microros'))
Мы получили данные о серийном номере MCU и версии системной прошивки платы
- mcu_id: 002300533034510A30323536
- firmware_version: TB2_microros
Создание сервисов (Python)
Что вы узнаете в этом разделе:
- Как создать сервер сервиса на Python
- Структуру программы-сервера в ROS2
- Как обрабатывать запросы и формировать ответы
- Как найти подходящие типы сообщений для сервисов
- Как протестировать работу сервиса через консоль
- Основные методы работы с сервисами в rclpy
Пример Сервиса (Service) на Python
Рассмотрим создание простого сервиса, который будет складывать два числа. Клиент отправляет два числа, которые необходимо сложить, в сервер производит сложение и возвращает результат сложения.
Такую программу, можно считать "каноничной" демонстрацией работы сервиса. Также данный пример нам удобно реализовать, так как сообщения для такой программы уже созданы, и нам не нужно их создавать.
Найдем подходящие нам сообщения для сервиса. Используем дополнительный ключ --only-srvs, для того, чтобы получить только сервисы
ros2 interface list --only-srvs
===
action_msgs/srv/CancelGoal
....
example_interfaces/srv/AddTwoInts
example_interfaces/srv/SetBool
example_interfaces/srv/Trigger
....
Необходимое нам сообщение example_interfaces/srv/AddTwoInts рассмотрим его структуру.
ros2 interface show example_interfaces/srv/AddTwoInts
int64 a
int64 b
---
int64 sum
До символов --- мы видим описание Запроса (переменные a и b, тип int64), а после идет Ответ (int64 sum)
Создадим сервер (Service server)
Сервер должен "постоянно" работать, ожидая запрос. При получении запроса, необходимо сложить два числа, и вернуть результат операции.
Создадим файл chapter6/minimal-service.py
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
class MinimalService(Node):
def __init__(self):
super().__init__('minimal_service')
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)
self.get_logger().info("Service AddTwoInts is ready")
def add_two_ints_callback(self, request, response):
response.sum = request.a + request.b
self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))
return response
def main(args=None):
rclpy.init(args=args)
minimal_service = MinimalService()
try:
rclpy.spin(minimal_service)
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()
Скелет программы аналогичен тому, с которым мы работали с топиками.
В начале, мы импортируем библиотеки ROS и сообщение сервиса AddTwoInts
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
Блок "запуска" ноды, также аналогичен рассмотренным ранее, поменялось только имя созданного класса.
def main(args=None):
rclpy.init(args=args)
minimal_service = MinimalService()
try:
rclpy.spin(minimal_service)
except KeyboardInterrupt:
pass
if __name__ == '__main__':
main()
Рассмотрим с комментариями тело самой программы. Методы инициализации __init__(self) и основной рабочий метод сервиса add_two_ints_callback
def __init__(self):
#Инициализируем ноду
super().__init__('minimal_service')
#Создаем сервис. Указав параметры
# `AddTwoInts` Тип сервиса
# `add_two_ints` Название сервиса
# `add_two_ints_callback` Имя функции, которую необходимо запустить
# когда поступи Запрос от Клинта
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)
# Функция "реализатор" сервиса, выполняет сложение
def add_two_ints_callback(self, request, response):
#Операция сложения, где
#request - объект из Запроса
#response - объект Ответа
response.sum = request.a + request.b
self.get_logger().info(f'Incoming request a: {request.a} b: {request.b}')
#Возврат ответа (суммы)
return response
Запустим нашу программу
python3 ./minimal-service.py
[INFO] [1758020610.453802380] [minimal_service]: Service AddTwoInts is ready
Мы видим, что сервис запустился, и программа ожидает запросы.
Найдем наш сервис
ros2 service list | grep add
===
/add_two_ints
Наш сервис появился в списке. Используя CLI, создадим запрос к сервису.
ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts '{"a": 1, "b": 5}'
===
waiting for service to become available...
requester: making request: example_interfaces.srv.AddTwoInts_Request(a=1, b=5)
response:
example_interfaces.srv.AddTwoInts_Response(sum=6)
Мы видим формирование запроса (a=1, b=5) и получения результата (sum=6)
В консоле, где работает программа сервер, мы видим вывод сообщение, что получен запрос с параметрами a: 1 b: 5
[INFO] [1758020904.243744414] [minimal_service]: Incoming request
a: 1 b: 5
Мы удостоверились, что программа работает верно, и мы можем использую CLI протестировать ее.
Создание клиента (Python)
Что вы узнаете в этом разделе:
- Как создать клиент сервиса на Python
- Как формировать запросы к серверу
- Как ожидать доступности сервиса перед отправкой запроса
- Как обрабатывать ответы от сервера
- Разницу между синхронным и асинхронным вызовом сервиса
- Структуру программы-клиента в ROS2
Пример Клиента на Python
Мы вызывали сервис при помощи CLI, сейчас напишем клиент на python, который будет вызывать сервис add_two_ints
Создадим файл chapter6/minimal-client.py
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
class MinimalClientAsync(Node):
def __init__(self):
super().__init__('minimal_client_async')
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 again...')
self.req = AddTwoInts.Request()
def send_request(self, a, b):
self.req.a = a
self.req.b = b
return self.cli.call_async(self.req)
def main():
rclpy.init()
minimal_client = MinimalClientAsync()
future = minimal_client.send_request(2, 7)
rclpy.spin_until_future_complete(minimal_client, future)
response = future.result()
minimal_client.get_logger().info(
f'Result of add_two_ints: for {minimal_client.req.a} + {minimal_client.req.b} = {response.sum}')
minimal_client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
Рассмотрим блоки кода и комментарии к ним, метод инициализации __init__
def __init__(self):
super().__init__('minimal_client_async')
#Создание клиента сервиса
#`AddTwoInts`, тип сообщения
#`add_two_ints`, имя сервиса
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 again...')
#Создание "пустого" запроса
self.req = AddTwoInts.Request()
Метод создания Запроса к серверу
#Параметры, переменные a, b
def send_request(self, a, b):
#Заполнение Запроса
self.req.a = a
self.req.b = b
#Отправка запроса и возращение результата
return self.cli.call_async(self.req)
Блок запуска и вывода результата
minimal_client = MinimalClientAsync()
#Создание запроса, с переменными 2 и 7
future = minimal_client.send_request(2, 7)
#Ожидание результата
#Ожидание "обернуто" в метод rclpy.spin_until_future_complete
#Так как мы не останавливаем работу основной ноды (rclpy.spin),
#что необходимо для работы таймеров, и работы с топиками
rclpy.spin_until_future_complete(minimal_client, future)
#Получение **Ответа**, после завершения вызова сервиса.
response = future.result()
minimal_client.get_logger().info(
f'Result of add_two_ints: for {minimal_client.req.a} + {minimal_client.req.b} = {response.sum}')
Запустим программу, и увидим результат выполнения сложения
python3 minimal-client.py
[INFO] [1758030881.075401754] [minimal_client_async]: Result of add_two_ints: for 2 + 7 = 9
После получения результата, программа завершается.
Дополнительные материалы
Официальной документации ROS https://docs.ros.org/en/jazzy/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Service-And-Client.html
Пример "синхронного" и "асинхронного" вызова сервиса https://docs.ros.org/en/rolling/How-To-Guides/Sync-Vs-Async.html
Код примеров https://github.com/voltbro/turtlebro2-examples/tree/master/ros2-base/chapter6
Python api https://github.com/ros2/examples/tree/jazzy/rclpy/services
Практические задания
- Разработайте программу "перезапуска" лидара
lidar_restart_server.py- Программа реализует сервис по адресу
/lidar_restartс сообщениемstd_srvs/srv/Empty - При поступлении запроса
- Программа останавливает мотор лидара
- Ожидание 10 секунд
- Программа запускает мотор лидара
- Программа должна выводить в логи все изменения статуса программы
- Программа должна быть "устойчивой" к одновременному запуску процедуры рестарта
- Добавить программу в
runкакlidar_restart_server
- Программа реализует сервис по адресу
Рабочее пространство (Workspace)
Что вы узнаете в этом разделе:
- Что такое рабочее пространство (workspace) в ROS2
- Структуру директорий workspace
- Как создать собственное рабочее пространство
- Как инициализировать workspace для работы
- Как настроить системное окружение для использования workspace
- Зачем нужно разделять пакеты по разным workspace
Рабочие пространства и создание ROS2 пакетов
Прежде чем перейти к созданию пакетов, нам необходимо понять концепцию рабочего пространства в ROS2, научиться его создавать и использовать для организации и сборки собственных пакетов.
Что такое рабочее пространство (Workspace)?
Рабочее пространство (workspace) в ROS2 — это директория (папка), где вы создаете, модифицируете и собираете свои собственные пакеты. Это ваша песочница для разработки. Все пакеты, находящиеся в src директории workspace'а, могут быть собраны вместе с помощью системы colcon.
Аналогия из жизни: Рабочее пространство можно сравнить с вашей личной мастерской или рабочим столом. Это место, где вы храните свои инструменты (пакеты), работаете над проектами и собираете готовые изделия. Как в мастерской есть полки для инструментов (
src), место для сборки (build), готовые изделия (install), так и в workspace есть соответствующие директории. Вы можете иметь несколько мастерских (несколько workspace) для разных типов проектов, и каждая из них изолирована от других.
Удобно распределять пакеты ROS при разработке в разные пространства (по сути разные директории), используя необходимую нам логику.
Для робота TurtleBro, в домашней директории /home/pi создано три пространства
microros_ws #Пространство для пакетов касающиеся подсистемы MicroROS
ros_extra_ws #Пространство для "чужих" пакетов, которых нет в репозитории ubuntu, и их необходимо установить из исходников
turtlebro_ws #системные пакеты для робота TurtleBro
Для удобства в окончание название директории, мы добавляем окончание _ws (сокращение от workspace)
Структура workspace
Типичная структура workspace:
turtlebro_ws/ # Корень рабочего пространства (workspace)
├── build/ # Временные файлы сборки (cmake, make)
├── install/ # Установленные файлы (исполняемые файлы, библиотеки)
├── log/ # Логи и диагностика процесса сборки
└── src/ # Исходный код ваших пакетов (ЗДЕСЬ МЫ РАБОТАЕМ!)
└── turtlebro/ # Расположение пакета ROS
Для того чтобы система ROS "видела" все созданные рабочие пространства, они должны быть добавлены в системные настройки OS.
Например для подключения пространства turtlebro_ws необходимо выполнить команду
source /home/pi/turtlebro_ws/install/setup.bash
Для перечисленных директорий инициализация всех директорий указана в файле .bashrc, для того чтобы не приходилось каждый раз вводить повторяющиеся команды.
Создадим и настроим собственный workspace
Создание рабочего пространства user_ws
-
Создайте директорию для workspace: Откройте терминал и выполните:
mkdir -p ~/user_ws/srcФлаг
-pгарантирует, что будут созданы все родительские директории, если их не существует. -
Инициализация пустого workspace Хотя мы и не установили ни одного пакета в директорию
src, мы можем инициализировать необходимые файлы для пространства, запустив "пустую" сборку пакетов. Выполним команду сборки пакетов.cd ~/user_ws colcon buildЭта команда создаст базовые файлы конфигурации для
colcon.
Важно: Теперь ваше рабочее пространство user_ws готово к созданию пакетов. Все последующие команды по созданию пакетов должны выполняться внутри директории ~/user_ws/src.
Настройка системного окружения
Для подключения созданного рабочего пространства в систему ROS необходимо выполнить команду
cd user_ws
source install/setup.bash
Создание пакета
Что вы узнаете в этом разделе:
- Зачем нужны пакеты в ROS2
- Как создать свой первый ROS2 пакет
- Структуру Python-пакета ROS2
- Как настроить файлы
package.xmlиsetup.py - Как собрать пакет с помощью
colcon - Как добавить новые программы в существующий пакет
- Как запускать программы из пакета через
ros2 run
Часть 1: Зачем нужны пакеты?
В ROS2 пакет — это основная единица организации кода. Это контейнер, который содержит всё необходимое для определенного функционального модуля: узлы (ноды), библиотеки, данные конфигурации, файлы запуска, зависимости и т.д.
Аналогия из жизни: Пакет ROS2 можно сравнить с модулем конструктора LEGO или готовым набором инструментов для конкретной задачи. Например, набор "Навигация" содержит все детали (программы, библиотеки, настройки) для создания системы навигации робота. Как набор LEGO имеет инструкцию по сборке, так и пакет имеет файлы конфигурации, которые объясняют системе, как его собрать и использовать. Пакеты можно переиспользовать, комбинировать и делиться ими с другими, как готовые модули конструктора.
Зачем их создавать?
- Модульность: Вы разбиваете сложную систему робота (например, навигация, управление манипулятором) на независимые, хорошо определенные компоненты. Каждый пакет решает свою конкретную задачу.
- Переиспользование: Хорошо написанный пакет можно легко скопировать и использовать в другом проекте или поделиться с сообществом.
- Управление зависимостями: Пакет явно объявляет, от каких других пакетов или библиотек Python он зависит. Это позволяет инструментам ROS2 автоматически устанавливать всё необходимое.
- Сборка: Система сборки (
colcon) знает, как собрать и установить именно ваш код, потому что он оформлен в виде пакета с правильными файлами конфигурации. - Простота тестирования: Можно писать и запускать тесты для конкретного пакета, не затрагивая всю систему.
Создание первого пакета
Есть некоторое отличие в создании пакета для разработки на
pythonиcpp. Мы рассмотрим только то, что касается работы сpython.
Перейдите в src -- директорию вашего рабочего пространства (workspace) и выполните команду:
cd ~/user_ws/src
ros2 pkg create my_first_package --build-type ament_python --dependencies rclpy std_msgs --node-name my_first_node
Разберем параметры:
ros2 pkg create: основная команда для создания пакета.my_first_package: имя вашего нового пакета. Используйтеsnake_case.--build-type ament_python: указывает, что мы будем писать на Python и использовать систему сборкиament.--dependencies rclpy std_msgs: перечисляет пакеты, от которых зависит ваш код.rclpy— это клиентская библиотека ROS2 для Python,std_msgs— содержит стандартные типы сообщений. Эти зависимости автоматически добавятся в файлыpackage.xmlиsetup.py.--node-name my_first_node: опционально создает заготовку Python-файла для узла в директорииmy_first_package/my_first_package.
Структура созданного пакета
После выполнения команды вы увидите следующую структуру файлов:
user_ws/
└── src/
└── my_first_package/
├── package.xml
├── setup.py
├── setup.cfg
├── resource/
│ └── my_first_package
├── test/
│ └── ...
└── my_first_package/
├── __init__.py
└── my_first_node.py # Создан благодаря --node-name
Ключевые файлы и их настройка:
-
package.xml: Файл метаданных пакета.- Обязательно обновите поля
<description>,<maintainer>и<license>. - Здесь объявляются зависимости.
- Зависимости бывают трех типов:
ament_python(дляbuildtool_depend): Указывает, что для сборки нуженament_python.depend: Универсальная зависимость (например,rclpy,std_msgs). Включает в себяbuild_depend,exec_dependиbuild_export_depend.exec_depend: Зависимость, необходимая только для запуска вашего кода (например, если ваш узел публикует сообщение типаsensor_msgs/Image, добавьтеexec_depend sensor_msgs).
- Обязательно обновите поля
-
setup.py: Главный скрипт для сборки Python-пакета.- Самая важная часть — точка входа (
entry_points), где вы регистрируете свои узлы как консольные скрипты.
entry_points={ 'console_scripts': [ 'my_first_node = my_first_package.my_first_node:main', # 'имя_исполняемого_файла = python_пакет.имя_файла:функция_main' ], },При сборке пакета
colconсоздаст исполняемые файлы в системе, которые будут напрямую вызывать вашу функциюmain. - Самая важная часть — точка входа (
-
Файл c программой
my_first_node.py
def main():
print('Hi from my_first_package.')
if __name__ == '__main__':
main()
Сборка, настройка окружения и запуск
Под сборкой пакета, мы понимаем набор множества операций над исходным кодом, которые необходимо выполнить для того, чтобы созданные программы ROS могли функционировать. Можем выделить несколько ключевых операций: компилирование исходного кода (для программ на cpp), установка и проверка зависимостей, копирование различных файлов.
Результатом успешной сборки является создание специальных директорий ( build, install и log) в рабочем пространстве. В папке install создаются все исполняемые файлы, библиотеки, конфигурации и другие файлы (артефакты), необходимые для запуска пакета. Именно из этой директории будут запускаться программы.
- Сборка: Вернитесь в корень рабочего пространства (
~/user_ws) и выполните:
colcon build --packages-select my_first_package
Флаг --packages-select указывает собрать только один конкретный пакет.
- Настройка окружения: После сборки необходимо добавить рабочее пространство с нашим пакетом в сессию терминала.
source ~/user_ws/install/setup.bash
(Это нужно делать в каждом новом терминале! Чтобы избежать этого, можно добавить эту строку в ваш ~/.bashrc).
- Запуск первой программы: Теперь вашим узлом можно управлять как любой другой ROS2-нодой:
ros2 run my_first_package my_first_node ==== Hi from my_first_package.
Добавление новой программы в пакет
Скопируем пример из второй части нашего курса (файл temp_topic_publisher.py) в директорию пакета src/my_first_package/my_first_package/
Добавим в файл setup.py новую точку входа
entry_points={
'console_scripts': [
'my_first_node = my_first_package.my_first_node:main',
'temp_topic_publisher = my_first_package.temp_topic_publisher:main'
],
Пересоберем пакет
colcon build --packages-select my_first_package
Запустим нашу новую ноду
ros2 run my_first_package temp_topic_publisher
====
[INFO] [1757431348.282070055] [minimal_publisher]: Publishing CPU temp: 48.5
[INFO] [1757431348.760248464] [minimal_publisher]: Publishing CPU temp: 48.5
Программа запущена и работает
Дополнительная информация
При работе с Python-кодом удобнее не пересобирать пакет после каждого изменения. Для этого можно использовать опцию --symlink-install, которая создает символическую ссылку в директории install на программу из директории src. Это позволяет запускать измененный код сразу через ros2 run без необходимости повторной сборки.
colcon build --packages-select my_first_package --symlink-install
Официальная документация на работу с Creating a workspace
Официальная документация по созданию пакетов Creating a package
Практические задания
-
Создайте собственное рабочее пространство
user_ws- Добавьте инициализацию окружения в файл
.bashrc
- Добавьте инициализацию окружения в файл
-
Создайте пакет
ros_base_course- Перенесите в проект код всех программ, созданных ранее в курсе.
- temp_topic_publisher
- publisher_cmd_vel
- temp_topic_subscriber
- odom_subscriber
- lidar_restart_server
- Проверьте, что в файлах конфигурации пакетов правильно заполнены
- Указаны все зависимости
- Указаны авторы и лицензии
- Через
ros2 runпродемонстрируйте работу программ
- Перенесите в проект код всех программ, созданных ранее в курсе.
Создание собственных сообщений для топиков
Что вы узнаете в этом разделе:
- Когда нужно создавать собственные сообщения
- Как создать пакет для хранения сообщений
- Как описать структуру нового сообщения в файле
.msg - Как настроить сборку пакета для генерации сообщений
- Как использовать созданные сообщения в Python программах
- Основные типы данных для сообщений ROS
Собственные типы сообщений
Ранее мы использовали встроенные типы сообщений (или сообщения сторонних пакетов). В ROS созданы сообщения для обработки большинства необходимых типовых (данные датчиков, моторы, одометрия и тп). Перед созданием нового сообщения, необходимо убедиться что для вашей задачи не существует стандартного сообщения.
В части работы с топиками, мы рассматривали пример мониторинга температуры CPU RaspberryPi. Предположим ситуацию, что для мониторинга состояния CPU нам также важен параметр текущей частоты CPU (современные процессоры понижают частоту, когда нагрузка на CPU низкая, и повышают при возрастании нагрузки)
У RaspberryPI 4 ядра, каждое из которых может иметь собственную частоту. Итого, наше сообщение должно содержать температуру и информацию о частоте 4 ядер CPU.
Создание собственного сообщения (собсвтенного типа сообщения) — довольно сложная часть, которая затрагивает множество систем ROS. Ваше сообщение должно быть правильно создано и установлено в систему. Автоматизировать этот процесс помогает сборщик colcon.
Создание пакета для сообщений
Хорошей практикой может служить создание отдельного пакета для хранения сообщений и их генерации. Также в пакет созданные для python программ с опцией --build-type ament_python не получится добавить генерацию сообщения.
Удостоверимся, что мы находимся в рабочем пространстве user_ws, в директории с исходным кодом пакетов.
cd ~/user_ws/src
Создадим пакет robot_msgs для работы с нашими сообщениями
ros2 pkg create --build-type ament_cmake --license Apache-2.0 robot_msgs
===
going to create a new package
package name: robot_msgs
destination directory: /home/pi/user_ws/src
package format: 3
version: 0.0.0
....
Создание файла с сообщением
Сообщения для топиков принято хранить в директории msg пакета. Создадим эту директорию.
mkdir msg
Создадим файл, который опишет наше новое сообщение msg/CPUInfo.msg. Имя файла, определит название сообщения.
float32 temp
int32 cpu0_freq
int32 cpu1_freq
int32 cpu2_freq
int32 cpu3_freq
На каждой строчке необходимо указать тип переменной и название переменной которую мы хотим использовать.
Базовые типы сообщений можно посмотреть на странице https://docs.ros.org/en/jazzy/Concepts/Basic/About-Interfaces.html#field-types
Также можно создавать переменные, используя уже созданные типы сообщений. Например в логики пакета std_msgs, можно переписать файл сообщения как
std_msgs/Float32 temp
std_msgs/Int32 cpu0_freq
std_msgs/Int32 cpu1_freq
std_msgs/Int32 cpu2_freq
std_msgs/Int32 cpu3_freq
Настройка сборки пакета
Файл с сообщением мы создали, далее нам необходимо правильно установить сообщение в систему ROS. Для этого необходимо внести изменение в стандартные параметры сборки, которые мы получили при создании простого пакета.
В корне проекта находится файл CMakeLists.txt, мы должны удостовериться, что он содержит данные о необходимых зависимостях и инструкцию по сборке сообщений.
Для нашего сообщения, в файл необходимо добавить (до директивы ament_package())
find_package(std_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/CPUInfo.msg"
DEPENDENCIES std_msgs
)
Где
# Указание зависимости на пакет `std_msgs`, этот пакет содержит необходимые сообщения Float32 и UInt32
find_package(std_msgs REQUIRED)
# Указание зависимости на пакет `rosidl_default_generators`, который реализует дополнительную опцию сборки `rosidl_generate_interfaces()`
find_package(rosidl_default_generators REQUIRED)
# Команда сборки сообщений, и перечень файлов для сообщений
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/CPUInfo.msg"
DEPENDENCIES std_msgs
)
Также необходимо внести изменения в файл package.xml, в теле элемента <package>
<depend>std_msgs</depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
Сборка и тестирование пакета
Соберем пакет
cd ~/user_ws
colcon build --packages-select robot_msgs
====
Starting >>> robot_msgs
Finished <<< robot_msgs [9.01s]
Summary: 1 package finished [9.22s]
Мы видим, что пакет успешно "собран". Проверим, что сообщения появились в системе ROS.
Если рабочее пространство
user_wsне добавлено в систему - не забывайте выполнять командуsource ./install/local_setup.sh
ros2 interface list --only-msgs
...
robot_msgs/msg/CPUInfo
...
Сообщение появилось в системе.
Использование созданных сообщения в python программах
Создадим простой демонстрационный пример подключения и использования сообщения в файл src/cpu_info_msg.py. Файл необходимо создать в вашем основном пакете ros_base_course из прошлого урока.
from robot_msgs.msg import CPUInfo
msg = CPUInfo()
msg.temp = 55.1
msg.cpu0_freq = 1600000
msg.cpu1_freq = 1600000
msg.cpu2_freq = 1600000
msg.cpu3_freq = 1600000
print(msg)
Запустим и проверим его
python3 ./cpu_info_msg.py
robot_msgs.msg.CPUInfo(temp=55.1, cpu0_freq=1600000, cpu1_freq=1600000, cpu2_freq=1600000, cpu3_freq=1600000)
Сообщение доступно в общей инфраструктуре ROS и корректно работает.
Создание новых сообщений для сервисов
#№# Что вы узнаете в этом разделе:
- Как создать собственное сообщение для сервиса
- Структуру файла
.srv(запрос и ответ) - Как настроить сборку пакета для генерации сервисов
- Как проверить, что сервис правильно создан
- Как использовать созданные сервисы в программах
Описания сервиса
Создание нового сервиса похоже на создание нового сообщения. Сначала нам необходимо в файле, описать формат сообщения. В файле необходимо указать в какой структуре посылается Запрос (ServiceRequest)** и в какой отправляется Ответ (ServiceResponse);
Для этого нам необходимо создать в пакете robot_msgs папку srv и в ней файл с расширением .srv.
Для примера, мы разберем пример сервиса, который сложит три числа.
Файл описания сервиса разделен на две части, первая часть (до разделителя ---) это описание Запроса, далее описание Ответа.
Создадим файл srv/AddThreeInts.srv
std_msgs/Int32 x
std_msgs/Int32 y
std_msgs/Int32 z
---
std_msgs/UInt32 sum
Так мы создали структуру из трех значений Int32, которые мы хотим сложить. И значение sum как результат работы нашего сервиса.
Важно отметить, что имя файла AddThreeInts.srv соответствует сообщению сервиса AddThreeInts.
Генерация сообщений
Для того чтобы сообщения сервисов можно было использовать в ROS, необходимо добавить обработку этих файлов в процесс сборки.
В файл CMakeLists.txt необходимо обновить блок инструкции сборки в части rosidl_generate_interfaces, добавить ссылку на файл сервиса srv/AddThreeInts.srv)
find_package(geometry_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/CPUInfo.msg"
"srv/AddThreeInts.srv" #Добавленно
DEPENDENCIES std_msgs
)
Файл package.xml мы обновили, когда создавали сообщение для топиков.
Сборка и тестирование пакета
Соберем пакет
cd ~/user_ws
colcon build --packages-select robot_msgs
====
Starting >>> robot_msgs
Finished <<< robot_msgs [9.01s]
Summary: 1 package finished [9.22s]
Если все выполнено без ошибок, то мы можем убедиться что файл с описанием сервиса установлен верно. Для этого выполним поиск и фильтрацию по ключевому слову Add
ros2 interface list --only-srvs | grep Add
===
diagnostic_msgs/srv/AddDiagnostics
example_interfaces/srv/AddTwoInts
robot_msgs/srv/AddThreeInts
slam_toolbox/srv/AddSubmap
Мы видим созданные сообщения Сервиса robot_msgs/srv/AddThreeInts
Просмотрим информацию
ros2 interface show robot_msgs/srv/AddThreeInts
====
int32 x
int32 y
int32 z
---
int32 sum
Дополнительная информация
Официальная документация создание сообщений https://docs.ros.org/en/jazzy/Tutorials/Beginner-Client-Libraries/Custom-ROS2-Interfaces.html
Официальная документация с описанием системы сообщений https://docs.ros.org/en/jazzy/Concepts/Basic/About-Interfaces.html
Примеры созданного пакета и python программы https://github.com/voltbro/turtlebro2-examples/tree/master/ros2-base/chapter8
Практические задания
Задание 1: Издатель CPUInfo
- Создайте
cpu_info_publisher.pyс таймером 1 сек - Читайте температуру из
/sys/class/thermal/thermal_zone0/temp - Читайте частоты CPU из
/sys/devices/system/cpu/cpuN/cpufreq/scaling_cur_freq(N=0..3) - Публикуйте в топик с типом
robot_msgs/msg/CPUInfo - Добавьте в пакет и протестируйте через
ros2 run
Задание 2: Сервис сложения трех чисел
- Создайте
add_three_ints_server.pyс сервисом типаrobot_msgs/srv/AddThreeInts - В callback суммируйте
request.x + request.y + request.z - Возвращайте результат в
response.sum - Добавьте в пакет и протестируйте:
ros2 service call /add_three_ints robot_msgs/srv/AddThreeInts "{x: 1, y: 2, z: 3}"
Задание 3: Новое сообщение
- Создайте
msg/RobotState.msgв пакетеrobot_msgs - Добавьте поля:
geometry_msgs/Twist twistиsensor_msgs/Imu imu - Обновите
CMakeLists.txtиpackage.xml(добавьте зависимости) - Соберите пакет и проверьте:
ros2 interface show robot_msgs/msg/RobotState
Arduino
Что вы узнаете в этом разделе:
- Какие микроконтроллеры используются в роботе TurtleBro
- Как настроить Arduino IDE для работы с роботом
- Как создать простую программу-издатель на microROS
- Как подключить microROS-агент для связи с ROS2
- Как проверить работу программы на Arduino через топики ROS2
- Основы работы с библиотекой micro_ros_arduino
Arduino и плата TurtleBro
На системной плате TurtleBro находится два микроконтроллера. Первый — это STM32F4, на котором работает внутренняя прошивка, обеспечивающая все системные потребности платы (управление моторами, контроль питания, одометрию и т.п.)
И второй — это AT91SAM3X8E, функционально полностью совместимый с платами Arduino DUE. Для этого микроконтроллера вы можете самостоятельно разрабатывать скетчи, и загружать их на плату TurtleBro при помощи Arduino IDE.
Работа вашей программы (скетча) никаким образом не повлияет на функционирование системной платы.
Подключение и настройка ArduinoIDE
Полное руководство по настройке ArduinoIDE располагается в инструкции к роботу, в разделах Arduino и microROS. Изучите эти разделы как часть данного урока
По этой инструкции, вам необходимо установить программу ArduinoIDE и установить библиотеку micro_ros_arduino.
Создание простого Издателя на microros-arduino
За основу, мы возьмем пример из официальной документации microros
Для упрощения, уберем все лишнее, и оставим наиболее простой для понимания вариант microros_publisher_lite
#include <micro_ros_arduino.h>
#include <stdio.h>
#include <rcl/rcl.h>
#include <rcl/error_handling.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>
#include <std_msgs/msg/int32.h>
rcl_publisher_t publisher;
std_msgs__msg__Int32 msg;
rclc_executor_t executor;
rclc_support_t support;
rcl_allocator_t allocator;
rcl_node_t node;
void setup() {
set_microros_transports();
delay(2000);
allocator = rcl_get_default_allocator();
//create init_options
rclc_support_init(&support, 0, NULL, &allocator);
// create node
rclc_node_init_default(&node, "micro_ros_arduino_node", "", &support);
// create publisher
rclc_publisher_init_default(
&publisher,
&node,
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32),
"micro_ros_arduino_node_publisher");
msg.data = 0;
}
void loop() {
rcl_publish(&publisher, &msg, NULL);
msg.data++;
delay(100);
}
Разберемся с программой, в начале программы мы подключаем необходимые библиотеки и объявляем переменные
//Подключение micro_ros_arduino
#include <micro_ros_arduino.h>
//Необходимых библиотек
#include <stdio.h>
#include <rcl/rcl.h>
#include <rcl/error_handling.h>
#include <rclc/rclc.h>
#include <rclc/executor.h>
//Подключение сообщения
#include <std_msgs/msg/int32.h>
//Объявление переменных
rcl_publisher_t publisher;
std_msgs__msg__Int32 msg;
rclc_executor_t executor;
rclc_support_t support;
rcl_allocator_t allocator;
rcl_node_t node;
Функция setup() инициализирует все объекты
void setup() {
//Настройка типа подключения. В этот момент происходит чтение default_transport.cpp
set_microros_transports();
delay(2000);
allocator = rcl_get_default_allocator();
//create init_options
rclc_support_init(&support, 0, NULL, &allocator);
// create node
rclc_node_init_default(&node, "micro_ros_arduino_node", "", &support);
// Создание объекта Издателя в переменной publisher
// Тип сообщения Int32
// название топика micro_ros_arduino_node_publisher
rclc_publisher_init_default(
&publisher,
&node,
ROSIDL_GET_MSG_TYPE_SUPPORT(std_msgs, msg, Int32),
"micro_ros_arduino_node_publisher");
msg.data = 0;
}
Ну и главный цикл, довольно простой. Мы с задержкой 100мс публикуем данные в топик из простого счетчика.
void loop() {
rcl_publish(&publisher, &msg, NULL);
msg.data++;
delay(100);
}
Так как наша программа на Arduino пытается связаться с ROS только при запуске (в блоке setup()), то для ее функционирования необходим уже работающий microros-агент.
Запустим его перед загрузкой скетча
ros2 run micro_ros_agent micro_ros_agent serial --dev /dev/serial/by-id/usb-Silicon_Labs_CP2102N_USB_to_UART_Bridge_Controller_0010-if00-port0 -b 115200
Также вы можете перезапустить программу на Arduino, нажав на кнопку reset рядом с arduino, программа при инициализации опять попытается подключиться к агенту
Загрузим программу в плату Arduino.
Посмотрим что у нас появилось в топиках
ros2 topic list
===
/imu
/joint_states
/micro_ros_arduino_node_publisher
/odom
/parameter_events
Мы видим топик micro_ros_arduino_node_publisher
Посмотрим, что в нем происходит
ros2 topic echo /micro_ros_arduino_node_publisher
data: 565
---
data: 566
---
data: 567
Данные в топике, созданном на плате Arduino мы видим в общей системе ROS.
Дополнительные материалы
Пример программы Издателя, на основе официальной документации microros_publisher
Пример программы, проверяет соединение с Агентом, и переподключения если связь восстанавливается microros_reconnection
Практические задания
Задание 1: microROS Publisher
- Настройте Arduino IDE согласно инструкции робота
- Установите библиотеку
micro_ros_arduino - Скачайте и откройте скетч
microros_publisher - Запустите microROS-агент:
ros2 run micro_ros_agent micro_ros_agent serial --dev /dev/ttyACM0 -b 115200 - Загрузите скетч на Arduino
- Проверьте топик:
ros2 topic echo /micro_ros_arduino_node_publisher
Задание 2: microROS Subscriber
- Скачайте и откройте скетч
microros_subscriber - Изучите код: программа подписывается на топик и управляет RGB-лентой
- Загрузите скетч на Arduino
- Опубликуйте тестовые данные:
ros2 topic pub /micro_ros_arduino_node_subscriber std_msgs/msg/Int32 "data: 255" - Наблюдайте изменение цвета RGB-ленты
Утилит roslaunch и launch файлы
Что вы узнаете в этом разделе:
- Зачем нужны launch-файлы в ROS2
- Как создать простой launch-файл в формате XML
- Как запускать ноды через launch-файлы
- Как настроить пакет для использования launch-файлов
- Как запускать программы стандартным способом через
ros2 launch - Основные элементы launch-файлов
Для чего нужны launch-файлы в ROS2?
У любого серьезного робота одновременно работает огромное количество программ, которые необходимо гибко настраивать.
Для эффективного управления запуском и работой всего этого «зоопарка» в ROS используется система launch файлов (файлы запуска). Эти файлы позволяют не только запускать нужные ноды, но и настраивать их взаимодействие, задавать параметры и управлять зависимостями между ними. Это упрощает администрирование и настройку систем, делая ее более понятной и гибкой.
Аналогия из жизни: Launch-файл можно сравнить с рецептом приготовления сложного блюда или сценарием для театральной постановки. В рецепте указано, какие ингредиенты (программы) нужны, в каком порядке их добавлять (зависимости), и с какими настройками (параметрами) готовить. Вместо того чтобы каждый раз вручную запускать десятки программ и настраивать их, вы просто "следуете рецепту" — запускаете один launch-файл, и все необходимое запускается автоматически в правильном порядке. Это как кнопка "Включить все" на пульте управления умным домом: одно нажатие — и загорается свет, включается музыка, закрываются шторы.
Файлы запуска можно написать на XML, YAML или Python.
Хорошая практика для любого пакета, помимо самих программ, — добавлять полностью настроенные файлы запуска (с установкой всех параметров), для того чтобы другие разработчики могли подключать их в свои системы.
Например, для робота TurtleBro созданы все необходимые файлы, которые автоматический запускают все необходимые пакеты.
Создание файла запуска
Перед началом работы, убедитесь выполнены все части задания из Главы7. Создан пакет ros_base_course и созданные "точки входа" программ
temp_topic_publisherиtemp_topic_subscriber
Для начала работы с файлами запуска мы будем использовать формат XML, как наиболее простой и «визуальный». Для сложных «случаев» запуска необходимо использовать формат python.
Предположим что мы хотим автоматизировать запуск нашей программы публикующей данные о температуре процессора.
Для этого в ранее созданном нами пакете ros_base_course необходимо создать папку /launch, а в ней файл temp_topic_publisher.launch.xml
<launch>
<node pkg="ros_base_course" exec="temp_topic_publisher" name="temp_publisher" output="log" respawn="false"/>
</launch>
Мы указали, что при запуске этого файла необходимо запустить ноду exec="temp_topic_publisher" с названием name="temp_publisher" из пакета pkg="ros_base_course"
Запустить только что созданный файл (и проверить его) мы можем утилитой ros2 launch, указав путь и имя файла.
cd ~/user_ws/src/ros_base_course/launch/
ros2 launch ./temp_topic_publisher.launch.xml
===
ros2 launch ./temp_topic_publisher.launch.xml
[INFO] [launch]: All log files can be found below /home/pi/.ros/log/2025-09-23-16-47-16-052933-turtlebro01-1561
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [publisher-1]: process started with pid [1564]
[publisher-1] [INFO] [1758635237.335591481] [temp_publisher]: Publishing CPU temp: 46.9
[publisher-1] [INFO] [1758635237.813923849] [temp_publisher]: Publishing CPU temp: 46.9
[publisher-1] [INFO] [1758635238.313854530] [temp_publisher]: Publishing CPU temp: 46.9
[publisher-1] [INFO] [1758635238.813841415] [temp_publisher]: Publishing CPU temp: 46.9
Мы видим, что при запуске .launch файла запустилась нода публикации данных температуры.
Конфигурация пакета
Стандартный способ запуска .launch файла из любого пакета — это выполнение команды ros2 launch _имя-пакета_ _имя-файла_. Поиск необходимой директории с пакетом и файлом осуществляет ROS.
Для того чтобы такой способ работал и для нашего пакета, необходимо в конфигурацию пакета добавить директиву, которая "откроет" директорию с .launch файлами для поиска и загрузки.
Если мы создавали пакет с директивой ament_python то нам необходимо изменить файл setup.py, добавив в него раздел data_files, который сделает файлы доступными для использования.
import os
from glob import glob
# Other imports ...
package_name = 'ros_base_course'
setup(
# Other parameters ...
data_files=[
# ... Other data files
# Include all launch files.
(os.path.join('share', package_name, 'launch'), glob('launch/*'))
]
)
Директива
(os.path.join('share', package_name, 'launch'), glob('launch/*'))добавит в доступ все файлы из директории launch
Пересобираем пакет
cd user_ws
colcon build --symlink-install
Запустим .launch файл стандартным способом
ros2 launch ros_base_course temp_topic_publisher.launch.xml
===
[INFO] [launch]: All log files can be found below /home/pi/.ros/log/2025-09-23-17-17-46-822958-turtlebro01-2477
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [publisher-1]: process started with pid [2480]
[publisher-1] [INFO] [1758637068.097792153] [temp_publisher]: Publishing CPU temp: 46.3
[publisher-1] [INFO] [1758637068.576227671] [temp_publisher]: Publishing CPU temp: 47.4
[publisher-1] [INFO] [1758637069.076075606] [temp_publisher]: Publishing CPU temp: 45.8
Файл запуска создан и установлен в систему ROS. Более подробно и создании файлов, можно посмотреть в официальной инструкции https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Launch/Creating-Launch-Files.html
Параметры ROS
Что вы узнаете в этом разделе:
- Что такое параметры в ROS и зачем они нужны
- Как просмотреть список всех параметров системы
- Как получить значение конкретного параметра
- Как установить параметр через консоль
- Как настроить параметры через launch-файлы
- Разницу между статическими и динамическими параметрами
Что такое параметры в ROS?
Параметры в ROS — это глобальные переменные для всех узлов системы. Они хранят конфигурационные данные, которые можно менять без перекомпиляции кода. Параметры можно настроить через консоль или при запуске ноды с помощью launch-файлов.
Основные характеристики
- Хранятся на Parameter Server.
- Доступны для чтения и записи из любого узла.
- Статические параметры настраиваются при запуске узла.
- Динамические параметры можно менять, даже если программа уже запущена.
- Поддерживают разные типы данных: строки, числа, списки и словари.
Работа с параметрами через консоль
Просмотр доступных в системе параметров
# Просмотреть все параметры, с группировкой по нодам
ros2 param list
# Получить значение конкретного параметра
# ros2 param get <node_name> <parameter_name>
ros2 param get /camera brightness
===
Integer value is: 50
# Просмотреть всё дерево параметров и все значения
ros2 param dump camera
Установка параметров
# Установить параметр
ros2 param set /camera brightness 10
===
Set parameter successful
#Проверить значение
ros2 param get /camera brightness
Integer value is: 10
Если изменить параметр brightness камеры и посмотреть на изображение через веб-интерфейс, мы увидим, что яркость картинки меняется. Это иллюстрация работы динамического параметра. Его также можно задать при запуске ноды.
Установка параметров через launch файлы
Если мы хотим, чтобы при загрузке системы у нас установился параметр brightness со значением 10, то нам необходимо добавить этот параметр в launch файл, как это сделано в примере ниже.
<launch>
<node pkg="usb_cam" exec="usb_cam_node_exe" name="camera" respawn="true" respawn_delay="15">
<param name="brightness" value="10"/>
</node>
</launch>
Дополнительная информация
Официальная документация о параметрах https://docs.ros.org/en/jazzy/Concepts/Basic/About-Parameters.html
Параметры ROS (Python)
Что вы узнаете в этом разделе:
- Как объявить параметр в Python программе
- Как читать значения параметров в коде
- Как установить значение параметра по умолчанию
- Как изменять параметры во время работы программы
- Как настроить параметры через launch-файлы
- Основные методы работы с параметрами в rclpy
Объявление и использование параметров в Python-коде ROS2
Рассмотрим как использовать параметры при разработке на python
В пакете ros_base_course создайте новый python скрипт simple_params.py
import rclpy
from rclpy.node import Node
class MinimalParam(Node):
def __init__(self):
super().__init__('minimal_param_node')
self.declare_parameter('my_parameter', 'world')
self.timer = self.create_timer(1, self.timer_callback)
def timer_callback(self):
my_param = self.get_parameter('my_parameter').get_parameter_value().string_value
self.get_logger().info('Hello %s!' % my_param)
def main():
rclpy.init()
node = MinimalParam()
rclpy.spin(node)
if __name__ == '__main__':
main()
Данная программа создает таймер self.timer который раз в секунду запускает функцию timer_callback для чтения и вывода параметра my_parameter на экран.
Рассмотрим основной код, который касается обработки параметров.
#Инициализация параметра.
#Мы указываем имя параметра и значение параметра по умолчанию
self.declare_parameter('my_parameter', 'world')
#Чтение параметров происходит в строчке
my_param = self.get_parameter('my_parameter').get_parameter_value().string_value
Запустим данную программу
python3 ./simple_params.py
===
[INFO] [1758716765.167893879] [minimal_param_node]: Hello world!
[INFO] [1758716766.167960852] [minimal_param_node]: Hello world!
Мы видим сообщение Hello world!, которое выводится раз в секунду.
Посмотрим список параметров робота
ros2 param list
===
/minimal_param_node:
my_parameter
start_type_description_service
use_sim_time
Мы видим созданный параметр my_parameter ноды minimal_param_node
Установим новое значение параметра
ros2 param set minimal_param_node my_parameter "Robot"
Set parameter successful
В консоли с запущенной программой изменился вывод сообщения world->Robot
[INFO] [1758716966.602924761] [minimal_param_node]: Hello world!
[INFO] [1758716966.602924761] [minimal_param_node]: Hello world!
[INFO] [1758716967.602871050] [minimal_param_node]: Hello Robot!
[INFO] [1758716968.602862135] [minimal_param_node]: Hello Robot!
Настройка параметров через launch файл
Подготовка скрипта для запуска
Удостоверимся, что мы добавили в инструкцию сборки, новую точку входа для программы simple_params.py в файл setup.py
entry_points={
'console_scripts': [
...
'simple_params = ros_base_course.simple_params:main'
],
Пересобираем пакет и запустим программу используя ros2 run
cd ~/user_ws
colcon build --packages-select ros_base_course --symlink-install
ros2 run ros_base_course simple_params
===
[INFO] [1758722226.201036205] [minimal_param_node]: Hello world!
[INFO] [1758722227.180272894] [minimal_param_node]: Hello world!
Нода запускается.
Создание launch файла
В директории launch создадим файл для тестирования, запуска ноды, сначала без параметра simple_params.launch.xml
<launch>
<node pkg="ros_base_course" exec="simple_params" name="minimal_param_node" output="screen"/>
</launch>
Мы уже настроили директорию launch для глобальной настройки ROS, но для нового launch файла необходимо заново пересобрать пакет.
cd ~/user_ws
colcon build --packages-select ros_base_course --symlink-install
Запустим ноду через launch файл
ros2 launch ros_base_course simple_params.launch.xml
[INFO] [launch]: All log files can be found below /home/pi/.ros/log/2025-09-24-17-03-07-239472-turtlebro01-4041
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [simple_params-1]: process started with pid [4044]
[simple_params-1] [INFO] [1758722588.961406715] [minimal_param_node]: Hello world!
[simple_params-1] [INFO] [1758722589.940741468] [minimal_param_node]: Hello world!
Мы видим сообщение Hello world!. Добавим новый параметр в настройку запуска ноды <param name="my_parameter" value="Robot"/>
<launch>
<node pkg="ros_base_course" exec="simple_params" name="minimal_param_node" output="screen">
<param name="my_parameter" value="Robot"/>
</node>
</launch>
Запустим и проверим, что параметр установлен при запуске программы.
ros2 launch ros_base_course simple_params.launch.xml
[INFO] [launch]: All log files can be found below /home/pi/.ros/log/2025-09-24-17-35-30-192083-turtlebro01-4606
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [simple_params-1]: process started with pid [4609]
[simple_params-1] [INFO] [1758724531.906593599] [minimal_param_node]: Hello Robot!
[simple_params-1] [INFO] [1758724532.886221930] [minimal_param_node]: Hello Robot!
Мы видим сообщение c установленным параметром Hello Robot!
Дополнительная информация
Официальная документация по работе с параметрами в python https://docs.ros.org/en/jazzy/Tutorials/Beginner-Client-Libraries/Using-Parameters-In-A-Class-Python.html
Создание динамических параметров https://docs.ros.org/en/jazzy/Tutorials/Intermediate/Monitoring-For-Parameter-Changes-Python.html
Автостарт программ робота
Что вы узнаете в этом разделе:
- Как настроен автостарт программ на роботе TurtleBro
- Как управлять системным сервисом
turtlebroчерез systemctl - Как запускать и останавливать ПО робота
- Как добавить свои программы в автозагрузку
- Как изменить конфигурацию запуска робота
Как устроен автозапуск в TurtleBro
Робот TurtleBro настроен таким образом, что при включении робота происходит запуск системного сервиса turtlebro. Данный сервис запускает turtlebro.launch — файл, расположенный в пакете turtlebro (~/turtlebro_ws/src/turtlebro/launch/turtlebro.xml).
Вы можете запускать и останавливать работу ПО робота используя утилитуsystemctl
sudo systemctl stop turtlebro
sudo systemctl start turtlebro
Для запуска системы turtlebro также можно использовать утилиту ros2 launch
ros2 launch turtlebro turtlebro.xml
Если вам необходимо добавить новые ноды или изменить конфигурацию запуска, вы можете придерживаться следующего порядка:
- Остановить действующую систему.
- Внести изменения в launch файл (
turtlebro.launch) - Запустить все ноды через запуск сервиса
turtlebro
sudo systemctl start turtlebro
Практические задания
-
Добавьте параметр в пример Издателя температуры
- Добавьте параметр
frequencyв пример издателя температуры, который будет отвечать за частоту публикации данных - Создайте параметр в launch файле, в котором определите частоту в 20 герц.
- Добавьте параметр
-
Запуск одновременно двух нод
- Создайте отдельный launch-файл для запуска ноды с подписчиком на температуру. (
temp_topic_subscriber.py) - Создайте единый launch-файл, запускающий два уже созданных
.launchфайла
- Создайте отдельный launch-файл для запуска ноды с подписчиком на температуру. (
-
Настройка автостарта Издателя температуры
- Добавить в автозагрузку робота запуск Издателя с данными температуры процессора.
- Проверьте работоспособность с помощью утилиты
ros2 topic echo, после перезагрузки робота
Задание
В задании курсовой работы вам понадобятся все знания, полученные при прохождении данного курса. Курсовая работа рассчитана на 2-3 урока для самоподготовки и 1 для финального испытания.
Задание
Вам необходимо разработать пакет ROS (my_robot), установить его на робота и продемонстрировать его работу.
Запуск необходимых сервисов должен происходить при включении робота.
- Движение по квадрату
- При нажатии на кнопку D15 робот должен начать двигаться по квадрату с диагональю 1 метр
- Придумать и разработать алгоритм движения, позволяющий наиболее точно и быстро достигать точки вершин квадрата
- Скорость движения робота должны быть максимально возможной, но при этом должны выполняться условия на точность достижения вершин (ошибка не более 5% в каждой точки относительно точки старта
- Координаты и погрешности вершин [1±0.05,0±0.05],[1±0.05,1±0.05],[0±0.05,1±0.05],[0±0.05,0±0.05]
- Движение к стенке и обратно
- Робот устанавливается на произвольном расстоянии (не более 2-x метров) от стенки. Угол по направления к стенке +-45 градусов.
- При нажатии на кнопку D15 робот должен начать прямолинейное движение
- Когда минимальное расстояние до стенки 30см
- Робот останавливается
- Рассчитывает необходимые данные для возвращения в точку старта
- Возвращается в точку старта и останавливается.
- Движение вдоль стенки
- Робот устанавливается параллельно стенке (с погрешностью +-10 градусов) на расстоянии (от 30см до 1м)
- При нажатии на кнопку D15 робот должен начать движение параллельно стенки
- При движении вдоль стенки, робот должен остановиться за 50 см до перпендикулярного препятствия перед стенкой.
- После остановки должна быть включена RGB лента красным цветом.
Глоссарий терминов ROS2
В этом разделе собраны определения основных терминов, используемых в курсе изучения ROS2.
И
Издатель (Publisher) — программа (нода) в ROS, которая отправляет сообщения в топик. Издатель публикует данные, не зная, кто их получает.
К
Кватернион (Quaternion) — математический способ описания ориентации объекта в трехмерном пространстве с помощью четырех чисел (x, y, z, w). Используется в ROS для представления углов поворота робота.
Клиент сервиса (Service Client) — программа, которая создает запрос к серверу сервиса и отправляет его. Клиент ожидает ответ от сервера.
Л
Launch-файл — файл конфигурации в ROS, который позволяет запускать несколько нод одновременно, настраивать их параметры и управлять зависимостями между ними. Может быть написан в форматах XML, YAML или Python.
Н
Нода (Node) — отдельная программа в ROS2, которая выполняет конкретную задачу. Например, нода может читать данные датчика, управлять мотором или обрабатывать изображения. Ноды общаются друг с другом через топики и сервисы.
О
Одометрия (Odometry) — процесс оценки изменения положения робота во времени относительно начальной точки. Основан на интегрировании данных о движении (обороты колес, данные IMU). Подвержена накоплению ошибок.
П
Пакет (Package) — основная единица организации кода в ROS2. Пакет содержит все необходимое для определенного функционального модуля: узлы (ноды), библиотеки, данные конфигурации, файлы запуска, зависимости.
Параметр (Parameter) — глобальная переменная в ROS, которая хранит конфигурационные данные. Параметры можно изменять без перекомпиляции кода, через консоль или launch-файлы.
Подписчик (Subscriber) — программа (нода) в ROS, которая получает сообщения из топика. Подписчик автоматически получает все новые сообщения, публикуемые в топик.
Р
Рабочее пространство (Workspace) — директория в ROS2, где создаются, модифицируются и собираются собственные пакеты. Содержит директории src (исходный код), build (временные файлы сборки), install (установленные файлы) и log (логи сборки).
С
Сервер сервиса (Service Server) — программа, которая ожидает запросы от клиентов, выполняет необходимые вычисления и возвращает ответ.
Сервис (Service) — модель коммуникации в ROS, использующая паттерн Request-Response (запрос-ответ). Клиент отправляет запрос, сервер обрабатывает его и возвращает ответ. В отличие от топиков, сервис работает по принципу "один запрос — один ответ".
Сообщение (Message) — структурированный набор данных определенного типа, используемый для обмена информацией между нодами в ROS. Каждое сообщение имеет строго определенную структуру и тип.
Т
Топик (Topic) — канал связи между нодами в ROS, через который передаются сообщения. Топик имеет уникальное имя и определенный тип сообщений. Издатели публикуют сообщения в топик, подписчики получают их из топика.
У
Углы Эйлера (Euler Angles) — способ описания ориентации объекта с помощью трех углов поворота вокруг осей координат (крен, тангаж, рыскание). Более понятны для работы в 2D-пространстве, чем кватернионы.
Дополнительные термины
colcon — система сборки пакетов в ROS2. Используется для компиляции и установки пакетов в рабочем пространстве.
rclpy — клиентская библиотека ROS2 для языка Python. Предоставляет API для создания нод, работы с топиками, сервисами и параметрами.
Twist — тип сообщения в ROS (geometry_msgs/msg/Twist), используемый для управления движением робота. Содержит векторы линейной и угловой скорости.
Odometry — тип сообщения в ROS (nav_msgs/msg/Odometry), содержащий данные о текущем положении, ориентации и скорости робота.
IMU (Inertial Measurement Unit) — инерциальный измерительный модуль, датчик, содержащий гироскоп, акселерометр и иногда магнитометр. Используется для определения ориентации и ускорения робота.
LIDAR (Light Detection and Ranging) — лазерный дальномер, датчик, который измеряет расстояния до объектов с помощью лазерных лучей. Создает облако точек (point cloud) для построения карты окружения.
microROS — версия ROS2, адаптированная для работы на микроконтроллерах с ограниченными ресурсами (например, Arduino).
Callback-функция — функция, которая автоматически вызывается при наступлении определенного события (например, при получении нового сообщения в топик).
QoS (Quality of Service) — параметры качества обслуживания в ROS2, определяющие надежность доставки сообщений, глубину очереди и другие характеристики коммуникации.
Создатели курса
Курс создан проектом «Братья Вольт» voltbro.ru для изучения современного фреймворка ROS на основе мобильного робота TurtleBro.
Авторы курса
- Слабуха Николай
Обратная связь
Группа в телеграм ROS в образовании с проектом "Братья Вольт"