Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ROS2 на базе робота TurtleBro

Данный курс направлен на изучение базовых аспектов фреймворка ROS (Robot Operating System) версии jazzy (ROS2). Курс разработан для самостоятельного освоения.

Для успешного прохождения курса рекомендуется использовать робот TurtleBro. Материал курса адаптирован специально для этого оборудования.

Для прохождения курса вам понадобятся базовые знания языка python и операционной системы linux.

Каждый модуль курса включает в себя теоретическую часть и практическое задание, направленное на закрепление полученных знаний и навыков.

Желаем вам успешного прохождения курса

Robot

Что такое 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

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

alt text

Для MacOS

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

iTerm2

OS базе Linux (Ubuntu, Debian)

Нужно воспользоваться встроенным терминалом SSH.

Terminal

Для удобства, прикрепите программу 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

Далее вы увидите подключение, похожее на экран ниже. ssh_connection

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

ip a

Вы увидите вывод команды

ip a

Раздел eth0, inet 192.168.51.83, это IP адрес в Ethernet подключении.

Раздел wlan0, inet 192.168.51.84 это IP адрес в WiFi подключении.

Если не работает подключение по имени

Если подключится к роботу по имени не получилось, вы можете зайти панель управления роутера и посмотреть подключения к сети, чтобы узнать IP вашего робота.

Например для роутера ASUS

ASUS

Подключение к web-редактору кода VSCode

Для задач быстрого прототипирования, есть возможность использовать web-редактор VSCode, который установлен на образ робота.

Для этого необходимо зайти в браузере на страницу http://192.168.51.83:8090 (указав IP вашего робота).

Пароль для доступа к редактору brobro

Web-IDE

Во внешнем виде WEB-редактора VSCode мы видим структуру файлов робота. Файлы, созданные в редакторе, будут создаваться в файловой системе робота.

Подключение через "нативный" VSCode

Если функционал web редактора не устраивает, то есть возможность подключится к роботу через VSCode по ssh.

alt text

Запустите VSCode. Нажмите на синии скобочки в низу редактора, выберите Connect to Host.

Укажите ssh хост робота, например pi@192.168.51.81 Дождитесь установки плагина, нажмите "Open Folder", выберите директорию которую необходимо подключить, для доступа к файлам.

Также в разделе расширений VSCode проверьте, что установлено расширение для работы с python непосредственно для подключения. Некоторые расширения требуют дополнительной установки на удаленные системы.

Пример подключения расширения python. Мы видим подсказку для методов python для работы с ROS.

VsCode+python

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

https://stepik.org/course/73/

Время прохождения курса: 14 часов, курс содержит видеоматериалы.

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

Задание 1: Настройка рабочего места

Шаги:

  1. Установите VSCode и SSH-клиент для вашей ОС
  2. Включите робота и дождитесь загрузки (2 минуты)
  3. Определите IP робота через панель роутера или по имени turtlebroNN.local
  4. Откройте web-интерфейс: http://IP_РОБОТА:8080

Задание 2: Подключение к роботу

Шаги:

  1. Подключитесь по SSH: ssh pi@turtlebroNN.local (пароль: brobro)
  2. Откройте web-редактор: http://IP_РОБОТА:8090 (пароль: brobro)
  3. Подключитесь через VSCode по SSH (расширение Remote-SSH)

Задание 3: Работа с файлами

Шаги:

  1. Создайте файл /home/pi/myrobot.txt с IP-адресом робота
  2. Выполните 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) — это тот, кто забирает письма из ящика или подписывается на канал и смотрит видео. Несколько подписчиков могут читать одни и те же письма или смотреть одно и то же видео. Топик — это сам ящик или канал, который имеет название (например, "/температура" или "/скорость") и определенный тип контента (только письма о температуре, только видео о скорости).

Иллюстрация работы Издателя, Подписчика и Топика.

MultiplePublisher

Работа с топиками через консоль (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: Работа с сообщениями

Шаги:

  1. Используйте ros2 interface list для поиска подходящих сообщений
  2. Для каждого устройства подберите тип сообщения:
    • Датчик давления воздуха (в Паскалях, с точностью до 3-го знака после запятой);
    • Ультразвукового сонара (в мм);
    • Управление сервоприводом (углы в градусах);
    • Цвет RGB светодиода

Задание 2: Топик, Издатель (CLI)

Шаги:

  1. Изучите команды: ros2 topic list, ros2 topic echo, ros2 topic info
  2. Просмотрите данные топиков: /imu, /scan, /bat
  3. Измените частоту в примере урока: timer_period = 0.2 (5 Гц)

Задание 3: Топик, Издатель (Python)

Шаги:

  1. Создайте скрипт counter_publisher.py с таймером 0.2 сек
  2. Используйте std_msgs/msg/Int32, счетчик увеличивайте в timer_callback
  3. Запустите скрипт и проверьте: ros2 topic echo /counter
  4. Проверьте частоту: 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: Движение по кругу

Шаги:

  1. Создайте cmd_vel1_publisher.py с одновременной линейной (0.16 м/с) и угловой (2 рад/с) скоростью

  2. Запустите программу и засеките время полного круга

  3. Добавьте остановку робота после завершения

  4. Напишите программу на python (cmd_vel3_publisher.py), которая заставит робота двигаться по заданному алгоритму:

    1. Движение вперед в течение 5 секунд со скоростью 0.1 м/с
    2. Разворот на 180 градусов (рассчитать время разворота робота)
    3. Движение в течение 5 секунд вперед (обратно к точке старта)
    4. Разворот на 180 градусов (в стартовое положение)
    5. Выход из программы

    В итоге робот должен оказаться около точки старта в том же положении в котором стартовал.

Подписчик (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 оборотов", робот с помощью математической модели вычисляет, что он:

  1. Проехал примерно 3 метра.
  2. Повернул направо на 15 градусов.
  3. И теперь находится в новой точке с новыми координатами (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

Довольно серьезный набор данных:

  1. Блок std_msgs/Header header содержит стандартный набор параметров, который очень часто используется в системных сообщениях, который содержит время создания сообщения и объект, относительно которого создано сообщение;

  2. Блок geometry_msgs/PoseWithCovariance pose содержит данные о положении робота в пространстве. Сообщение содержит данные: geometry_msgs/Point position; geometry_msgs/Quaternion orientation; covariance

  • position это переменные расположения робота в осях XYZ, где Z это высота;
  • orientation — для определения ориентации (углов) объектов в трехмерном пространстве в робототехнике используется система кватернионов. Далее мы приведем примеры, как эти комплексные значения можно перевести в более привычные углы Эйлера;
  1. Блок 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)

Дополнительные материалы

  1. https://ru.wikipedia.org/wiki/Кватернионы_и_вращение_пространства
  2. https://www.youtube.com/watch?v=d4EgbgTm0Bg&t=1398s
  3. https://habr.com/ru/post/426863/
  4. Описание структуры данных Odometry

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

Шаги:

  1. Создайте программу Подписчик odom_subscriber.py
    1. Подпишитесь на топик /odom
    2. Выведите данные о положении робота в терминале:
      • Данные положения робота в формате: 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: Движение туда-обратно (синхронный подход)

Шаги:

  1. Создайте move_straight.py с подпиской на /odom и издателем /cmd_vel
  2. Сохраните стартовую позицию при запуске
  3. В callback одометрии проверяйте расстояние до цели (1 м)
  4. При достижении цели развернитесь и вернитесь в стартовую точку
  5. Остановите робота при возврате

Задание 2: Движение по квадрату (асинхронный подход)

Шаги:

  1. Создайте move_square.py с таймером для проверки положения
  2. Реализуйте логику движения к 4 вершинам квадрата (1×1 м)
  3. В таймере проверяйте достижение каждой вершины
  4. После 4-й вершины вернитесь в стартовую точку

Задание 3: Анализ точности

Шаги:

  1. Запустите программы несколько раз и зафиксируйте ошибки
  2. Проанализируйте причины: накопление ошибок одометрии, проскальзывание колес
  3. Предложите решения: коррекция по датчикам, уменьшение скорости, использование IMU

Сервисы

Что вы узнаете в этом разделе:

  • Что такое сервисы в ROS и чем они отличаются от топиков
  • Модель взаимодействия Request-Response
  • Что такое клиент и сервер сервиса
  • Как работать с сервисами через консоль (CLI)
  • Как просмотреть список доступных сервисов
  • Как получить информацию о сервисе и вызвать его

Сервис (Service)

Ранее мы познакомились с такой сущностью как топики. По сути, работа с ними, это широко используемая Publisher-Subscriber модель коммуникации. Одни программы постоянно публикуют данные, другие эти данные получают. Использовать ее удобно, когда у нас есть постоянный поток данных (например данные датчиков и тп).

Но это не единственная модель коммуникации. Сегодня мы поговорим об еще одной модели взаимодействия между модулями в ROS, это модель Сервис (Service), которая использует паттерн взаимодействия Request-Response.

Эта модель коммуникации очень похожа на "удаленный" вызов функции. Одна программа вызывает функцию создавая Запрос (ServiceRequest), а другая программа получая запрос, формирует Ответ (ServiceResponse), и возвращает ее программе вызвавшей сервис.

Аналогия из жизни: Сервис можно сравнить с походом в ресторан. Клиент (Service client) делает заказ (запрос) официанту: "Принесите мне пиццу". Официант (сервер, Service server) передает заказ на кухню, получает готовое блюдо (ответ) и приносит его клиенту. Это взаимодействие "вопрос-ответ": клиент задает вопрос и ждет конкретный ответ. В отличие от топиков, где данные "текут" постоянно, сервис работает по принципу "спросил-получил ответ". Это как звонок в службу поддержки: вы звоните, задаете вопрос, получаете ответ и разговор заканчивается.

Запрос и Ответ обычно содержат переменные, необходимы для работы программы, но также они могут быть пустыми.

Мы можем выделить еще два определения, которые касаются Сервисов

  • Клиент (Service client) -- это программа, которая создает Запрос, и отправляет его на Сервер.
  • Сервер (Service server) -- это программа, которая ожидает Запрос от Клиента, производит вычисления и отправляет результат вычислений Ответ (в том числе и "пустым" ответом).

Service

Работа с сервисами через 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

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

  1. Разработайте программу "перезапуска" лидара 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

  1. Создайте директорию для workspace: Откройте терминал и выполните:

    mkdir -p ~/user_ws/src
    

    Флаг -p гарантирует, что будут созданы все родительские директории, если их не существует.

  2. Инициализация пустого 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 имеет инструкцию по сборке, так и пакет имеет файлы конфигурации, которые объясняют системе, как его собрать и использовать. Пакеты можно переиспользовать, комбинировать и делиться ими с другими, как готовые модули конструктора.

Зачем их создавать?

  1. Модульность: Вы разбиваете сложную систему робота (например, навигация, управление манипулятором) на независимые, хорошо определенные компоненты. Каждый пакет решает свою конкретную задачу.
  2. Переиспользование: Хорошо написанный пакет можно легко скопировать и использовать в другом проекте или поделиться с сообществом.
  3. Управление зависимостями: Пакет явно объявляет, от каких других пакетов или библиотек Python он зависит. Это позволяет инструментам ROS2 автоматически устанавливать всё необходимое.
  4. Сборка: Система сборки (colcon) знает, как собрать и установить именно ваш код, потому что он оформлен в виде пакета с правильными файлами конфигурации.
  5. Простота тестирования: Можно писать и запускать тесты для конкретного пакета, не затрагивая всю систему.

Создание первого пакета

Есть некоторое отличие в создании пакета для разработки на 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

Ключевые файлы и их настройка:

  1. 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).
  2. setup.py: Главный скрипт для сборки Python-пакета.

    • Самая важная часть — точка входа (entry_points), где вы регистрируете свои узлы как консольные скрипты.
    entry_points={
        'console_scripts': [
            'my_first_node = my_first_package.my_first_node:main',
            # 'имя_исполняемого_файла = python_пакет.имя_файла:функция_main'
        ],
    },
    

    При сборке пакета colcon создаст исполняемые файлы в системе, которые будут напрямую вызывать вашу функцию main.

  3. Файл c программой my_first_node.py

def main():
    print('Hi from my_first_package.')


if __name__ == '__main__':
    main()

Сборка, настройка окружения и запуск

Под сборкой пакета, мы понимаем набор множества операций над исходным кодом, которые необходимо выполнить для того, чтобы созданные программы ROS могли функционировать. Можем выделить несколько ключевых операций: компилирование исходного кода (для программ на cpp), установка и проверка зависимостей, копирование различных файлов.

Результатом успешной сборки является создание специальных директорий ( build, install и log) в рабочем пространстве. В папке install создаются все исполняемые файлы, библиотеки, конфигурации и другие файлы (артефакты), необходимые для запуска пакета. Именно из этой директории будут запускаться программы.

  1. Сборка: Вернитесь в корень рабочего пространства (~/user_ws) и выполните:
colcon build --packages-select my_first_package

Флаг --packages-select указывает собрать только один конкретный пакет.

  1. Настройка окружения: После сборки необходимо добавить рабочее пространство с нашим пакетом в сессию терминала.
source ~/user_ws/install/setup.bash

(Это нужно делать в каждом новом терминале! Чтобы избежать этого, можно добавить эту строку в ваш ~/.bashrc).

  1. Запуск первой программы: Теперь вашим узлом можно управлять как любой другой 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

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

  1. Создайте собственное рабочее пространство user_ws

    • Добавьте инициализацию окружения в файл .bashrc
  2. Создайте пакет 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

  1. Создайте cpu_info_publisher.py с таймером 1 сек
  2. Читайте температуру из /sys/class/thermal/thermal_zone0/temp
  3. Читайте частоты CPU из /sys/devices/system/cpu/cpuN/cpufreq/scaling_cur_freq (N=0..3)
  4. Публикуйте в топик с типом robot_msgs/msg/CPUInfo
  5. Добавьте в пакет и протестируйте через ros2 run

Задание 2: Сервис сложения трех чисел

  1. Создайте add_three_ints_server.py с сервисом типа robot_msgs/srv/AddThreeInts
  2. В callback суммируйте request.x + request.y + request.z
  3. Возвращайте результат в response.sum
  4. Добавьте в пакет и протестируйте: ros2 service call /add_three_ints robot_msgs/srv/AddThreeInts "{x: 1, y: 2, z: 3}"

Задание 3: Новое сообщение

  1. Создайте msg/RobotState.msg в пакете robot_msgs
  2. Добавьте поля: geometry_msgs/Twist twist и sensor_msgs/Imu imu
  3. Обновите CMakeLists.txt и package.xml (добавьте зависимости)
  4. Соберите пакет и проверьте: 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

  1. Настройте Arduino IDE согласно инструкции робота
  2. Установите библиотеку micro_ros_arduino
  3. Скачайте и откройте скетч microros_publisher
  4. Запустите microROS-агент: ros2 run micro_ros_agent micro_ros_agent serial --dev /dev/ttyACM0 -b 115200
  5. Загрузите скетч на Arduino
  6. Проверьте топик: ros2 topic echo /micro_ros_arduino_node_publisher

Задание 2: microROS Subscriber

  1. Скачайте и откройте скетч microros_subscriber
  2. Изучите код: программа подписывается на топик и управляет RGB-лентой
  3. Загрузите скетч на Arduino
  4. Опубликуйте тестовые данные: ros2 topic pub /micro_ros_arduino_node_subscriber std_msgs/msg/Int32 "data: 255"
  5. Наблюдайте изменение цвета 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

Если вам необходимо добавить новые ноды или изменить конфигурацию запуска, вы можете придерживаться следующего порядка:

  1. Остановить действующую систему.
  2. Внести изменения в launch файл (turtlebro.launch)
  3. Запустить все ноды через запуск сервиса turtlebro
sudo systemctl start turtlebro

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

  1. Добавьте параметр в пример Издателя температуры

    • Добавьте параметр frequency в пример издателя температуры, который будет отвечать за частоту публикации данных
    • Создайте параметр в launch файле, в котором определите частоту в 20 герц.
  2. Запуск одновременно двух нод

    • Создайте отдельный launch-файл для запуска ноды с подписчиком на температуру. (temp_topic_subscriber.py)
    • Создайте единый launch-файл, запускающий два уже созданных .launch файла
  3. Настройка автостарта Издателя температуры

    • Добавить в автозагрузку робота запуск Издателя с данными температуры процессора.
    • Проверьте работоспособность с помощью утилиты ros2 topic echo, после перезагрузки робота

Задание

В задании курсовой работы вам понадобятся все знания, полученные при прохождении данного курса. Курсовая работа рассчитана на 2-3 урока для самоподготовки и 1 для финального испытания.

Задание

Вам необходимо разработать пакет ROS (my_robot), установить его на робота и продемонстрировать его работу.

Запуск необходимых сервисов должен происходить при включении робота.

  1. Движение по квадрату
  • При нажатии на кнопку 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]
  1. Движение к стенке и обратно
  • Робот устанавливается на произвольном расстоянии (не более 2-x метров) от стенки. Угол по направления к стенке +-45 градусов.
  • При нажатии на кнопку D15 робот должен начать прямолинейное движение
  • Когда минимальное расстояние до стенки 30см
    • Робот останавливается
    • Рассчитывает необходимые данные для возвращения в точку старта
    • Возвращается в точку старта и останавливается.
  1. Движение вдоль стенки
  • Робот устанавливается параллельно стенке (с погрешностью +-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 в образовании с проектом "Братья Вольт"