МОСКОВСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ ИМЕНИ Н.Э. БАУМАНА | Дмитровский филиал | English


Регистрация и вход в систему

   Вход в систему

   Регистрация

   Поменять пароль

   Выход из системы

Общие сведения о функционально-моделирующем стенде

   Принцип действия

   Состав

   Программное обеспечение

   Моделирующая система реального времени

   Пульт управления манипулятором

   Система избегания столкновений

   Система подготовки миссий

   Управление исполнительными устройствами

   Обработка внешних воздействий

   Средства оценки операторов

   Исполнительные устройства

   Промышленные роботы

   Трехпальцевый центрирующий схват

   Очувствленная кисть

   Системы очувствления

   Системы технического зрения

   Система измерения сил и моментов

   Тактильные сенсоры кисти

   Обзорные камеры

   Органы управления

Видео

Удаленное управление функционально-моделирующим стендом

   Методика удаленного управления

   Схема удаленного доступа

   Интерфейс удаленного управления

   Формирование миссии робота

Проведение удаленного эксперимента

Контактная информация

Библиография

Введение в ROS

   Уроки

   Тестирование
 
 Урок 3

Урок 3

Урок 3. Симулятор Turtlesim

3.1 Краткое изложение предыдущего урока и план на этот урок

Мы узнали, что такое рабочее пространство catkin, создали в рабочем пространстве первый пакет ROS и получили первое представление о структуре пакета, на языке Python создали в парадигме ROS две программы – издателя и подписчика, научились создавать файлы запуска для сложных проектов, научились искать и удалять пакеты ROS, узнали про фреймворк rqt. Попутно мы узнали про права файлов, настройку версии Python по умолчанию, ну и потихоньку осваивались в командной строке. В этом уроке мы запустим нашу первую симуляцию мобильного робота, коротко поговорим о кинематике и поиграемся с черепашкой – конечно же, не с живой черепахой, а моделью робота, похожего на черепаху, находящегося в некотором окружении.

3.2 Симулятор черепашки

Сегодня мы познакомимся с очень простой компьютерной моделью робота, работающего в двумерном пространстве – turtlesim. В ROS wiki этому “роботу” посвящена своя страница, и можно сказать, что он очень популярен в учебных курсах на раннем этапе обучения, так как с обной стороны это почти полноценный мобильный робот, а с другой стороны мы ничего не сможем сломать, когда будем пробовать его в работе. Так как turtlesim практически официальное учебное пособие, мы скорее, всего найдём ROS-пакет для него уже установленным на нашем компьютере. Ну если мы ставили версию full desktop. Давайте вспомним, как с помощью команды rospack и поиска grep искать пакеты и проверим:

rospack list | grep turtlesim

Если пакет найден, замечательно, если не найден, ничего страшного, поставим его на компьютер с помощью команды, которую можно найти на страничке в ROS-вики:

sudo apt-get install ros-$(rosversion -d)-turtlesim

Сразу вопрос, что я забыл сделать? Всё верно, я не сделал apt update, а это очень, очень некрасиво. Вы можете не покормить своего кота, но не сделать апдейт перед установкой вы не можете. Ну хорош, согласен, с котом перебор, но в принципе это кощунство одного порядка. Вернёмся от котиков к черепахе. Давайте попробуем её запустить. Для этого надо запустить ROS-мастер, а потом взять команду запуска черепахи со странички turtlesim. Ну или попробовать запустить её самостоятельно с помощью rosrun, имени пакета и волшебства TAB. Для запуска ноды (узла ROS, так я иногда буду называть его далее) turtlesim из списка
turtlesim_nodes.png

я бы выбрал ноду с именем turtlesim_node, вроде бы это логично. Давайте попробуем. Ну и если вы ещё не запустили терминал, самое время это сделать. Итак:

roscore
# Открыть новый терминал
rosrun turtlesim turtlesim_node

Я вижу вот такой рисунок.
turtlesim_gui.png

“Значит, сработала эта хреновина!”

Всё хорошо, мы, в отличие от героев Кин-дза-дзы, этого и добивались. Если вы видите у себя на экране другую черепашку, а вы видите другую черепашку, не переживайте, это нормально, она для каждого запуска разная. Отлично, мы запустили процесс симуляцию некоего мобильного робота похожего на черепашку и теперь можем посмотреть, что же она рассказывает о себе ROS. Давайте откроем новый терминал и посмотрим, какие темы сейчас есть в нашем ROS. Как посмотрим? Конечно, с помощью команды rostopic list:
turtlesim_topics.png

Мы снова видим надоевшие rosout и rosout_agg, но пора к ним привыкнуть, мы их видим всегда, если запущен ROS-мастер. А вот следующие три темы – это явно то, что нас интересует. Можно предположить, что тема cmd_vel – управление скоростью, pose – информация о положении, а color_sensor – некий датчик цвета. Давайте применим в команде rostopic новый ключ info и изучим эти топики (я уже говорил, что буду так иногда называть темы?):

rostopic info /turtle1/cmd_vel

Посмотрим на результат:
topic_info_cmd_vel.png

Мы видим здесь, что формат этого сообщения geometry_msgs/Twist. “Геометрические сообщения” или geometry_msgs – это ещё один набор типов сообщений, типа того, что мы уже использовали – std_msgs. Давайте посмотрим, что входит в этот набор, перейдём в общую папку ROS, папку, которая содержит всё пакеты, что были установлены в процессе установки ROS. Перейдём в папку geometry_msgs общей папки. Так это тоже ROS-пакет, за файлами сообщений нам надо будет нырнуть в папку msg внутри папки этого пакета:

ls /opt/ros/noetic/share/geometry_msgs/msg/

Посмотрим на результат:
geometry_msgs.png

Давайте посмотрим содержимое файла с определением сообщения Twist с помощью ещё одной полезной команды – cat.

cat /opt/ros/noetic/share/geometry_msgs/msg/Twist.msg

Команда эта имеет мощный функционал и предназначена в первую очередь для соединения файлов и отправки результата в стандартный вывод stdout (для нас, работающих в терминале, сейчас это вывод текста в терминал), но сейчас нам будет полезен побочный эффект, заключающийся в том, что единственный файл, заданный в качестве аргумента команды cat, также попадёт в стандартный вывод. Итак, результат:
twist_file.png

Здесь мы видим, что сообщение составлено из “линейной” и “угловой” компоненты, каждая из которых также является неким вектором-три. Что “три”, будем выяснять. Если мы с помощью команды cat посмотрим на файл Vector3.msg, то мы поймём, что это за вектор-три. Кстати, мы видим, что сообщение может состоять из других сообщений. Надо отметить, что для сообщений ROS это распространённая практика, когда сообщения объединяются в другие сообщения. Теперь посмотрите сами, пожалуйста, файл “/opt/ros/noetic/share/geometry_msgs/msg/Vector3.msg”. Посмотрели? Очевидно, что это тип данных, который определяет некий набор из трёх величин x, y, z формата 64-разрядное число с плавающей точкой. Очевидно, что рассматриваемое нами сообщение Twist состоит из шести 64-битных чисел с плавающей точкой, объединённых в группы по три, называемые “линейное” и “угловое”. Вероятнее всего, мы видим некоторый способ задать координаты в декартовом пространстве. Чтобы не вводить в заблуждение, скажу, что мы видим способ представления скоростей в трёхмерном пространстве с использованием линейных и угловых компонент. Twist — это сообщение для описания скоростей и, на мой взгляд, имена внутри сообщения могут сбить с толку. Ни кто нам немешает, однако, обратиться к ROS-сообществу и уточнить, для чего предназначен тот или иной тип данных.
Возвращаясь именно к структуре сообщения могу сказать, что, необязательно ходить по папкам, и смотреть файлы, чтобы разобраться со структурой. Для того чтобы предъявить нам структуру сообщения, есть команда rosmsg с ключом info или show. Посмотрим структуру сообщения Twist.msg:

rosmsg info geometry_msgs/Twist

Посмотрим на результат:
rosmsg_info_twist.png

Мы видим шесть чисел, объединённых в тройки с именами “линейное” и “угловое”, а имена переменных в тройках – привычное обозначение осей в трёх измерениях. Ознакомиться заранее с топиками пакета, с которым вы начинаете работу – полезно. Если вернёмся немного назад, как выводу данных по команде rostopic info /turtle1/cmd_vel, мы увидим ещё и “направление” дуги графа ROS или говоря другими словами, кто подписан на топик и кто его публикует. В качестве паблишера там мы видим Никого, а вот сабскрайбером указана нода /turtlesim. Теперь мы можем вполне обоснованно утверждать, что turtlesim ждёт данных через топик cmd_vel, то есть, мы можем управлять движением черепашки, подавая данные через эту тему. Сейчас черепашка подписана на тему, но никто не публикует данные, по которым она могла бы осуществлять какое-то движение. Поэтому черепашка стоит на месте. Что нужно сделать, чтобы заставить черепаху двигаться?

3.3 Нода для управления симулятром черепашки

3.3.1 Создание ноды

Надо написать ноду паблишера, которая будет рассчитывать данные о скорости черепахи и передавать их в топик управления скоростью. Это и будет нашим сегодняшним маленьким проектом. Мы должны создать новый пакет, написать ноду на Питоне, в которой организовать паблишер, который будет задавать скорости движения черепашки. Оставим включенными ROS-мастер и узел симуляции черепахи и перейдем исходники нашего рабочего пространства. Если перешли, то давайте создадим пакет, как делали это на прошлом уроке. Назовём мы такой пакет turtle_play. Хорошо, надеюсь у всех получилось. Что нужно сделать теперь? Верный ответ – это построить рабочее пространство. Уверен, каждый из вас подал следующие команды:

cd ~/catkin_ws/src/
catkin_create_pkg turtle_play
cd ..
catkin build

Надеюсь, перед тем, как построить рабочее пространство, вы не забыли перейти из каталога с исходниками на уровень выше, так как я часто забываю это делать. Хорошо перейдём в каталог с нашим новым пакетом и создадим там папку для скриптов, которую назовём также, как и на прошлом уроке – scripts. После этого папке со скриптами создадим файл управления движением control.py и откроем его для редактирования. Надеюсь, у всех была такая последовательность команд:

cd ~/catkin_ws/src/turtle_play/
mkdir scripts
cd scripts
touch control.py
gedit control.py

Как мы выяснили раньше, черепашка подписана на топик cmd_vel, а вот издателя у темы нет, значит, напишем узел издателя. Начнём, как всегда, с шебанга, который скажет, какой интерпретатор использовать для скрипта. Далее импортируем rospy и тип данных Twist из geometry_msgs, инициализируем узел с именем control_node. Теперь создаём паблишер с именем темы /turtle1/cmd_vel, типом данных Twist и размером очереди = 1. В этом месте появляется то, что вы ещё не делали. В прошлый раз мы передавали целое число, а сейчас надо передать составное сообщение. Также, как и для паблишера pub, породим объект сообщения с именем msg от класса Twist:

msg = Twist()

Теперь давайте зададим скорость по одной из координат. У класса Twist, который представляет сообщение типа Twist и от которого мы породили наш объект сообщения msg, есть атрибуты linear и angle, у которых в свою очередь есть атрибуты x y z. Ими мы можем пользоваться также, как обычными переменными, то есть, мы можем присвоить им какое-либо значение, соответствующее их типу float64. Присвоим координате x значение, равное единице. Записывать атрибуты следует от объекта через точку:

msg.linear.x = 1

Также мы можем присвоить значение и другим атрибутам, но сейчас остановимся на linear.x. Дальше вам следует написать цикл публикации сообщений с ожиданием окончания работы программы и приостановкой программы на 1 секунду на каждом шаге – это мы уже делали в предыдущем уроке. Отлично, всё сделано, сохраним и закроем текстовый редактор. Нам осталось только дать скрипту право на исполнение и запустить его с помощью rosrun. Сделайте это, пожалуйста.

3.3.2 Разбор результатов

Теперь давайте разбираться, что у нас получилось. Ну или не получилось, что интереснее. Для начала напомню, как дать право на исполнение файла владельцем:

chmod u+x control.py

Теперь напишу команду, которой следует запускать нашу ноду:

rosrun turtle_play control.py

Получилось или появилась ошибка, как на картинке ниже?
rosrun_error.png

Если получилось, значит вы выполнили эту команду в терминале, который открыли после того, как перестроили рабочее пространство. Если не получилось – откройте новый терминал и запустите ноду там, а после этого ответьте, почему не получилось запустить в старом терминале. Ответ простой, когда при запуске терминала подгрузился скрипт с настройками рабочего пространства, в рабочем пространстве ещё не было нашего пакета, поэтому терминал и надо перезагрузить, ну или дать одну команду, а вот какую, скажите сами. Отлично, идём дальше. В этом месте вы можете получить от своей ноды сообщения об ошибке в процессе выполнения. Если это так, то для начала нужно прочитать, что вам пишет интерпретатор Питона, понять, в чём может быть проблема и запустить ещё раз. В общем, вы сейчас начали заниматься отладкой своего приложения, а это одна из наиболее важных частей программирования. Но делать каждый следующий шаг отладки стоит только после прочтения и осознания сообщения об ошибке, правке кода и запуске. Не стоит без внесения правок и раз за разом запускать не изменённый код. Ведь … > “Я уже говорил тебе… что такое… безумие, а? Безумие… это… точное повторение… одного и того же действия. Раз за разом… в надежде… на изменение. Это… есть… безумие.”

Если в результате нескольких последовательных запусков и правок ничего не получилось, то стоит сличить ваш код с тем кодом скрипта, который написал я, и понять, в чём отличия. Понять, почему мой работает, а ваш – нет. Итак, мой код:

#!/usr/bin/env python3
import rospy
from geometry_msgs.msg import Twist

# Инициализация узла и публикатора
rospy.init_node('control_node')
pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size = 1)
# Инициализация сообщения и задание значения атрибуту linear.x
msg = Twist()
msg.linear.x = 1

# Циклическая публикация сообщения
while not rospy.is_shutdown():
  pub.publish(msg)
  rospy.sleep(1)

3.3.3 После успешного запуска

Итак, у нас всё получилось, паблишер запустился, черепашка подвинулась и … врезалась в стену
hit_the_wall.png

Мы видим это в симуляторе, мы можем прочитать предупреждение об этом в терминале, где запущен симулятор, мы понимаем, что черепашка страдает, так как эти сообщения идут непрекращающимся потоком. Остановим наш паблишер и увидим, что черепашка перестала слать нам предупреждения. Отлично, давайте сдвинем её назад, ведь стоять, уткнувшись лицом в стену совсем не интересно. Что надо сделать? Во-первых, это изменить направление подаваемой скорости, заменим его значение на обратное, на минус единицу:

# msg.linear.x = 1
msg.linear.x = -1

Во-вторых, если мы запустим ноду сейчас, то паблишер будет подавать команду бесконечно и загонит черепашку в другую стену. Давайте применим другой цикл, цикл for. Цикл со счётчиком – цикл, в котором некоторая переменная изменяет своё значение от заданного начального значения до конечного значения с некоторым шагом, и для каждого значения этой переменной тело цикла выполняется один раз. Используем цикл со счётчиком для диапазона длиной 5:

# while not rospy.is_shutdown():
for i in range(5):

Перед каждым изменением нашего кода вы можете видеть прежнюю версию, но помеченную символом решётки. Как я говорил в прошлый раз, это комментарий. Я оставил прежний вариант кода, просто закомментировал его часть. Многие программисты так делают, а потом получают кучу мусора в своём коде и не понимают зачем его там оставили. То есть, пользоваться таким способом отката изменений можно, но злоупотреблять им не стоит – лучше воспользоваться системами контроля версий. Хорошо, сохранили, запустили. Что видим?
turtle_back_move.png

Давайте посмотрим теперь на темы, которые публикует симулятор черепашки. Первая тема /turtle1/color_sensor. Над разгадкой того, что это такое, бьются лучшие умы робототехники. Бьются и не знают, что это цвет следа, который оставляет черепашка, когда ползает по экрану. Цвет задаётся в формате rgb. Почему про это не написано на страничке turtlesim в ROS wiki - не знаю, но это факт. Если не верите, давайте сходим и посмотрим. Задаётся цвет следа с помощью ещё одного средства связи ROS, так называемого сервиса ROS, но о них мы будем говорить позже. Нам сейчас этот топик ни к чему, забудем про него. Вторая публикуемая turtlesim тема – /turtle1/pose. Этот топик намного интереснее и полезнее. Если мы выведем содержимое этого топика, например, с помощью команды rostopic echo /turtle1/pose, мы увидим следующие данные: :
turtlesim_pose.png

Разберём, что каждое поле означает.

3.3.4 Кинематика черепашки

Очевидно, мы имеем две координаты на плоскости, некий угол поворота черепашки и угловую и линейную скорости. Давайте попробуем разобраться, что это за угол. Нарисуем двумерную систему координат и некий объект в ней.
frame1.png

У объекта есть координаты по осям X и Y и угол направления на черепашку θ, правильно? Наверное, нет. Если у нас есть координаты на плоскости, этот угол для задания положения нам не нужен. Да и вообще, зачем нам нужен угол для плоского случая, если у нас есть две координаты? Надо вспомнить, что черепашка наша не материальная точка и не круг на плоскости, у неё, как у всякой уважающей себя черепахи, есть голова, и голова эта куда-то направлена.
frame2.png

И что это за направление, как нам теперь определить это угол? Это угол между осью X системы координат и направлением головы черепахи. Для программиста такое описание возможно и понятно, а вот компьютеру, даже если он оснащён каким-нибудь генеративным предобученным трансформером, об этом пока говорить рано. Для ROS точно такое описание понятно не будет. Давайте дорисуем зелёную стрелку из левого бока черепахи и допишем у красной стрелки x, а у зелёной y.
frame3.png

Мы видим две системы координат, одна из которых, обозначенная строчными буквами, смещена и повёрнута относительно другой, обозначенной прописными буквами, что вполне похоже на то, что даёт нам топик.
frame4.png

А в каком направлении у нас повернута “маленькая” система координат? Давайте думать из трёхмерного пространства, мне так привычнее. Да и если подумать, мы работаем сейчас с мобильным роботом, типа робота-пылесоса, который перемещается по плоскости, не может перекатиться через бок или кувыркнуться (повернуться вокруг осей x и y), не может подпрыгнуть или провалиться сквозь землю, но всё равно существует в трехмерном пространстве. Хорошо, предположим, что у нашей системы координат есть ещё и ось Z и она на рисунке направлена на нас, то есть мы поселим нашу черепашку в стандартную или правую систему координат. Если так, то всё становится вполне понятно, хватаемся правой рукой за ось Z, большой палец направляем по направлению оси, а по направлению остальных пальцев смотрим, куда надо повернуть систему координат. Следующая картинка не должна вводить в заблуждение тем, что рука держится не за ту ось, картинка дана для примера.
right_hand.png

Давайте теперь назовём наши системы координат. Систему координат, которая никак не зависит ни от смещения, ни от поворота черепашки, будем называть внешней. Внутренняя система координат – это система координат, которую черепашка таскает с собой, та, которая «привязана» к самому роботу. Для черепашки — это её собственный взгляд на мир: где бы она ни находилась, её начало координат всегда в центре тела, ось X — по направлению движения, Y — вбок, Z — вверх.
Теперь ещё один момент. Скорости, на которые подписан симулятор. Мы помним этот самый cmd_vel, который состоит из двух трёхкомпонентных векторов — linear и angular. То есть теоретически мы могли задать скорость по всем шести координатам нашего многострадального трёхмерного пространства: три линейные (x, y, z) и три угловые (x, y, z). Так вот, нам давали возможность задать скорости шестью координатами, а возвращает нам симулятор только две. Что произошло? Куда пропали ещё четыре?
На самом деле, всё на месте — они просто не отображаются. И не могли бы мы заставить черепашку двигаться по всем шести координатам — два.

Давайте разберём эти “раз” и “два”, и заодно поймём, зачем нам нужна внутренняя СК.
“Логично” начать с “два”. Оказывается, через cmd_vel мы можем перемещать черепаху только по трём координатам: по x черепашка поедет в направлении головы, если зададим скорости по y, черепашка двинется боком. Движение по z не приведёт к видимому результату — из двумерного пространства не вырваться. А вот если задать скорость по z для вектора angular, то получим вращение черепашки вокруг своей вертикальной оси.
Кстати, вы заметили, как незатейливо мы воспользовались внутренней СК при задании скоростей черепашки? Именно так и работает внутренняя система координат: ось X направлена вперёд (по направлению головы), Y — вбок, а Z — вверх. Поэтому задавать скорости относительно неё очень удобно, особенно когда ты сам — эта самая черепашка.
Теперь посмотрим на “раз” — данные темы pose симулятор нам отдаёт во внешней системе координат, так мы всегда знаем, где черепашка расположена на поле. Да и в принципе, я пока плохо представляю, как можно было бы нам получать понятные человеку данные о положении черепашки во внутренней системе себя — там она всегда находится в начале СК. Вот если бы у нас было две черепахи, тогда можно было бы задать координаты одной черепашки во внутренней СК другой.
> Но это уже совсем другая история.

3.3.5 Обратная связь от робота

Для чего нам может быть полезна информация о положении черепашки? Информация о текущем положении черепашки, доступная через топик /turtle1/pose или pose какого-то другого робота, позволяет замкнуть контур управления. Например, если мы хотим переместить робота в определённую точку или пройти по заданной траектории, знание текущего положения критично для корректной работы алгоритма. Я перестал говорить “черепашка” не просто так, публикация информации о положении робота – это общая практика для узла ROS, отвечающего за управление роботом на некотором, скажем так, уровне оборудования. Фактически, мы уже попробовали переместить нашу черепашку в нужную нам область, когда отодвигали её от стены, и даже добились результата. Однако, дело в том, что мы использовали управление с открытым контуром, то есть без обратной связи, которое нас не спасло бы от нештатной ситуации. Если бы мы находились не у правой стенки, а начали бы движение близко к левой стенке, наша черепашка снова ударилась бы об стену. Управление с открытым контуром применяется редко – только для очень предсказуемых систем. В реальности может случится, например, проскальзывание колёс робота и робот по окончании подачи скорости окажется совсем не там, где мы ожидали. Давайте построим систему управления с замкнутым контуром. Для начала подпишемся на тему /turtle1/pose в нашей программе control.py. Из набора сообщений turtlesim.msg импортируйте Pose и подпишитесь на тему с форматом Pose и функцией обратного вызова с именем callback, а затем напишите саму функцию, которая просто печатает сообщение. Программу закончите спиннером, который нам нужен, чтобы программа не закончилась сразу после цикла публикации скоростей. После того, как мы дополнили код, сохраните его и попробуйте запустить с помощью rosrun. Это уже должно войти в привычку, что вы должны попробовать исправить программу самостоятельно, если в ней что-то не так, но если что-то не получается, можно почитать код:

#!/usr/bin/env python3
import rospy
from geometry_msgs.msg import Twist
from turtlesim.msg import Pose

def callback (msg):
  print(msg)

rospy.init_node('control_node')
pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size = 1)
rospy.Subscriber("/turtle1/pose/", Pose, callback)
msg = Twist()
msg.linear.x = -1

for i in range(5):
  pub.publish(msg)
  rospy.sleep(1)

rospy.spin()

Отлично, давайте обсудим, что мы увидели после запуска. Черепашка переместилась в сторону своего хвоста, в процессе движения мы видели, что изменяются координаты и линейная скорость равнялась единице. После окончания движения нода не прекратила работу, так как терминал не вышел в приглашение, а печать данных будто бы остановилась. На самом деле, данные продолжают выводиться на экран с частотой, с которой их публикует симулятор, убедиться в этом можно, если прокрутить вывод терминала вверх – мы не увидим линейную скорость, отличную от нуля. Давайте убедимся в том, что наша нода принимает данные ещё одним способом – закомментируем спиннер в тексте, сохраним и запустим. Только давайте перед сохранением ещё и поменяем знак у заданной скорости, а то черепашка ударится хвостом, а это не только больно, но и неожиданно. Запускаем и видим, что черепашка переместилась, нода завершила работу и последнее сообщение, которое она прочитала, имело ненулевую линейную скорость. Значит можно сделать вывод, что на предыдущем этапе нода получала данные и после окончания движения. Давайте ещё проверим, как симулятор отреагирует на установку угловой скорости. Как я уже говорил, ни кувыркаться ни перекатываться наша черепашка не умеет, а значит единственная координата, вокруг которой её стоит вращать – z и угловую скорость будем задавать, изменяя координату msg.angular.z. Скорость задаём в радианах. Давайте зададим, запустим и посмотрим, что получилось. Получился неожиданный результат:
after_rot.png

Крутили в положительном направлении, а угол получили отрицательный. Скажите, пожалуйста, почему? Ответ – угол theta у нас получился больше, чем Пи радиан, и число, которое мы видим на экране и 4 радиана, на которые повернулась черепашка – это фактически одно и то же. Мы бы достигли того же угла, в котором находимся, если бы двигались против часовой стрелки, но угол этот был бы записан отрицательно. А получить при компьютерных вычислениях число больше Пи нам не дадут обратные тригонометрические функции. Значит, мы всегда будем получать результат в диапазоне от до .
plus_minus_4rad.png

Почему 4 рад, а не 5 мы разбираться не будем, скажу только, что на первое отправленное сообщение симулятор не реагирует движением, а так как наш цикл был от 0 до 4, мы и повернулись на 4 рад. Если бы поставили цикл с range(9), повернулись бы на 8 рад. Если хотите, можете разобраться в этом самостоятельно. И ещё одно задание на самостоятельную проработку – разобраться, что делает такой код нашего узла:

#!/usr/bin/env python3

import rospy
from geometry_msgs.msg import Twist
from turtlesim.msg import Pose

ret_x = 100
ret_y = 100
def callback (msg):
  ret_x = msg.x
  ret_y = msg.y
  print ("X = ", ret_x, "Y = ", ret_y)

rospy.init_node('control_node')
pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size = 2)
rospy.Subscriber("/turtle1/pose/", Pose, callback)
msg = Twist()

while not rospy.is_shutdown():
  if ret_x <= 0:
    msg.linear.x = 0
  else:
    msg.linear.x = -0.2
  if ret_y <= 0:
    msg.linear.y = 0
  else:
    msg.linear.y = -0.2
  pub.publish(msg)
  rospy.sleep(0.1)

3.4 Введение в tf на примере симулятора черепашки

Сейчас мы познакомимся с одним очень полезным инструментом ROS – tf (The Transform Library). Библиотека tf была разработана для стандартизации работы с системами координат (СК) и их преобразовавниями в ROS и сейчас является одной из основ работы ROS. Большинство проектов роботов в ROS просто не запустятся, если в нём не будет установлен пакет tf. Библиотека tf ROS самостоятельно выполняет все преобразования СК и делает доступными эти СК всем пакетам, работающим под управлением одного ROS-мастера.

Всё это хорошо, но какая нам от этого польза?
Представьте, что у нас есть мобильный робот. Он ездит по комнате, видит объекты камерой, измеряет расстояния лазерным сканером и при этом оснащён манипулятором с захватным устройством. Все эти компоненты — камера, лазерный сканер, колёса, манипулятор — находятся в разных местах относительно друг друга. И чтобы робот мог работать корректно, ему нужно понимать, где что находится. Если мы не хотим считать все эти преобразования самостоятельно, а мы не хотим, здесь-то и появляется спасительный инструмент tf, который разберётся с большинством СК самостоятельно.

А для чего вообще нужны системы координат?
Об этом мы говорили чуть раньше и на очень простом примере, где у нас был единственный объект на плоскости – наша черепашка. А теперь представьте, тот робот с камерой, колёсами, манипулятором и прочим, и что этот робот обнаружил объект камерой в некой точке с координатами, допустим, (x=2, y=3, z=1). Что это за координаты? Вероятнее всего, это координаты относительно самой камеры. А если нам нужно, чтобы манипулятор взял этот объект, то манипулятору нужны координаты не относительно камеры, а относительно какого-то своего элемента, например, основания. Вот тут и возникает задача перевода координат из одной системы в другую, которую tf делает автоматически.

Как же tf работает?
TF строит дерево систем координат. Например для нашего робота с камерой и манипулятором есть:

  • Мировая система координат — некоторая “основа”, относительно которой всё происходит, базовая СК, относительно которой определяются все остальные СК.
  • Система координат робота — это его “корпус”.
  • Система координат камеры, которая может крепиться к корпусу под определённым углом и на определённом расстоянии.
  • Система координат манипулятора, который тоже крепится к корпусу в каком-то месте. Так вот библиотека tf по данным о роботе, которые она получает от разработчика в специальном формате, знает, как все эти узлы робота связаны между собой: где камера относительно корпуса, где манипулятор относительно корпуса, и так далее. То есть, tf построит дерево с корнем в мировой системе координат, содержащее все СК робота. Когда вы попросите бибилиотеку tf перевести координаты объекта из системы камеры в систему манипулятора, бибилиотека tf просто пройдёт по этому дереву и выполнит необходимые математические преобразования.

У сообщений tf есть временная составляющая, зачем она нужна?
Представьте, что данные от камеры приходят с задержкой. Например, камера увидела объект полсекунды назад, а за это время робот уже немного сдвинулся. Если мы просто возьмём старые координаты и попробуем их использовать, они будут некорректными. TF решает эту проблему: он умеет хранить историю преобразований и может выполнить перевод координат для любого момента времени. То есть вы можете сказать: “Мне нужны координаты объекта в тот момент, когда камера его увидела”, и бибилиотека tf всё правильно посчитает.

Визуализация.
Ещё одна отличная вещь в tf — это возможность визуализировать системы координат. Вы запускаете RViz, добавляете TF-плагин, и видите, как робот, камера, манипулятор и другие компоненты связаны между собой. Это очень помогает при отладке: если что-то работает неправильно, вы сразу видите, где проблема.

В общем, понятно, что бибилиотека tf — это мощный инструмент, который позволяет роботу ориентироваться в пространстве. Она решает задачи преобразования координат, синхронизации данных и работы с временными метками. Без неё робот не понимает, где что находится. Более того, он не понимает, где находятся его руки, ноги и голова.

Кстати, для понимания того, сколько систем координат нам было бы нужно корректно запрограммировать и обрабатывать в процессе моделирования, можно взглянуть на tf робота PR2:
tf-PR2.png
Вот это всё многообразие красно-зелёно-синих рогаток за нас считает tf.
Наша черепашка это тоже робот и как мы выяснили чуть ранее, СК, которые я теперь иногда буду называть tf, у неё тоже присутствуют. Давайте попробуем понять tf на примере нашей черепашки. Перед запуском примера я должен сказать, что tf в настоящий момент является устаревшей библиотекой, на смену которой пришла доработанная версия tf – tf2. Никаких возможностей первой версии версия вторая не потеряла. В ROS для совместимости присутствует возможность работы с tf, но реализована она теперь через tf2. Давайте пока поработаем с первым поколением, так как понимание концепции оно нам даст, а большего пока и не надо.

3.4.1 Демонстрационная программа для обучения tf

Для того, чтобы разобраться, как работают tf, запустим демонстрацию из ROS-пакета turtle_tf, в котором мы увидим уже знакомый нам turtlesim, но уже с двумя черепашками. Для запуска будем использовать команду roslaunch, с которой мы уже знакомились в разделе 2.7 урока 2. Откроем новый терминал и напишем в нём команду:

roslaunch turtle_tf turtle_tf_demo.launch

После запуска мы увидим экран с двумя черепашками, одна из которых пока стоит на месте, а вторая стремиться к ней:
turtle-sim-tf-started.png
Теперь давайте подвигаем стоявшую черепашку, для чего в терминале, откуда запустили демонстрацию командой roslaunch, будем клавишами со стрелками подавать команды движения:
turtle-tf-chasing.png
Что мы теперь видим? Одна черепашка, которой мы управляем, перемещается по экрану, а следом за ней непрерывно бежит вторая.
NNDrozdov.png

3.4.2 Программирование обмена tf между нодами

Что же у нас происходит “под капотом”, так сказать? Давайте для начала посмотрим граф узлов ROS, мы это уже делали в rqt из меню Plugins->Introspection->Node Graph. Также это можно сделать командой rqt_graph из терминала. Команда rqt_graph запустит плагин Node Graph в качестве отдельного приложения, и покажет нам следующее:
turtle_tf_demo_graph.png
Здесь мы можем видеть ноду /teleop, которая обрабатывает нажатие клавиш и публикует управляющие скорости для преследуемой черепашки (turtle1) в топик /turtle1/cmd_vel, на который подписана основная нода симулятора /sim. Также /sim подписан на топик с управляющими скоростями для преследующей черепашки (turtle2), который публикует нода /turtle_pointer. В свою очередь, /turtle_pointer получает некие /tf от узлов (как мы помним, нода у нас – это жаргонное название узла ROS) /turtle1_tf_broadcaster и /turtle2_tf_broadcaster, которые подписаны на паблишеры /turtle1/pose и /turtle2/pose. Всё, наконец круг замкнулся, можно начинать разбираться, как это работает. ROS-пакет с помощью библиотеки tf создал две системы координат: СК черепашки 1 и СК черепашки 2, данные о которых транслируются через так называемый вещатель tf. Эти данные обрабатываются слушателем tf, который вычисляет разность систем координат и осуществляет их широковещательную трансляцию. Давайте посмотрим на код, запрятанный в недрах ros-пакетов, который реализует работу вещателя tf – turtle_tf_broadcaster.py, от которого и порождены узлы /turtle1_tf_broadcaster и /turtle2_tf_broadcaster, которые запускаются launch-файлом ROS-пакета с переменными /turtle1 и /turtle2, чтобы их можно было идентифицировать при работе. Для лучшего понимания, что происходит при выполнении этого скрипта, я этот код прокомментировал:

#!/usr/bin/env python
# Импорт необходимых модулей:
# ROS для Питона, модуль библиотеки tf и модуль, описывающий сообщения из пакета симулятора черепашки
import rospy
import tf
import turtlesim.msg

def handle_turtle_pose(msg, turtlename):
# Тело функции обратного вызова для сабскрайбера,
# который подпишется на топик с данными о положении, публикуемый симулятором черепашки

# Создадим объект br на базе класса TransformBroadcaster модуля tf, который и будет осуществлять вещание
    br = tf.TransformBroadcaster()
# Транслируем информацию о преобразовании (точно, ведь tf это transform) одной системы координат в другую
    br.sendTransform((msg.x, msg.y, 0), # Смещение tf
                      tf.transformations.quaternion_from_euler(0, 0, msg.theta), # Поворот tf
                      rospy.Time.now(), # Время преобразования
                      turtlename, # СК преследуемой черпашки
                      "world") # Мировая СК

# Проверка на то, используется ли код в качестве скрипта или подключен как модуль
if __name__ == '__main__':
# Инициализация узла turtle_tf_broadcaster
    rospy.init_node('turtle_tf_broadcaster')
# Получение имени черепашки
    turtlename = rospy.get_param('~turtle')
# Уже знакомая нам функция подписки на топик
    rospy.Subscriber('/%s/pose' % turtlename, # Имя топика
                     turtlesim.msg.Pose, # Тип данных сообщения
                     handle_turtle_pose, # Функция обратного вызова
                     turtlename) # Дополнительный параметр
# Зациклим ROS-приложение с помощью спиннера
    rospy.spin()

На некоторых моментах текста скрипта я хочу остановиться подробнее. Начнём с не относящейся к ROS, но достаточно стандартной для Питона вещи, которую мы ещё не использовали – обработку переменной __name__. Когда скрипт запускается на выполнение, Питон присвоит переменной __name__ значение __main__. В противном случае интерпретатор предположит, что мы подключаем код как модуль и присвоит переменной __name__ имя модуля (имя файла с кодом, но без расширения .py). В строке if __name__ == '__main__': производится проверка на то, используется ли код в качестве скрипта или подключен как модуль. Если это скрипт, о чём и говорит значение '__main__' переменной __name__, исполняется основная часть. Фактически, это аналог функции main() в языках C и C++. Чтобы понять, что это хороший тон программирования на Питоне, давайте вспомним код нашего скрипта, реализующего работу подписчика в предыдущем занятии. Могу сказать, что нам повезло с длиной кода. Даже среди небольшого количества строк место, в котором начинается основная часть скрипта, сразу увидеть не просто. То есть, эта конструкция не только позволяет нам задать поведение программы в ситуации “скрипт или подключаемый модуль”, но и улучшает читаемость программы, что очень важно для последующего сопровождения программного продукта. Давайте разберём работу этой “функции main()”. Инициализацию ноды вы, несомненно, узнали. В следующей строке получаем имя черепашки, которое было задано как параметр запуска узла в launch-файле. Значение этого параметра через сервер параметров ROS, о котором мы поговорим в следующем занятии, становится доступной для скрипта через функцию rospy.get_param('~turtle'). Так как программа эта не сложная, имя черепашки здесь является и именем паблишера и именем системы координат черепашки, однако, для более сложных программ стоит давать имена, учитывая контекст.

Теперь разберём подробнее параметры, используемые при инициализации подписчика:

    rospy.Subscriber('/%s/pose' % turtlename, # Имя топика
                     turtlesim.msg.Pose, # Тип данных сообщения
                     handle_turtle_pose, # Функция обратного вызова
                     turtlename) # Дополнительный параметр

Здесь мы уже знакомым нам способом подписываемся на топик pose паблишера с именем черепашки, которое хранится в нашем случае в переменной turtlename. Собранная из этих двух частей строка будет первым параметром. Сообщения этого топика имеют тип turtlesim.msg.Pos, что мы и задаём вторым параметром. Реакцией на появление данных в топике будет вызов callback-функции или функции обратного вызова (ФОВ), имя которой handle_turtle_pose задаётся третьим параметром. Четвёртым параметром функции инициализации подписчика, который мы не использовали в нашем первом опыте работы с подписчиком, будет дополнительный аргумент, передаваемый ФОВ. Как можно заметить, аргументов в данном случае у ФОВ два: msg с данными сообщения и некий turtlename, который в теле функции мы использовали как имя СК черепашки при создании сообщения вещателя:

    br.sendTransform((msg.x, msg.y, 0), # Смещение tf
                      tf.transformations.quaternion_from_euler(0, 0, msg.theta), # Поворот tf
                      rospy.Time.now(), # Время преобразования
                      turtlename, # СК преследуемой черпашки
                      "world") # Мировая СК

Давайте разберём параметры функции вещателя sendTransform. Смещение tf – здесь смещение преследуемой черепашки по осям x и y в мировой СК. Так как прыгать наша черепашка не умеет, координате z присвоено значение 0. Поворот tf здесь задаётся кватернионом, полученным из углов Эйлера позы черепашки с помощью функции quaternion_from_euler. Так как черепашка не научилась перекатываться и кувыркаться, повороты вокруг осей x и y обнулены. Время преобразования – текущее время ROS. Последними двумя параметрами функции трансляции tf задаются имя дочерней и родительской СК. Для нашего случая это СК преследуемой черепашки и мировая СК. Общий алгоритм работы ноды достаточно прост. Подписчик ожидает сообщение сообщение с позицией преследуемой черепашки, вызывает ФОВ, транслирующую данные о вычисленной СК и переходит в ожидание следующего сообщения о положении черепашки. А где же код, который породит черепашку-преследователя и будет ей управлять? Ответ на этот вопрос придётся поискать в другом файле – файле, от которого порождён узел turtle_pointer со слушателем tf turtle_tf_listener.py:

#!/usr/bin/env python
# Импорт необходимых модулей:
# rospy - ROS
# math - популярные математические функции и константы
# tf - преобразоания СК
# geometry_msgs.msg - геометрические сообщения
# turtlesim.srv - доступ к сервисам симулятора черепашк
import rospy

import math
import tf
import geometry_msgs.msg
import turtlesim.srv

# Главная "функция"
if __name__ == '__main__':
   rospy.init_node('tf_turtle')
# Создадим объект listener класса TransformListener модуля tf
# Это слушатель нашего вещания
   listener = tf.TransformListener()
# Три следующие строчки - порождение в симуляторе turtlesim черепашки-преследователя с именем turtle2
   rospy.wait_for_service('spawn')
   spawner = rospy.ServiceProxy('spawn', turtlesim.srv.Spawn)
   spawner(4, 2, 0, 'turtle2')
# Иницализация паблишера для управления скоростью черепашки-преследователя
   turtle_vel = rospy.Publisher('turtle2/cmd_vel', geometry_msgs.msg.Twist, queue_size=1)
# Задание частоты работы основного цикла программы 10 Гц
   rate = rospy.Rate(10.0)
# Пока работу ноды не прервали
   while not rospy.is_shutdown():
# Получить преобразование из СК turtle1 в СК turtle2 в настоящий момент времени
# В результате будет получена пара троек значений - смещение и поворот
       try:
           (trans, rot) = listener.lookupTransform('/turtle2', '/turtle1', rospy.Time())
       except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
           continue
# Рассчитать угловую angular и линейную linear скорости для преследователя
       angular = 4 * math.atan2(trans[1], trans[0])
       linear = 0.5 * math.sqrt(trans[0] ** 2 + trans[1] ** 2)
# Подача скоростей черепашке-преследователю
       msg = geometry_msgs.msg.Twist()
       msg.linear.x = linear
       msg.angular.z = angular
       turtle_vel.publish(msg)
# Приостановить выполнение цикла
       rate.sleep()

Как мы теперь понимаем, эта нода фактически состоит только из основной функции. Давайте, подглядывая в комментарии в коде, разберём, что в ней написано. С подключаемыми модулями всё должно уже быть понятно – мы подключаем наборы функций, которые понадобятся в процессе работы скрипта. Далее мы видим исполняемую часть скрипта, отвечающую за то, чтобы породить черепашку-преследователя, получить tf черепашки, которую надо догонять и рассчитать скорость преследователя в зависимости от положения черепашек. Для начала здесь происходит инициализция ноды и слушателя tf, который и будет получать данные от вещателя. Следом за этим идёт код, порождающий черепашку-преследователя. Порождается она с помощью ещё одного средства взаимодействия узлов ROS, об одном из которых “издатель-подписчик” мы уже узнали и смогли его применить на практике. Так вот, ещё один способ взаимодействия – это сервисы ROS. Способ этот широко используется в ROS, так даже в этом уроке мы о нём вспоминаем во второй раз. Его мы позже также разберём теоретически и используем практически в процессе наших занятий. Сейчас же достаточно понимать, что мы отправили некий запрос симулятору черепашки, который заставит его породить ещё одну черепашку с заданными координатами x=4, y=2, theta=0 и именем turtle2. Паблишер в следующей строке нам хорошо знаком, мы использовали его совсем недавно. С помощью этого паблишера мы подаём скорости движения черепашке с именем turtle2 – это черпашка-преследователь. Теперь нам надо установить частоту работы основного цикла программыю. Если раньше мы явно задавали тайм-аут в работе цикла на 100 миллисекунд вызовом функции rospy.sleep(0.1) в конце цикла, то здесь мы используем связку из rate = rospy.Rate(10.0) перед циклом и rate.sleep() в конце цикла. Функция rospy.Rate задаст частоту выполнения цикла, а rate.sleep() приостановит выполнение на соответствующий промежуток времени. Здесь возникает закономерный вопрос, зачем нам использовать две команды там, где прекрасно обходились одной? Ответ на него лежит практически на поверхности, Rate задаёт частоту работы цикла, то есть стремится сделать так, чтобы каждая итерация занимала в нашем случае 0,1 секунды. Если всё ещё не понятно, давайте предположим, что все операторы внутри цикла, кроме sleep(0.1), выполняются за 50 миллисекунд, а потом мы ещё притормаживаем выполнение на 100 миллисекунд. Как результат, одна итерация будет выполняться у нас 0,15 секунды, а не 0,1. А вот команда Rate(10) заставляет ROS стараться выполнять каждую итерацию за время максимально близкое к 0,1 секунды, то есть, подгонять время тайм-аута под заданную нами частоту. Дальше наконец идёт знакомый нам цикл паблишера, выполняющийся до тех пор, пока не будет подана команда останова. Тело цикла начинается с преобразования одной tf в другую командой listener.lookupTransform, которая заботливо окружена так называемым блоком контроля исключений:

        try:
            (trans, rot) = listener.lookupTransform('/turtle2', '/turtle1', rospy.Time())
        except (tf.LookupException, tf.ConnectivityException, tf.ExtrapolationException):
            continue

Если в блоке между try и exception произойдёт ошибка времени выполнения программы (не синтаксическая ашипка, за проверку синтаксиса отвечает транслятор языка, который просто не даст выполнить команду, написанную не по правилом Python), так вот, если произойдёт ошибка времени выполнения, то её тип будет передан обработчику исключений. Если тип соответствует тому, который мы задали после команды except, программа выполнит составной оператор под except, в нашем случае это оператор перехода к следующей итерации цикла continue. Если же обработчику попадётся незнакомый тип ошибки, увы, программа остановится с сообщением об ошибке времени выполнения. Функция listener.lookupTransform принимает на вход в качестве параметров:

  1. в какую tf преобразуем (назовём её TF2),
  2. из какой tf преобразуем ( назовём её TF1),
  3. какой момент времени берём для преобразования. С первыми двумя параметрами всё понятно, а вот третий может вызвать удивление, ну или восторг. Мы что, на самом деле можем получить tf из прошлого? Да, из прошлого можем, ну а будущее нам всё ещё недоступно из-за принципа причинности. Более того, мы можем брать tf из разного момента времени для каждой tf из пары, для этого существует функция listener.lookupTransform с четырьмя параметрами на входе. В качестве результата функция listener.lookupTransform отдаёт нам пару наборов чисел: trans – три числа (x, y, z) для смещения из TF1 в TF2, rot – кватернион (x, y, z), необходимый для поворота из TF1 в TF2. Дальше в цикле по рассчитываются угловая и линейная скорость по линейному рассогласованию, угол theta нам здесь не помощник, ведь мы хотим, чтобы преследователь не двигался параллельно с преследуемым, а бежал в его сторону, ну или хотя бы лежал в его направлении, если устал. Следующим блоком команд, который нам предельно понятен, так как мы его только что делали, будет подготовка и публикация скоростей через топик 'turtle2/cmd_vel'. Завершается всё это rate.sleep(), который, как мы говорили, за нас посчитает, не сколько приостановить выполнение цикла паблишера. На этом же завершается и наш разбор того, как устроены простейшие вещатель и слушатель tf в ROS. Забегая наперёд скажу, что очень часто вы будете использовать tf даже не подозревая об этом. Например, когда мы будем создавать файл со структурой робота с помощью специального формата описания роботов, ROS без нашей помощи породит tf для каждого сочленения.

3.4.3 Утилиты для работы с tf

Давайте вспомним граф узлов демонстрационной программы tf из предыдущего подраздела урока. Среди множества рёбер мы можем видеть два ребра графа, обозначенные /tf, которые соединяют вершины графа /turtle1_tf_broadcaster и /turtle2_tf_broadcaster с вершиной /turtle_pointer. Рёбрами графа называются связи между вершинами графа. Вершинами графа в ROS являются узлы, рёбрами – коммуникационные связи между ними. Итак, у нас сейчас в ROS присутствуют tf, давайте посмотрим, как мы их можем обследовать.

Утилита TF Tree

Для начала давайте запустим визуализацию дерева tf. Сделать мы это можем как с помощью вызова плагина TF tree программы rqt через пункт меню Plugins->Visualization->TF Tree. Также мы можем запустить только этот плагин непосредственно: rosrun rqt_tf_tree rqt_tf_tree. Как бы мы его не запускали, в результате мы получим подобное дерево tf:
TF-Tree.png
На этой картинке мы видим, что у нас есть две tf – turtle1 и turtle2, которые “живут” в общей “мировой” системе координат. Также мы видим, кто является вещателем этих tf и с какой частотой идёт вещание. К сожалению “больше не знаем о них ничего”.

Утилита TF Echo

Ну так давайте узнаем. Давайте посмотрим на tf turtle1 с помощью консольной утилиты tf_echo:

rosrun tf tf_echo world turtle1

Утилите tf_echo нам обязательно надо будет передать два аргумента. Первым аргументом будет tf, относительно которой мы будем получать информацию – в данном случае “мировая”, а вторым аргументом tf, для которой мы эту информацию будем получать – в данном случае turtle1. Третьим аргументом можно указать частоту обновления информации, но это не обязательно. В терминале мы увидим нечто подобное:
tf_echo.png
Если мы перемещаем черепашку, информация для смещения и поворота изменяется. Попробуйте погонять черепашку по экрану и посмотреть, как изменяются координаты. Также можно попробовать выводить координаты turtle2 в системе координат (не забываем, что tf – это инструментарий для работы с СК) turtle1. Проделайте это сами, пожалуйста.

Утилита TF Monitor

Ещё одна консольная утилита для работы с tf – TF Monitor. Если запустить её с параметрами двух СК, turtle1 и turtle2, например, так rosrun tf tf_monitor turtle1 turtle2, то она выдаст информацию о цепочке преобразований tf и работе вещателей:
tf-monitor.png

Работа с tf в RViz

Давайте запустим RViz командой rviz и проделаем такую операцию, на панели Displays в разделе Global Options->Fixed Frame заменим map на world:
rviz-map2world.png
После этого в самом низу панели Displays нажмём кнопку Add:
rviz-displays-add.png
и там, в длинном списке, который мне пришлось обрезать, выберем By display type->rviz->TF:
By-dis-type-TF.png
Мы назначили в качестве базовой системы координат для RViz СК world симулятора turtlesim, а затем попросили предъявить нам на экране RViz существующие на данный в ROS системы координат.
На основном экране мы увидим нечто похожее:
rviz-turtles-tf.png
Если turtle1 и turtle2 находятся далеко от world, попробуйте двинуть черепашек к началу мировой СК в левый нижний угол.

На этом с tf мы на сегодня закончим, но уже никуда от них не денемся. Они будут сопровождать нас всё время работы с ROS.

3.5 Заключение (старое)

Давайте вспомним, чем мы занимались сегодня. Мы познакомились с симулятором простейшего мобильного робота turtlesim на уровне понимания взаимодействия с ним средствами ROS, узнали, как получить информацию о формате сообщения, создали свою ноду, которая одновременно была и издателем и подписчиком для симулятора черепашки, разобрались с тем, как черепашка движется, и разобрались, для чего нужна обратная связь от робота. Записанное коротко впечатляет не очень, но на самом деле мы фактически написали программу для управления нашим первым мобильным роботом. Также мы получили некоторое представление о средствах ROS для работы с системами координат – библиотеке tf, принципах работы вещателя и слушателя tf и утилитах, полезных при разработке и отладке приложений касающихся tf.

3.5 Заключение (новое)

На этом уроке мы совершили важный шаг в освоении ROS: познакомились с симулятором мобильного робота turtlesim, научились взаимодействовать с ним через систему топиков, а также создали собственную ноду, способную как управлять движением черепашки, так и получать обратную связь о её положении.
Мы изучили структуру сообщений типа geometry_msgs/Twist, научились подписываться на данные о состоянии робота и использовать их для замкнутого управления. Важным этапом стало понимание разницы между внутренней и внешней системами координат, что особенно критично при работе с реальными роботами.
Кроме того, мы начали знакомство с библиотекой tf — мощным инструментом для работы с преобразованиями систем координат. Мы рассмотрели, как tf строит дерево координат, как используются вещатели (TransformBroadcaster) и слушатели (TransformListener), а также познакомились с полезными утилитами, такими как rqt_tf_tree, tf_echo, tf_monitor и визуализация в RViz.
Этот урок дал не только практические навыки программирования на Python в ROS-среде, но и первое представление о том, как роботы воспринимают пространство и взаимодействуют с ним. Вы уже умеете:
- Создавать и запускать ROS-пакеты; - Писать ноды, работающие с топиками; - Управлять симуляцией и обрабатывать обратную связь; - Работать с системами координат и tf; - Использовать основные инструменты диагностики и отладки.
Это серьёзная база для дальнейшего изучения ROS.