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


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

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

   Регистрация

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

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

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

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

   Состав

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Видео

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

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

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

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

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

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

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

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

Введение в ROS

   Уроки

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

Урок 2

ROS – это Robot Operating System, фреймворк, который устанавливается на базовую операционную систему, такую, как Windows или Linux. Мы знакомимся с последней версией ROS 1, которая устанавливается на Ubuntu 20.04. Для работы с ROS нам придётся использовать эмулятор терминала Linux с командной оболочкой bash. ROS предполагает модульный подход к созданию программного обеспечения, то есть, программный проект состоит из нескольких небольших модулей – узлов, отвечающих за конкретную небольшую программную задачу. Такие узлы связаны друг с другом с помощью внутренних протоколов ROS.

2.2 Издатели и подписчики

Давайте разберёмся с модульным построением программного обеспечения ROS на примере двух модулей и протокола передачи сообщений. Итак, давайте опять возьмём пример из жизни людей. У нас есть некоторый создатель контента, который публикует его, например, в Telegram-канале. И есть читатели этого Telegram-канала, которые на него подписываются. Обмен между публикатором и подписчиками происходит с помощью некоторого сообщения, которое может включать в себя текст, изображения или видео. Аналогичная модель применяется при передаче сообщений между узлами ROS. У нас тоже есть “Telegram”, то есть, сеть ROS, как среда для передачи сообщений. У нас есть некий Публикатор Publisher, который мы или кто-то другой создали. Этот публикатор организует Тему Toipic для отправки, такой “TG-канал”. В этот канал публикатор отправляет сообщения установленного формата. Также у нас есть Подписчики Subscribers, которые эти сообщения читают. Подписчиков у публикатора может быть от 0 и до пределов возможности компьютера.
PublSubscr.png

Из рисунка видно, что Публикатор не посылает Подписчику информацию непосредственно, да Подписчиков и в принципе может не быть, Публикатор от этого работать не перестанет. В этом уроке мы постараемся создать собственные Публикатор, Тему и подписать на эту Тему Подписчика. Для того чтобы нам это сделать, будем использовать язык программирования Python. Также мы создадим свой первый проект ROS и запустим его.

2.3 Python третьей версии

Как установить Python 3 в Ubuntu мы можем прочитать по ссылке, где всё рассказано достаточно доходчиво. Там расписано подробно, как с помощью уже знакомого нам установщика apt из терминала установить Python 3. Но я надеюсь, что Python уже установлен с Ubuntu. Проверим командой

python --version
pythonversionisabsent.png

Просчитался, но где?!

Давайте применим магию TAB:
pythonTAB.png

Тут у нас целый зоопарк питонов, и второй и третий, а вот просто python отсутствует. Кстати, зачем нам версии 2 и 3, если можно было бы использовать более новую? Дело в том, что Python 2 и Python 3 имеют значительные различия в синтаксисе и стандартных библиотеках и Python 3 не является обратно совместимым для Python 2. То есть, программа на Python 2 часто может быть некорректна для интерпретатора Python 3. Кстати, официальная поддержка Python 2 закончилась в 2020 году. Теперь давайте посмотри версии наших питонов:

python3 --version
python2 --version
diff_pythons.png

“Они живы, но парализованы”.

Цитата из моего детства неточна, они оба вполне работоспособны, вот только никто из них не является Python по умолчанию. Есть у некоторых дистрибутивов Linux такой механизм, как Alternatives, с помощью которого мы можем выбрать в качестве некоторого программного средства по умолчанию одну из альтернатив. Например, вам не нужно знать, какой браузер установлен в системе, достаточно в терминале написать x-www-browser и браузер по умолчанию будет запущен. Управляет альтернативами команда update-alternatives. Если с её помощью мы посмотрим список альтернатив для python, то мы увидим, что их нет.
alterlist_pythin.png

В принципе, не думаю, что было бы очень страшно, если бы дальше вместо команды python мы бы использовали python3, но всё же давайте настроим альтернативы. Для начала посмотрим, где у нас гнездятся наши питоны, подадим команду

whereis python
whereis_python.png

И настроим альтернативы командами, не забывая sudo:

sudo update-alternatives --install /usr/bin/python python /usr/bin/python2.7 1
sudo update-alternatives --install /usr/bin/python python /usr/bin/python3.8 2
update-alternatives --list python
python_altern.png

Альтернативы заданы, посмотрим на версию Python:
python_version.png

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

sudo update-alternatives --config python
alternatives_config.png

Если вы это повторили, не забудьте вернуть третью версию по умолчанию.

2.4 Рабочее пространство catkin

Сейчас нам надо будет создать рабочее пространство для наших проектов. В рабочем пространстве будут исходные коды программных проектов, которые мы будем создавать, а так же промежуточные и финальные результаты создания нашего ПО. Рабочее пространство конечно же организованно таким образом, чтобы мы могли достаточно легко работать с несколькими нашими проектами, не путаясь, “кто где и кто чей”, а интерпретаторы или компиляторы языка, которые мы используем, могли бы при необходимости использовать промежуточные результаты. Хорошо, давайте создадим папку для рабочего пространства командой mkdir catkin_ws, то есть, catkin workspace, в нашем домашнем каталоге. Здесь надо отметить, что никто не обязывает вас называть каталог для рабочего пространства catkin именно catkin_ws. Можете назвать его как угодно, и обязательно назовёте, но потом. Сейчас будем пользоваться таким. Теперь перейдём в созданный каталог cd catkin_ws и дадим ROS указание, что именно этот каталог мы будем использовать для проектов ROS или говоря иначе, инициализируем рабочее пространство. Сделаем это с помощью команды catkin init.
catkin_init_warn.png

Нас предупреждают, что в рабочей области ещё нет каталога с исходниками, который должен иметь имя src. Это может быть, например, потому что мы перепутали каталоги. Но мы же не перепутали. Просто создадим каталог src mkdir src и снова инициализируем catkin init.
catkin_init_well.png

Теперь всё прошло успешно. Если мы посмотрим содержимое каталога catkin_ws с помощью команды ls --all, которая покажет нам все файлы, в том числе и скрытые, мы сможем увидеть скрытую папку .catkin_tools. Теперь, когда у нас есть инициализированное рабочее пространство, нам надо его “построить”. Для тех, кто хотя бы немного знаком с программированием, в этом термине нет ничего странного, для тех кто не знаком, приведу некоторую аналогию. Допустим, вы собрались строить дом. У вас есть некоторый набор материалов, из которых вы планируете строить – исходные коды, есть инструменты catkin, с помощью которых мы будем строить, ну и территория, где будем строить – наше рабочее пространство. Один из инструментов так и называется catkin build, его мы и применим для того, чтобы строительство началось.
catkin_built.png

В результате мы видим сообщение, что в папке исходников ничего не было, но один пакет построен.
> Ааа..? Так надо!

Посмотрим наш каталог рабочего пространства и увидим, что появились забор, котлован и матерящийся прораб несколько подкаталогов.
catkin_ws_dir.png

Поговорим коротко о подкаталогах. Мы видим каталог build (строить), в который будут помещаться машинные коды создаваемых нами пакетов, то есть, программы после компиляции. Также мы видим каталог devel (разрабатывать), в котором хранятся настройки нашего рабочего пространства, автоматически создаваемые исходные коды, библиотеки и прочее, необходимые для разработки. И наконец, каталог logs (журналы), каталог, где при необходимости можно посмотреть информацию о происходящем в нашем рабочем пространстве при разработке и выполнении программных пакетов. Здесь необходимо запомнить, что нам создавать в них ничего не нужно, более того, делать этого категорически не стоит и не стоит надеяться, что если вы видите в папке devel, например, какой-то исходник, созданный автоматически, вы его там найдёте и завтра.

2.5 Наш первый пакет ROS

2.5.1 Паблишер

Приступим, наконец, к разработке приложения ROS. Для этого нам надо перейти в каталог src нашего рабочего пространства с помощью cd src, а затем создать пакет с помощью команды catkin_create_pkg. У пакета должно быть имя и я предлагаю его назвать publ_n_subs, так как создавать мы будем пакет с узлами публикатора и подписчика, которые я иногда буду называть паблишер и сабскрайбер. Выполним команду catkin_create_pkg publ_n_subs:
create_pkg.png

Перейдем в каталог publ_n_subs и обнаружим там файлы CMakeLists.txt и package.xml. Наполнение этих файлов нам пока не важно, важно, что они есть. Теперь создадим исходные коды нашего пакета. Их мы поместим в отдельный подкаталог, создать который вы уже можете самостоятельно. Создадим каталог scripts (сценариями или скриптами ещё называют исходные коды, которые можно запускать без компиляции в машинный код) и перейдём в него. В принципе, мы могли бы писать наши скрипты прямо в папке publ_n_subs, но создать отдельный каталог будет хорошим тоном структурирования исходников нашего пакета. Далее мы создадим файлы со скриптами на языке Python 3, про который мы говорили ранее. Можно было бы воспользоваться языком C++, исходники которого необходимо компилировать в машинный код, но для простоты и скорости разработки пока будем использовать Python.
Чтобы создать файл, воспользуемся командой touch, которая обладает достаточно большим функционалом, но сейчас нам полезно только то, что с её помощью можно создать пустой файл. Итак,

touch publisher.py

создаст файл с именем publisher, ведь нам надо создать паблишер, и расширением py (не эр-у, а пи-уай), которое говорит, что этот файл написан на языке Питон. Так как нам надо, чтобы у Публикатора был Подписчик, создадим файл для него:

touch subscriber.py

Права доступа к файлам и каталогам

Теперь надо сделать одну не совсем очевидную для пользователя Windows вещь, дать право на исполнение наших скриптов. В отличие от Windows, Linux не позволит запустить ни одни файл сценария, если вы явно не указали, что это надо сделать. Мы должны либо запустить скрипт через интерпретатор вручную, например: python3 script.py, либо дать файлу права на исполнение.
Права доступа к файлам и каталогам в Linux устанавливаются командой chmod (change mode). Разберём кратко формат задания таких прав. У нас есть возможность задать права для трёх типов пользователей: * владельца файла - u (User), * группы, в которую входит владелец - g (Group), * остальных пользователей (не забываем, что Linux многопользовательская система) - o (Other). Также есть три типа прав: * право на чтение - r (Read), * право на запись - w (Write), * право на исполнение - x (eXecute). Начнём разбираться с типа прав. Права каждого из типов пользователей кодируется числом от 0 до 7. Нарисуем такую таблицу:
rights_table.png

Что мы делаем? Мы кодируем права двоичным кодом, и если устанавливаем единицу – право дано, а если ноль – права нет. Например, для права читать и исполнять получается следующий код: 101 двоичное или 5 десятичное, для права читать и редактировать: 110 двоичное или 6 десятичное число. Понятно, что 0 (000) значит, что мы не имеем права ничего делать с файлом, а если 7 (111) – файл в нашем полном доступе. Последовательность важна, столбцы переставлять нельзя, только так: rwx. Составлять эти числа можно и не глядя в таблицу, достаточно запомнить, что читать = 4, писать = 2, а исполнять = 1, а дальше желаемые значения нужно сложить. То есть, например, читать и исполнять = 4 + 1 = 5, а читать и писать = 4 + 2 = 6. Вышесказанное верно для каждого из типов пользователей, но мы должны задать права для всех трёх типов. Делается это с помощью триады из чисел, составленных на предыдущем этапе. Здесь последовательность тоже важна: ugo. Например, если пользователь u может читать, писать и исполнять: 7, группа пользователя g только читать и исполнять: 5, а остальные o только читать: 4, то триада будет 764. Если триада 644 – все пользователи имеют право чтения, владелец может редактировать. Если триада 000, никто не имеет прав на работу с файлом. Ну а если 777 – бинго, можно всем и всё, воровать, убивать, любить гусей читать, писать и исполнять. Использовать можно не только цифровой, но и буквенный способ задания. Если мы хотим дать право владельцу исполнять файл, то мы должны обозначить владельца, который, как мы говорили ранее, обозначается буковой u, добавить знак плюс + и обозначить права на исполнение x. В результате получим u+x. Если хотим отнять какие-то права, используем такой же способ записи, но со знаком минус -, например, отберём у остальных право на запись o-w. Права задавать по одному совершенно не обязательно. Предположим, мы хотим дать владельцу права на чтение, запись и исполнение, его группе права на чтение и исполнение, а остальным только на чтение, запишем u+rwx,g+rx-w,o+r-xw, где в качестве разделителей для типов пользователей используется запятая (только запятая, без пробелов). Внимательные заметил, что при таком способе записи соблюдать последовательность rwx необязательно. Напоследок скажу, что можно задать одинаковые права всем типам пользователей - a (All), то есть, если мы напишем a+rw-x, у всех будут права читать и писать и ни у кого – исполнять. Хорошо, давайте дадим нашим скриптам право на исполнение:

chmod u+x publisher.py
chmod u+x subscriber.py

Госспади, а разговоров-то было.

Всё так, но нам сейчас необходимо задать для владельца права на исполнение, что мы и сделали. Посмотрим на результат с помощью команды ls с флагом -l, которая покажет список файлов в расширенном виде с правами, в том числе:
user_execute_list.png

На первый минус внимания не обращаем, он говорит, что наш файл не каталог, если бы был каталог, было бы значение d. А вот дальше идут наши триады для владельца, группы и остальных: u=rwx, g=rw-, o=r–. И сейчас вы уже способны расшифровать, что это значит. Кстати, цвет у исполняемого файла изменился на зелёный, но это не лучший способ узнать, можем ли мы его запустить, так как зелёным он будет и в случае разрешения права на выполнение от остальных, но запрет на исполнение от владельца.

Программирование узла паблишера

Теперь нам нужно с помощью какого-нибудь редактора текстов написать программу в файле publisher.py. Можно и дальше использовать терминал, есть текстовые редакторы с интерфейсом CLI, например, Vi/Vim, про который пока вам надо знать только команду выхода из него без сохранения файла :q!, или Nano. Но если мы сейчас станем использовать их, у нас начнёт расти борода и свитер крупной вязки, а мы собрались не за этим. Итак, для редактирования текста наших программ пока будем использовать GUI-редактор gedit.

gedit publisher.py

Первой строчкой программы у нас будет так называемый шебанг (shebang) – строка комментария, начинающаяся с символов #!. Шебанг — это директива, которая сообщает операционной системе, какой интерпретатор использовать для запуска файла сценария. Например, строка #!/usr/bin/env python3 указывает, что файл должен быть выполнен с помощью Python 3. Чтобы не писать точное место установки python3, а позволить системе самой найти его, напишем такой шебанг:

#!/usr/bin/env python3

Дальше, чтобы программа на питоне могла работать с ROS, нам нужно импортировать библиотеку ROS для Python таким образом:

import rospy

Теперь начнём писать код нашей программы публикации сообщений, а чтобы публиковать сообщения нам нужны … сообщения. Почитать про стандартные сообщения мы можем на соответствующей странице в wiki ROS в разделе, где описываются встроенные типы (Built-in types). Также мы можем просто сходить в каталог, где лежат файлы ROS, которые эти сообщения определяют:

cd /opt/ros/noetic/share/std_msgs/msg
ls
std_msgs_list.png

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

from std_msgs.msg import Int64

Здесь мы не тащим все стандартные сообщения в программу, а берём из них только нужный нам тип. На самом деле, импортировать можно и тем и другим способом, выигрыша в производительности, экономии ресурсов и прочем практически нет. Далее, нам необходимо инициализировать наш узел, для чего мы воспользуемся одной из функций библиотеки rospy – init_node с параметром 'publisher_node', что будет именем нашего узла:

rospy.init_node('publisher_node')

Такая функция инициализации должна быть обязательно вызвана при исполнении узла ROS и вызвана только один раз. Теперь мы должны создать объект – переменную, которая будет отвечать за публикацию сообщений. Объект это составная переменная, в составе которой могут быть как данные, так и обрабатывающие их функции. В принципе, Python такой язык, что в нём объектами является всё, но это, да и объектно-ориентированный подход к программированию является слишком большой темой для отступления. Вернёмся к коду. Итак, мы придумаем название для нашей переменной, например, pub, а затем зададим имя нашего канала передачи данных или темы ROS (ROS topic), типа данных, которые будут передаваться и размер очереди сообщений:

pub = rospy.Publisher('topic', Int64, queue_size = 1)

Канал мы назвали ‘topic’, а передавать он будет сообщения формата Int64. Формат этот мы импортировали чуть ранее именно для этой цели. Если заранее не импортировать определение какого-то типа данных, то и использовать их не получится – при выполнении возникнет ошибка. Последний параметр – это размер внутренней очереди сообщений, используемой для доставки сообщений подписчикам. Он задаёт максимальное количество сообщений, которые могут быть временно сохранены, если подписчики не успевают их обработать. Хотя это и называется очередью, поведение FIFO (первым пришёл — первым ушёл) не гарантируется, особенно при высокой нагрузке, что надо учитывать при создании систем, критичных к этому. Мы здесь задали значение 1, так как нам не очень важна потеря сообщений. Кстати, две последние строчки кода можно безболезненно поменять местами, так как порядок их выполнения не влияет на результат, и ни одна из них не содержит каких-либо отсылок к другой. Пришло время отправлять сообщения. Конечно, можно представить себе ситуацию, когда от нас потребуется отправить сообщение только один раз и закончить выполнение программы, но обычно такая передача ведётся на протяжении какого-то времени, то есть, мы посылаем подряд несколько сообщений одного формата, но с разными значениями. Для реализации такого поведения в языках программирования, и в Python, в том числе, имеются операторы цикла. Мы будем использовать так называемый цикл с предусловием – цикл, который выполняется до тех пор, пока условие цикла истинно, а проверяется это условие до выполнения тела цикла. То есть, тело такого цикла может не выполниться ни разу. В Python этот цикл реализуется оператором while. В качестве условия мы возьмём результат функции rospy.is_shutdown(), функция, которая возвращает значение Ложь (False) до тех пор, пока узел не получил сигнал завершения работы (например, через Ctrl+C). Что у этой функции “под капотом” для нас не важно, нам важно, что если мы дадим сигнал на остановку выполнения нашей программы, это значение изменится на Истина (True). Правильнее сказать даже, что значение этой функции становится истинным, если rospy прекращает свою работу. Соответственно, мы должны выполнять цикл с предусловием до тех пор, пока значение функции is_shutdown() не False, то есть мы должны применить к функции логическое отрицание – not в Python. Запишем наш оператор так:

while not rospy.is_shutdown():

Двоеточие в конце обязательно, регистр имеет значение, то есть, написать While с заглавной буквы нельзя. Такая организация цикла в ROS-приложении на питоне используется очень часто, так что стоит её запомнить. После строки оператора цикла мы начнём писать его тело. Тут необходимо сказать о так называемых уровнях вложенности в питоне. Если мы посмотрим на язык С, то увидим, что тело цикла там состоит из одного оператора или из нескольких операторов, но окруженных некоторой конструкцией:

while (k == 5) k++;
while (True) {a = 1; b = 2; s = a + b; if (s > 2) {s++; printf (s);}}

Если посмотреть на вторую строку с точки зрения теории языков программирования, то можно сказать, что мы там тоже используем один оператор – составной. И вот используя составной оператор, мы можем записывать в строчку десятки операторов и языковых конструкторов. Кто будет так делать, получит палкой по голове от своих коллег-программистов, которые потом будут читать этот …код. Хотя надо признаться, что так программисты в прошлом писали довольно часто, ведь “640 КБ хватит всем!”. То есть, памяти у компьютеров было мало, а лишние символы в коде эту память тратили и вставал выбор между читаемостью и размерами кода. Сегодня памяти много, так давайте будем милосердны к другим программистам и перепишем код из примера, используя отступы:

while (k == 5)
    k++;
while (True) {
    a = 1;
    b = 2;
    s = a + b;
    if (s > 2) {
        s++;
        printf (s);
    }
}

Читать стало легче? Да. Появился некий формальный способ обозначить уровни вложенности? Да, мы видим, где у нас тело цикла, а где операторы в условной конструкции. А смысл в составном операторе есть? Ииии… не торопитесь. Тоже – да, ведь есть стандарт языка С и менять его от того, что кто-то там занялся “украшательством” своего кода не будут. И вот тогда голландский программист Гвидо ван Россум под воздействием телешоу “Летающий цирк Монти Пайтона” придумал свой язык Python с объектно-ориентированным программированием и отсупами для обозначения уровня вложенности. Итак, вернёмся к коду паблишера. Чтобы написать следующую строку в теле цикла используем клавишу Пробел или TAB. Давайте для однообразия условимся использовать дальше по два пробела для следующего уровня вложенности. Давайте забудем, что мы уже писали оператор цикла и запишем его снова, но уже с телом цикла:

while not rospy.is_shutdown():
  pub.publish(1)
  rospy.sleep(1)

Теперь нам надо что-то делать с телом. В том смысле, что требуется понять, что же мы такое написали в теле цикла. А написали мы два вызова функций, первая из которых принадлежит нашему объекту pub и публикует целое число 1, а вторая принадлежит rospy и предлагает сделать некоторую приостановку в работе программы на одну секунду. Такая приостановка необходима в том случае, когда мы хотим публиковать наши данные с заданной периодичность. Следует также понимать, что если мы не будем приостанавливать программу, наша передача данных будет происходить с очень высокой частотой, не частотой выполнения машинных команд процессором конечно, но с частотой с сопоставимыми значениями, а в этом вряд ли есть необходимость.

Полный текст узла паблишера

И на этом с написанием паблишера всё. Вот что у нас получилось:

#!/usr/bin/env python3
import rospy
from std_msgs.msg import Int64

# Инициализация узла и публикатора
rospy.init_node('publisher_node')
pub = rospy.Publisher('topic', Int64, queue_size = 1)

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

В тексте появилась ещё несколько строк на русском языке, которые начинаются со знака #. Это комментарии, строки текста программы, которые не выполняются. Оставлять комментарии с пояснениями в программе – хорошая практика.

Сохраним файл Ctrl+s и закроем gedit.

Запуск паблишера на исполнение

Теперь, когда у нас есть первое приложение ROS, мы бы конечно хотели его запустить, но запустить не просто как скрипт на языке Python, а именно как рос приложение. Что для этого нужно? В первую очередь нам нужен ROS. Внезапно? Весь первый урок мы потратили на то, чтобы его установить, и тут такое. Ничего страшного, ROS в системе установлен, нам нужно лишь запустить его ядро, фактически, запустить эту самую операционную систему роботов, которая и будет для нас запускать узлы и направлять между ними сообщения. Для этого служит команда roscore. Открываем новый терминал Ctrl+Alt+t или открываем новую вкладку существующего терминала Ctrl+Shift+t в окне уже открытого терминала и пишем roscore
roscore.png

Как вы можете заметить, я не менял каталог, так и остался в каталоге с нашими скриптами. Если ROS установлен правильно, ядро ROS, или иначе ROS-мастер, запустится. Теперь этот терминал (или вкладка, смотря что открывали) нам надо оставить в покое, ROS-мастер работает в нём и ничего другого в этом терминале запустить нельзя. Хорошо, запустим на исполнение теперь скрипт публикатора, но сделаем это не как с обычным скриптом на питоне, а запустим его как узел ROS. Для этого есть команда rosrun <имя пакета> <имя узла>. Наш пакет называется publ_n_subs, а узел, который мы хотим запустить – publisher.py. Запустим rosrun publ_n_subs publisher.py:
rosrun_error.png

Мы видим ошибку, но уже привычно не пугаемся, а разбираемся в причинах. Причина первая – мы не собрали заново наше рабочее пространство catkin. В принципе, для скриптов питона сборка – это нечто чуждое, так как Python интерпретируемый, а не компилируемый язык. То есть, Python не переводит заранее исходный код программы в машинный, а читает этот исходный код и выполняет его. Вот если бы мы писали наши ROS-приложения на С++, а это ещё один язык на котором можно писать приложения ROS, тогда необходимость сборки приложения вопросов бы не вызывала. Но здесь мы говорим не о сборке приложений, а о сборке рабочего пространства, которая и даст возможность ROS увидеть новый пакет в том числе. Хорошо, снова перейдём в рабочее пространство catkin и выполним сборку:

cd ~/catkin_ws/
catkin build

Вторая причина – мы не предоставили нашим операционным системам информацию о рабочем пространстве catkin. Если помните, в первом уроке мы делали настройку среды и автоматизировали её через скрипт .bashrc. Тогда мы сообщили ОС Ubuntu, где находится ROS, а теперь настала очередь сообщить ROS и Ubuntu, где находится рабочее пространство catkin. В каталоге devel рабочего нашего пространства также есть скрипт setup.bash с настройками, который мы и должны показать ОС. Аналогично тому, как мы это делали в первом уроке для файла настроек ROS, сделаем для и файла настроек рабочей среды, только изменим путь:
source ~/catkin_ws/devel/setup.bash
Также автоматизируем запуск файла настроек рабочей среды через скрипт .bashrc:
echo "source ~/catkin_ws/devel/setup.bash" >> ~/.bashrc
Теперь нам надо сделать так, чтобы терминал увидел новые настройки. Для этого просто закроем его и откроем заново. Ну или дадим команду source ~/.bashrc.
Теперь снова дадим команду rosrun publ_n_subs publisher.py и … не увидим результата, хотя это уже и есть результат. Сообщений об ошибке нет, строка приглашения в терминале после запуска не появилась – наш публикатор работает? Давайте проверим. Откроем новый терминал, ведь оба открытых терминала сейчас заняты запущенными приложениями: roscore и нашим паблишером, и посмотрим список тем ROS (ROS topics) командой rostopic list:
rostopic_list.png

Как мы назвали наш канал передачи данных, topic? Так вот же он. Чтобы убедиться, что мы не только создали канал, но и публикуем туда данные, воспользуемся ещё одной возможностью команды rostopic, выведем данные темы в терминал. За это отвечает ключ echo. Попробуем выполнить эту команду для нашей темы topic. Важно отметить: команда rostopic echo /topic сама становится временным подписчиком на эту тему. Это означает, что подписчик всё-таки существует — просто он создаётся автоматически самой утилитой rostopic. Если тема не имеет активных подписчиков, сообщения могут не публиковаться (в зависимости от реализации паблишера и наличия подключения), поэтому данная команда — удобный способ проверить наличие данных в теме без необходимости запускать собственный узел-подписчик. Ещё одно замечание, перед именем темы надо обязательно поставить прямой слэш (это такая косая палочка, а ни в коем случае не жанр литературы ) rostopic echo /topic:
rostopic_echo.png

Отлично, данные идут приблизительно раз в секунду и их значение равно 1. Всё как мы и замышляли. Теперь давайте остановим печать наших данных, наш публикатор и ROS-мастер нажатием Ctrl+с в тех терминалах, где мы их запускали и закроем терминалы. А после откроем снова, в одном терминале запустим ROS-мастер, а в другом напишем rosrun publ и нажмём TAB. Магия TAB работает, ROS видит наши пакеты. А если не видит, мы что-то неправильно настроили. На этом считаем запуск и проверку паблишера завершёнными успешно.

2.5.2 Сабскрайбер

Замечательно, публикатор у нас есть, теперь надо разработать подписчик. Откроем файл subscriber.py в текстовом редакторе: gedit ~/catkin_ws/src/publ_n_subs/scripts/subscriber.py. Если вы думаете, что я помню этот путь наизусть, вы ошибаетесь, я просто пользуюсь магией TAB.

Программирование узла сабскрайбера

Вновь начнём с шебанга.
python #!/usr/bin/env python3
Так как мы будем работать с rospy и сообщениями типа Int64, мы должны повторить и эти строки из текста паблишера:

import rospy
from std_msgs.msg import Int64

Дальше начинаются отличия. Мы должны, как и в паблишере, инициализировать узел, но узлу подписчика мы дадим другое имя. Это логично, да и нельзя по-другому, ROS не разрешит запустить два узла с одним именем.

rospy.init_node('subscriber_node')

Теперь мы подошли к тому месту в программе, где должны подписаться на паблишер. В отличии от кода паблишера нам не надо создавать объект, здесь нам надо вызвать функцию с тремя входными параметрами, два первых из которых просты и понятны, это имя темы и формат получаемых данных. А вот третий параметр значительно интереснее, это так называемая функция обратного вызова. Функции обратного вызова нам интересны тем, что мы можем передать их в качестве параметра в другую функцию и другая функция может вызывать эту функцию обратного вызова уже по своему усмотрению. Строго говоря, функции обратного вызова позволяют определить логику работы программы при наступлении определённого события. Например, в ROS они используются, чтобы обрабатывать входящие сообщения от других узлов. Для чего нам это нужно? В состав библиотеки rospy входит функция rospy.Subscriber, которая и обеспечивает действия ROS при получении сообщения. Особенно радует, что её уже написали для нас, единственное, что мы должны сделать, это передать этой функции наш код обработки принятого сообщения. Код обработки мы должны оформить в виде функции. Передав имя нашей функции обратного вызова в качестве параметра, мы можем рассчитывать на автоматический вызов тела этой функции в момент получения сообщения. Запишем:

rospy.Subscriber('topic', Int64, callback)

Я назвал функцию callback, но это совершенно не обязательно, её можно назвать как угодно. В принципе, у нас может возникнуть необходимость написать сабскрайбер для нескольких случаев, в одном из которых мы должны только печатать данные, во втором производить над данными какие-то действия, а в третьем, пересылать данные в сеть. Мы можем создать функции callback_print, cback_process и cllbck_send, и передавать их параметром в функцию Subscriber по какому-либо ключу. Я так уверенно говорю про функции, надеясь, что про них все знают. Если знают не все, функция – некоторый способ организации кода программы, при котором часть кода программы выделяется в отдельную подпрограмму. Функция имеет собственные входные параметры и может возвращать некоторые значения. В Python запись функции начинается с заголовка: ключевого слова def, далее следует имя, а за именем в круглых скобках перечисляются входные параметры. Заканчивается заголовок двоеточием, а далее идёт тело функции, строки которого начинаются с отступов для обозначения уровня вложенности. Итак, мы создадим функцию с именем callback, параметром функции будет полученное от паблишера сообщение. Телом функции будет единственная строка кода, которая печатает полученное сообщение. Запишем:

def callback(msg):
  print(msg)

Текст функции мы поместим в коде узла подписчика после строк импорта и перед строчкой инициализации узла. Теперь нам надо обеспечить работу узла в цикле. Делать это с помощью циклической конструкции мы здесь не будем, так как обрабатывать в этом цикле ничего не придётся, всё за нас сделают ROS и rospy, которые вызовут callback-функцию тогда, когда придёт сообщение. Можно было бы использовать такую конструкцию:

while not rospy.is_shutdown():
  pass

Это достаточно распространённый способ “ничего не делать”, написать pass под условием или в качестве тела цикла, но здесь мы так делать не будем. О нас опять же уже позаботились, написав функцию rospy.spin(), которая заблокирует выход из узла, пока не получит команду об его останове, например, прерыванием с клавиатуры. Конечно, эта функция не просто подвешивает программу на ожидании, её основное назначение – удерживать узел в рабочем состоянии, позволяя внутреннему механизму ROS обрабатывать входящие сообщения и вызывать соответствующие callback-функции при необходимости.

Этой функцией, спиннером, мы и завершим текст нашего узла.

Полный текст узла сабскрайбера

Итак, у нас получился следующий код подписчика:

#!/usr/bin/env python3
import rospy
from std_msgs.msg import Int64

# Обработчик сообщения - функция обратного вызова
def callback(msg):
  print('Our first subscriber says:' + msg)

# Инициализация узла
rospy.init_node('subscriber_node')
# Инициализация подписчика с использованием функции обратного вызова
rospy.Subscriber('topic', Int64, callback)
# Зацикливание узла подписчика
rospy.spin()

Пройдёмся по тексту. В первой строке шебанаг, который говорит, какой интерпретатор использовать. Следующие две строчки (пустые строки и комментарии игнорируются) – импорт необходимых библиотек. Следующие две – функция, которая будет передана как параметр функции подписчика. Дальше строка, инициализирующая узел ROS. Следующая строка – инициализация подписчика. И последняя строка – зацикливание узла.

Давайте разберёмся, как работает код. Здесь надо понять, что у нас есть основное тело программы и тело функции, которая работает только тогда, когда эта функция будет вызвана. Если мы посмотрим на исполнение этого кода с точки зрения интерпретатора Python (не вдаваясь в теорию программирования и её раздел про трансляторы), то мы увидим такую последовательность строк для выполнения: 1. import rospy – импортировали rospy 2. from std_msgs.msg import Int64 – импортировали стандартное сообщение с Int64 3. rospy.init_node('subscriber_node') – инициализировали узел 4. rospy.Subscriber('topic', Int64, callback) – задали параметры подписчика 5. rospy.spin() – повисли на ожидании окончания работы программы.

Позвольте, у меня все ходы записаны! Где наша ладья функция? Что можно сказать? Приблизительно там же, где и функции rospy.init_node или rospy.spin. Давайте условимся, что код функции лежит отдельно, код программы отдельно. Это, конечно, не совсем так, даже совсем не так, но если есть желание разобраться, как – так, возьмите Седьмой том “Искусства программирования” Дональда Кнута, например. Там подробно написано, как работает транслятор. А пока давайте разделим, как я сказал раньше. Тогда вопрос, как же мы будем получать и обрабатывать данные? Ответ, с помощью той самой функции обратного вызова, которую функция rospy.Subcsriber “привязала” к определенному событию в ROS. Если вернуться к аналогии с Telegram-каналом, как только в канале опубликован пост, подписчикам приходит оповещение и они читают пост. Здесь происходит очень похожая ситуация, публикатор послал сообщение средствами ROS, подписчик получил от ROS об этом уведомление и прочитал это сообщение с помощью функции обратного вызова, и происходит всё это в том месте программы, где у нас работает спиннер. Запускать узел сабскрайбера без паблишера здесь лишено смысла, так как в нашем первом пакете узел сабскрайбера только печатает данные от паблишера, поэтому давайте испытаем весь наш пакет в комплексе.

2.5.3 Проверка публикации и приёма сообщений

Откроем три вкладки терминала. В первой запустим ROS-мастер, во второй - публикатор, а в третьей командой rosrun publ_n_subs subscriber.py – подписчик. Во вкладке терминала с подписчиком мы видим:
subscriber.png

Теперь давайте перейдём во вкладку с узлом паблишера и нажмём Ctrl+c. Чего мы добьёмся в этом случае? Во-первых, у нас очевидно перестал работать паблишер, так как мы видим приглашение терминала. Во-вторых, если мы перейдём во вкладку с узлом подписчика, мы увидим, что подписчик перестал печатать данные. Поток данных от издателя прекратился, события для обращения к функции обратного вызова нет, узел сабскрайбера не принимает данные от паблишера. То есть, узел подписчика работает, но данных не получает и отображать на экран ему нечего.

2.5.4 Что у нас получилось?

Давайте посмотрим, что у нас получилось. Мы создали два узла ROS – издателя и подписчика и создали одну тему, которую публикует издатель. А ещё мы запустили эти два узла параллельно под управлением ROS. Теперь мы можем дорабатывать наши узлы, так как они созданы независимыми друг от друга, за исключением типа сообщения, которым узлы обмениваются. В общем, мы создали пакет ROS следуя одному из принципов программирования в ROS – модульности. Кстати, вы заметили, что мы не выполняли больше команду catkin build, хотя изменяли исходные коды наших программ – я об этом говорил ранее, сборке здесь подвергается рабочее пространство, если случились мы в него внесли какие-то значительные изменения. Скрипты питона в сборке не нуждаются.

2.6 Несколько полезных команд для работы с пакетами ROS

2.6.1 Поиск установленных на компьютере пакетов

Хорошо, мы создали свой первый и единственный на данный момент пакет ROS, однако, пакетов на компьютере может быть не один и даже не один десяток. Более того, у вас сейчас на компьютере установлен не один десяток пакетов ROS, которые не лежат в нашем рабочем пространстве, а были установлены вместе с ROS. Возможна ситуация, когда вы сели за компьютер, на котором кто-то уже разрабатывал пакеты до вас. Или вы сами уже несколько лет на разных компьютерах занимаетесь разработкой пактов ROS. Возникает логичный вопрос – а как мне узнать, какие пакеты установлены на компьютере? Для ответ ан этот вопрос существует команда rospack с ключом list – список пакетов ROS. Давайте остановим ROS-мастер и наши узлы, если мы этого ещё не сделали и в одном из терминалов подадим команду rospack list. У меня результатом команды стало несколько экранов наименований пакетов в алфавитном порядке и места их расположения (покажу только первый пяток):
rospack_list.png

Первый пакет в списке с именем actionlib (выделил наименование пакета голубым цветом я, а не команда rospack) расположен в папке /opt/ros/noetic/share/actionlib (жёлтым путь также выделил я). Очевидно, этот пакет создавали не мы и не кто-то до нас, так как он лежит в папке, куда был установлен ROS. Попробуем найти наш пакет по имени:
rospack_list_publ_n_subs.png

Вот он, лежит в catkin_ws, как и ожидалось, но найти его среди десятка других пакетов было нелегко. Неужели нет ничего, что облегчило бы наш труд? Конечно есть. Мы можем ограничить вывод с помощью замечательной команды grep, которая применяется в Unix-подобных системах для поиска текста в потоках данных. Это универсальная команда терминала, а не специфичная для ROS. Здесь у нас есть поток данных от rospack list и есть строка, которую мы хотим найти, например, имя пакета. Осталось это объединить в одну команду с помощью оператора конвейера (pipeline), который позволяет перенаправить выход одной команды на вход другой. Оператор этот реализован с помощью символа вертикальной черты, которую мы поставим между командами rospack и grep. Поищем все пакеты ROS в нашем рабочем пространстве, наименование которого мы знаем – catkin_ws:

rospack list | grep catkin_ws
rpack_list_grep.png

Вот мы и получили список пакетов, размещённых в нашем рабочем пространстве. Необходимо учесть, что grep не является гарантированным способом получения только пакетов из catkin_ws, также возможны совпадения по другим путям, если таковые есть где-то в глубине дерева подкаталогов домашнего каталога. Вывод grep за нас отфильтрует, но оценивать его всё же придётся своей головой.
В любом случае, рекомендую запомнить такой способ фильтрации объёмных выводов команд в частности и команду grep в общем.

2.6.2 Удаление пакета ROS

Теперь давайте разберём ещё одну полезную возможность – удаление какого-либо пакета. Удалять свой, ставший нам родным, пакет publ_n_subs мы не станем. Удалять пакеты, которые устанавливались вместе с ROS нам “чревато боком”. Так что удалять такие пакеты мы тоже не станем. Что же делать? Создать новый пакет и затем, пока мы не успели к нему привязаться, безжалостно удалить его. Предлагаю вам вспомнить, как создавать пакет, а затем самим создать его и посмотреть, видит ли его ROS, ведь вы уже умеете всё это делать, а я пока отдохну. Хорошо, вы создали свои пакеты, я тоже создал свой, ведь мне тоже надо что-то удалить:

cd ~/catkin_ws/src/
catkin_create_pkg condemned
cd .
catkin build
# Открыть новый терминал
rospack list | grep catkin_ws

Итак, приговорённый пакет у нас есть, теперь можно его удалить. Будем считать, что у нас в каталоге с исходниками пакета есть и исходные коды, сейчас это не важно. Первое, что нужно сделать, стереть каталог нашего пакета. Сделаем это командой удаления непустого каталога:

rm -r ~/catkin_ws/src/condemned

После этого мы должны убрать следы пребывания нашего пакета на компьютере, ведь мы собирали наше рабочее пространство, соответственно, следы пакета содержаться в папках build, devel и log. Например, посмотрим на файлы в каталоге /home/groolu/catkin_ws/devel/lib/pkgconfig:
ls_orpans.png

Для полной зачистки мы должны использовать команду catkin clean с флагом --orphans. В этом месте мы можем немного взгрустнуть, ведь мы не профессиональные чистильщики и зачищать сирот (orphans) для нас непривычно. Ну да ладно, чёрный юмор в меру – это хорошо, приступим:

catkin clean --orphans

Если посмотреть на вывод catkin clean –orphans, увидим, что уничтоженный пакет опознали и провели зачистку его файлов:
clean_orphans.png
Мы очистили все файлы из каталогов build, devel и logs, которые остались сиротами без нашего пакета. Учтите, что эта команда не удаляет остаточные файлы вне этих директорий, если мы их там каким-то образом создали.
Хорошо, посмотрим ещё раз в каталог _/home/groolu/catkin_ws/devel/lib/pkgconfig:
ls_wout_orphans.png

Отлично, теперь мы умеем создавать, искать уже созданные и уничтожать пакеты ROS. Пойдём дальше.

2.7 Запуск проекта ROS

Предположим, что у вас есть проект, состоящий из большого количества узлов, и вам нужно их всех запустить, чтобы проект заработал. Мы можем последовательно запускать узлы с помощью команды rosrun, никто нас за это ругать не будет, а вот если нам надо передать наш проект кому-то другому, в идеале, заказчику, который нам заплатит за это деньги, писать ему инструкцию по запуску пакета на пару страниц – не очень хорошая практика. Возникает очередной вопрос “так что же делать?”. О нас опять же позаботились, создали для нас команду roslaunch, которая позволяет автоматизировать запуск проекта – осуществить запуск нескольких узлов, установку параметров, перенаправление вывода и даже запуск узлов на удалённых машинах. Для того, чтобы автоматизировать процесс запуска, нам, очевидно, надо каким-то образом передать команде roslaunch список узлов, которые мы хотим запустить. Такой список оформляется в виде так называемого launch-файла, который пишут на специальном языке XML (eXtensible Markup Language) или расширяемом языке разметки. С файлами на XML мы с вами встретимся ещё не раз, более того, один такой файл мы уже используем – файл package.xml содержит метаданные нашего ROS-пакета: имя, версию, авторов, зависимости и другие данные, необходимые для сборки и управления пакетом.
Хорошо, давайте создадим наш launch-файл. Для начала нам надо создать в каталоге нашего пакета подкаталог для launch-файлов. Множественное число я употребил корректно, таких файлов может быть несколько, их создают под различные сценарии выполнения проекта. Например, вы создали серьёзный проект, который работает с оборудованием, но хотите запускать его и с реальным оборудование и в симуляторе Gazebo. То есть у вас не будет узлов, которые взаимодействуют с оборудованием, но взамен будут узлы, которые взаимодействуют с эмулятором. Вот и очевидная причина создать два launch-файла. Вернёмся к созданию каталога. В ROS-проекте такой каталог принято называть launch. Создадим его, а внутри этого каталога создадим файл с расширением .launch – start.launch и начнём его редактировать:

mkdir mkdir ~/catkin_ws/src/publ_n_subs/launch
touch ~/catkin_ws/src/publ_n_subs/launch/start.launch
gedit ~/catkin_ws/src/publ_n_subs/launch/start.launch

Теперь в файл мы внесём следующие строки:

<launch>
    <node name="publisher_node" type="publisher.py" pkg="publ_n_subs" output="screen"/>
    <node name="subscriber_node" type="subscriber.py" pkg="publ_n_subs" output="screen"/>
</launch>

Для тех, кто знаком с языками разметки, разобраться в этом тексте просто. Давайте скажем так, здесь есть так называемые теги, которые ограничивают некий элемент. Здесь тегов два: launch для всего файла, так называемый корневой элемент и node для узлов. Теги должны быть открывающими и закрывающими, закрывающие повторяют имя открывающих, но начинаются с прямого слэша, как, например, <launch> и </launch>. Для тега node здесь применён упрощённый способ записи элемента узла. Внутри тега node узел определяется несколькими атрибутами: name – имя, данное узлу при инициализации, type – имя файла с текстом узла, pkg – наименование пакета ROS, к которому принадлежит узел, output – куда будем направлять вывод от узла. Разберём наш файл. Тег <launch> в начале и </launch> в конце – обязательные для launch-файла записи, между ними элементы узлов, которые открываются тегом node.

<node name="publisher_node" type="publisher.py" pkg="publ_n_subs" output="screen"/>

Для паблишера возьмем имя из rospy.init_node (publisher_node), зададим name="publisher_node", тип – это имя файла паблишера, то есть, type="publisher.py", пакет – это имя, которое мы присвоили пакету при создании, значит, pkg="publ_n_subs", ну а выводить мы пока будем на экран, следовательно output="screen". С узлом для подписчика всё аналогично. Будем считать, что текст launch-файла мы написали, теперь сохраним его Ctrl+s и попробуем запустить с помощью команды roslaunch, первым аргументом которой будет имя пакета, а вторым – имя launch-файла из этого пакета. Мы вводим команду:

roslaunch publ_n_subs start.launch

Мы нечего не забыли? Мы же не запустили ROS-мастер, но это не страшно. Команда roslaunch автоматически запускает roscore, если он ещё не запущен, и затем последовательно стартует все указанные в launch-файле узлы.
Смотрим на экран и видим, что подписчик печатает данные. Далее с помощью нескольких команд убеждаемся, что у нас работают узлы паблишера и сабскрайбера – rosnode list, что у нас есть тема /topicrostopic list и что в /topic публикуются данные rostopic echo /topic. test_roslaunch.png

2.8 Графический инструмент разработчика ROS

Мы всем сердцем полюбили командную строку и терминал, но отказываться от возможностей графического интерфейса мы тоже не будем. Тем более, иногда он может нам дать сразу гораздо более общую и наглядную информацию, чем текстовый вывод. Как говорится, одна картина стоит тысячи слов. В приведённом выше случае мы, наверное, хотели бы не вызывать перечисление узлов и тем, а посмотреть на всю картину целиком. И мы можем это сделать с помощью графического фреймворка для ROS под названием rqt. Это набор различных графических плагинов, очень полезных разработчику ROS. Давайте вызовем rqt из командной строки и перейдём в меню Plugins->Introspection->Node Graph:
rqt_node_menu.png

В ответ мы увидим следующую картину:
pkg_graph.png

Это граф запущенных узлов ROS. Мы видим две вершины – узлы ROS и ребро – тему. Граф ориентированный, то есть мы видим в какую сторону идёт передача данных, в ту сторону, в которую направлено ребро. Естественно, rqt предоставляет нам намного больше возможностей, которыми мы пока не воспользовались, но лиха беда начало, как говорится.

2.9 Заключение

Заключение Урока 2 (расширенный вариант)

На этом уроке вы познакомились с основами создания ROS-проектов и научились работать с системой catkin — инструментом сборки и управления рабочим пространством в ROS. Вы создали собственный пакет, реализовали два узла: паблишер , который публикует данные, и сабскрайбер , который подписывается на тему и обрабатывает полученные сообщения.

Также вы узнали, как использовать ключевые команды терминала, такие как rostopic, rosnode, roslaunch, чтобы запускать, отслеживать и анализировать работу узлов и тем. Это позволило вам проверить, что ваш код работает корректно, и что данные действительно передаются между узлами через общую тему.

Особое внимание было уделено настройке среды и правам доступа к файлам. Вы научились давать права на исполнение скриптам Python с помощью команды chmod u+x, а также поняли, как правильно использовать шебанг #!/usr/bin/env python3, чтобы система могла запустить ваш скрипт напрямую из терминала.

Вы освоили базовые принципы работы с launch-файлами, которые позволяют одновременно запускать несколько узлов без необходимости вручную открывать несколько терминалов и вызывать каждый узел отдельно. Это значительно упрощает управление сложными проектами, особенно когда их размер растёт.

Кроме того, вы начали применять графические средства отладки, такие как rqt_graph, которые наглядно показывают связи между узлами и направление потоков данных. Эти инструменты помогают быстрее находить ошибки, просматривать текущую архитектуру приложения и лучше понимать, как взаимодействуют компоненты вашего ПО.

Большое внимание уделено тому, как правильно организовать рабочее пространство и как управлять им:

  • Как его инициализировать и строить (catkin init, catkin build),
  • Как очищать остаточные файлы после удаления пакетов (catkin clean --orphans),
  • Как добавлять пути к нему в окружение с помощью файла .bashrc.

Вы также освоили важные навыки работы в терминале:

  • Переход по каталогам (cd, ~, ..),
  • Создание и удаление файлов и каталогов (mkdir, rm -r),
  • Автодополнение команд (Tab) и использование истории (стрелка вверх),
  • Обработку вывода команд через grep и pipeline.

Эти знания необходимы не только для эффективной разработки в ROS, но и для понимания внутреннего устройства Linux-систем, на которых ROS работает.

Теперь вы умеете:

  • Создавать и удалять ROS-пакеты,
  • Организовывать модульную структуру проекта,
  • Запускать и тестировать узлы,
  • Использовать launch-файлы для автоматизации запуска,
  • Применять графические инструменты для анализа топологии узлов и тем.

С каждым уроком вы всё глубже входите в экосистему ROS и становитесь не просто пользователем, а настоящим разработчиком. Теперь вы можете создавать собственные проекты, используя модульный подход, и масштабировать их, добавляя новые узлы, темы и логику взаимодействия.