Обзор основных концепций ROS. Топики (Topics) | Сообщения (Messages).
Друзья, привет! 👋
Продолжаем наш цикл статей о ROS.
Статьи цикла:
Наш Телеграм канал, где мы описываем прогресс нашего проекта — https://t.me/it_s_working.
Полезные ссылки:
- Репозиторий ROS2
- ROS2 концепции
- Обмен сообщениями
- Репозиторий с исходниками использованными в данной статье
Введение
В данной статье мы продолжим рассказывать об исследованиях графа вычислений ROS (ROS Computation Graph).
В прошлый раз мы коснулись темы Нод/Узлов — вычислительных процессов выполняющих узкоспециализированные задачи, но могут ли Ноды поделиться результатами своей работы с другими Нодами, могут ли они получать и использовать эти результаты в своих целях — конечно да, и в этом помогают концепции Тем (Topics) и Сообщений (Messages). В данной статье мы подробнее остановимся на этом.
🔍 Что такое темы и сообщения?
Тема/Топик (topic)
Темы/Топики — это именованные шины, по которым узлы (Node) обмениваются сообщениями.
Представьте, что у вас есть группы экспертов в комнате:
- Одни производят информацию (например, датчики)
- Другие потребляют её (алгоритмы управления)Но они не знают друг друга лично! Как им общаться? Через топики — тематические «доски объявлений».
🔑 Ключевые свойства:
Анонимность
Ноды публикуют и читают данные, не зная друг о друге. Датчик температуры просто «вещает» в эфир, а все заинтересованные системы (кондиционер, логгер, интерфейс) сами «прослушивают» его.Сильная типизация
Каждый топик работает только с одним типом сообщений. Попробуете отправить число вместо строки? ROS скажет: «Извините, я вас не понимаю!»
❗ Важно: Все участники должны использовать одинаковые версии форматов сообщений (проверка по MD5-сумме).Мультиподключение
Один топик может иметь:Несколько издателей (например, 2 камеры → топик /cameras)
Множество подписчиков (алгоритм навигации + система записи → топик /cameras)
Транспорт данных
По умолчанию используется TCPROS (надёжный, потоковый), TCPROS — это транспорт по умолчанию, используемый в ROS, и единственный транспорт, который должны поддерживать клиентские библиотеки. Но есть и UDP (для быстрых, но возможных потерь данных).
🛠 Инструмент topic
Хотите отладить обмен данными? В ROS есть инструмент — topic, предоставляющий следующие полезные команды:
list
ros2 topic list
Список всех активных топиков
info
ros2 topic info /sensor_temp
Показать тип сообщения, издателей и подписчиков
(где /sensor_temp — имя топика)
echo
ros2 topic echo /sensor_temp
Выводить сообщения в реальном времени
(где /sensor_temp — имя топика)
pub
ros2 topic pub /sensor_temp std_msgs/Bool True
Опубликовать сообщение вручную
(где /sensor_temp — имя топика)
bw
ros2 topic bw /sensor_temp
Измерить пропускную способность
(где /sensor_temp — имя топика)
hz
ros2 topic hz /sensor_temp
Проверить частоту обновления
(где /sensor_temp — имя топика)
find
ros2 topic find sensor_msgs/LaserScan
Найти топики по типу сообщения
✉ Сообщения (Messages)
Представьте, что топики — это радиоканалы, а сообщения — конкретные песни или новости, которые по ним передаются. Это структурированные данные, которые ноды используют для обмена информацией.
📦 Что такое сообщение?
- Простая структура данных (как конверт с заполненными полями)
- Содержит типизированные поля: числа (int32, float64), логические значения (bool), строки (string), массивы (float32[])
- Могут содержать вложенные структуры
Пример
🖋 Как создаются сообщения?
Типы сообщений используют стандартные соглашения об именовании ROS: имя пакета + /msg/ + имя файла .msg.Например, std_msgs/msg/String.msg имеет тип сообщения std_msgs/String.
Через .msg-файлы в папке msg вашего пакета. В виде простого текстового формата см. документацию
🔐 Важные особенности:
Строгая типизация
Нельзя отправить строку вместо числа — ROS выдаст ошибкуВерсионность через MD5
При изменении структуры сообщения автоматически меняется его «цифровой отпечаток». Разные версии несовместимы!Сообщение может включать специальный тип сообщения, называемый «Заголовок», который включает некоторые общие поля метаданных, такие как временная метка и идентификатор кадра. Клиентские библиотеки ROS автоматически установят некоторые из этих полей для вас, если вы этого захотите, поэтому их использование настоятельно рекомендуется.
В заголовке сообщения, показанном ниже, есть три поля:
- Поле seq соответствует идентификатору, который автоматически увеличивается по мере отправки сообщений от данного издателя.
- Поле stamp хранит информацию о времени, которая должна быть связана с данными в сообщении. Например, в случае лазерного сканирования stamp может соответствовать времени, когда было выполнено сканирование.
- Поле frame_id хранит информацию о кадре, которая должна быть связана с данными в сообщении. В случае лазерного сканирования это будет установлено на кадр, в котором было выполнено сканирование.
Рабочий пример
Примечание: в данной статье мы продолжаем использовать ROS в Docker контейнере. Установка описана в предыдущей статье.
Мы можем поступить двумя способами:
Использовать контейнер с предустановленным ROS и создавать необходимые пакеты в рамках работы с контейнером.
Учитывая, что пакет, по сути, — это определенная структура папок, подчиняющаяся стандарту ROS, мы можем подготовить необходимые пакеты заранее и копировать их в контейнер для работы на лету в процессе запуска.
Мы последуем подходу №2, поскольку он представит более удобный опыт, а также результат нашей работы будет сохранен.
Вы можете найти все описанные далее файлы в нашем репозитории
Мы планируем реализовать следующую схему:
Итак, приступим.
Издатель (Publisher) сообщений
Будем пользоваться структурой папок, полученной по команде создания нового пакета:
Мы планируем работать с сообщениями. Для этого необходимо объявить требуемые зависимости в файле packge.xml:
Далее убедимся что entrypoint для ноды задан верно. Для этого проверим файл setup.py
Теперь мы готовы описать логику нашей ноды. Она будет простая. В прошлой статье мы создавали ноду, которая писала логи в заданный интервал времени, а в этот раз сделаем ноду которая будет публиковать сообщения в топик. Также — по таймеру.
В целом, код ноды похож на наш недавний пример. Однако, есть отличия. Остановимся на них подробнее.
В качестве сообщений мы планируем пока передавать простые текстовые сообщения. Для этого мы добавили импорт строкового типа сообщения, предоставляемого библиотекой rclpy:
Логика конструктора класса нашей ноды была также обновлена:
Строка
объявляет, что нода публикует сообщения типа String (импортированные из модуля std_msgs.msg) в топик pub_topic и что «размер очереди» равен 10.
Метод обработчика событий таймера также изменился:
Основные изменения связаны с необходимостью публиковать сообщения.
Для начала мы создаем сообщение для типа string:
Далее мы задаем содержимое нашего сообщения. Это будет текст:
И в конце мы публикуем созданное сообщение в топик:
Для докер-образа мы будем использовать следующий Dockerfile
Для запуска ноды нам будет достаточно собрать и запустить представленный докер-образ:
После запуска мы увидим логи с информацией о публикуемых сообщениях:
Проинспектируем ROS, чтобы узнать какие данные о созданном топике мы можем получить. Для этого мы будем использовать описанный ранее инструмент ros2 topic.
Запросим список доступных топиков в системе. Выполним в новом терминале следующую команду:
Мы видим несколько стандартных для Ros топиков:
rosout — топик, отвечающий за логирование (http://wiki.ros.org/rosout);
Parameter_events — обрабатывает любые события, связанные с параметрами, такие как изменение или удаление параметра, с использованием ParameterEvent.msg;
И созданный нами pub_topic;
Запросим данные о нашем топике:
Как мы видим, доступна информация
- о типе сообщений, публикуемых в данном топике;
- о количестве нод, публикующих (publisher count) сообщения в данный топик
- о количестве нод, читающих (subscription count) сообщения из данного топика.
Теперь проверим сообщения, которые доступны в данном топике
На скриншоте сеанса терминала слева видно результат работы ноды.
На скриншоте сеанса работы терминала справа видно результат работы команды echo.
Можно заметить, что как только нода сообщает нам об отправке сообщения в топик, команда echo обнаруживает факт публикации нового сообщения в топике.
Подписчик (Subscriber) сообщений
Будем пользоваться структурой папок, полученной по команде создания нового пакета:
Шаги по настройке зависимостей и entrypoint будут идентичны действиям описанным для пакета publisher’a (смотри предыдущий раздел).
Подготовим логику для ноды, которая будет обрабатывать сообщения топика:
Как видно, текст почти идентичен тексту ноды publisher, однако, есть и отличия:
В конструкторе класса ноды создаётся подписка
self.create_subscription — при создании определяется
- тип/формат передаваемых сообщений msg_type=String
- имя топика для чтения: topic='pub_topic'
- ссылка на метод обработчик сообщений: callback=self.listener_callback
- очередь сообщений:qos_profile=10
Логика метода обработки получаемых сообщений:
В нашем случае, планируется просто выводить сообщения с текстом, полученным из топика.
Учитывая, что структура создаваемого пакета и имя совпадают с прошлым примером Dockerfile будет идентичным
Для запуска ноды будет достаточно собрать и запустить представленный докер образ:
Запустив два образа параллельно можно будет увидеть как публикуемые в топик сообщения успешно читаются и выводятся созданной нодой подписчика:
В данной статье мы описали основные принципы в работе с топиками, разобрали отправку и чтение нодами сообщений, а также, что немаловажно, разобрали отладку этого обмена.
В следующих статьях, используя полученные знания, мы расскажем о развёртывании и настройке SLAM.
Также мы продолжим готовить обзоры основных концепций ROS.
Подписывайтесь на наш Телеграмм канал, где мы публикуем как сами применяем полученные знания на реальном примере — популярно делимся нашими результатами, вместе будет интереснее https://t.me/it_s_working!