# ROS Основная документация: https://wiki.ros.org. **ROS** – это широко используемый фреймворк для создания сложных, распределенных робототехнических систем. На ROS основана [программная платформа Клевера](programming.md). ## Установка ROS уже установлен на [образе для RPi для Клевера](image.md). Для установки инструментов ROS на компьютере вы можете обратиться к [официальной документации](https://wiki.ros.org/noetic/Installation/Ubuntu) по установке. Для быстрого старта рекомендуется воспользоваться [образом виртуальной машины с ROS и симулятором Клевера](simulation_vm.md). ## Концепции ### Ноды Основная статья: https://wiki.ros.org/Nodes. ROS-нода[^1] – это специальная программа (обычно написанная на Python или C++), которая взаимодействует с другими нодами посредством ROS-топиков и ROS-сервисов. Разделение сложных робототехнических систем на изолированные ноды дает определенные преимущества: понижается связанность кода, повышается переиспользуемость и надежность. Очень многие робототехнические библиотеки и драйвера выполнены именно в виде ROS-нод. Для того, чтобы превратить обычную программу в ROS-ноду, необходимо подключить к ней библиотеку `rospy` (Python) или `roscpp` (C++) и добавить инициализирующий код. Пример ROS-ноды на языке Python: ```python import rospy rospy.init_node('my_ros_node') # имя ROS-ноды rospy.spin() # входим в бесконечный цикл... ``` > **Info** Любая [программа для автономного полета Клевера](programming.md) является ROS-нодой. ### Топики Основная статья: https://wiki.ros.org/Topics. Топик – это именованная шина данных, по которой ноды обмениваются сообщениями. Любая нода может *опубликовать* сообщение в произвольный топик, а также *подписаться* на произвольный топик. Для каждого созданного топика должен быть задан тип сообщений, которые по нему передаются. ROS включает в себя большое количество стандартных типов сообщений, покрывающих различные аспекты робототехники, но при необходимости возможно создание собственных типов сообщений. Примеры стандартных типов сообщений: |Тип сообщения|Описание| |-|-| |[`std_msgs/Int64`](https://docs.ros.org/api/std_msgs/html/msg/Int64.html)|Целое число.| |[`std_msgs/Float64`](https://docs.ros.org/api/std_msgs/html/msg/Float64.html)|Число с плавающей точкой (дробное) двойной точности.| |[`std_msgs/String`](https://docs.ros.org/api/std_msgs/html/msg/String.html)|Строка.| |[`geometry_msgs/PoseStamped`](https://docs.ros.org/api/geometry_msgs/html/msg/PoseStamped.html)|Позиция и ориентация объекта с заданной [системой координат](frames.md) и временной меткой (широко используется для передачи текущей позиции робота и его частей).| |[`geometry_msgs/TwistStamped`](https://docs.ros.org/api/geometry_msgs/html/msg/TwistStamped.html)|Линейная и угловая скорость объекта с заданной системой координат и временной меткой.| |[`sensor_msgs/Image`](https://docs.ros.org/api/sensor_msgs/html/msg/Image.html)|Изображение (см. [статью о работе с камерой](camera.md))| > **Info** Смотрите остальные стандартные типы сообщений в пакетах [`common_msgs`](http://wiki.ros.org/common_msgs), [`std_msgs`](https://wiki.ros.org/std_msgs), [`geometry_msgs`](https://wiki.ros.org/geometry_msgs), [`sensor_msgs`](https://wiki.ros.org/sensor_msgs) и других. Пример публикации сообщения типа [`std_msgs/String`](https://docs.ros.org/api/std_msgs/html/msg/String.html) (строка) в топик `/foo` на языке Python: ```python import rospy from std_msgs.msg import String rospy.init_node('my_ros_node') foo_pub = rospy.Publisher('/foo', String, queue_size=1) # создаем Publisher foo_pub.publish(data='Hello, world!') # публикуем сообщение ``` Пример подписки на топик `/foo`: ```python import rospy from std_msgs.msg import String rospy.init_node('my_ros_node') def foo_callback(msg): print(msg.data) # Подписываемся. При получении сообщения в топик /foo будет вызвана функция foo_callback. rospy.Subscriber('/foo', String, foo_callback) rospy.spin() # входим в бесконечный цикл, чтобы программа не завершила работу ``` Вы можете прочитать данные из топика однократно, используя функцию `wait_for_message`: ```python msg = rospy.wait_for_message('/foo', String, timeout=3) # ждать сообщения в топике /foo в таймаутом 3 с ``` Также существует возможность работы с топиками с помощью утилиты `rostopic`. Например, с помощью следующей команды можно просматривать сообщения, публикуемые в топик `/mavros/state`: ```bash rostopic echo /mavros/state ``` Команда `rostopic info` позволяет узнать тип сообщений в топике, команда `rostopic hz` — частоту публикуемых в топике сообщений. Также данные в топиках можно визуализировать и в [графических инструментах ROS](rviz.md). ### Сервисы Основная статья: https://wiki.ros.org/Services. Сервис – это некоторый аналог функции, которая может быть вызвана из одной ноды, а обработана в другой. У сервиса есть имя, аналогичное имени топика, и 2 типа сообщений: тип запроса и тип ответа. Таким образом, сервисы реализуют паттерн [*удаленного вызова процедур*](https://ru.wikipedia.org/wiki/Удалённый_вызов_процедур). Пример вызова ROS-сервиса из языка Python: ```python import rospy from clover.srv import GetTelemetry rospy.init_node('my_ros_node') # Создаем обертку над сервисом get_telemetry пакета clover с типом GetTelemetry: get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry) # Вызываем сервис и получаем телеметрию квадрокоптера: telemetry = get_telemetry() ``` С сервисами можно также работать при помощи утилиты `rosservice`. Так можно вызвать сервис `/get_telemetry` из командной строки: ```bash rosservice call /get_telemetry "{frame_id: ''}" ``` Больше примеров использования сервисов для автономных полетов квадрокоптера Клевер можно посмотреть в [документации ноды simple_offboard](simple_offboard.md). ### Имена Основная статья: https://wiki.ros.org/Names. Любой топик, сервис или параметр идентифицируется с помощью уникального имени. ROS-имя представляет собой иерархическую структуру с символом `/` в качестве разделителя (сходно с именами в файловой системе). Примеры ROS-имен: * `/` (глобальное пространство имен) * `/foo` * `/stanford/robot/name` * `/wg/node1` Эти имена является глобальными (аналогично полному пути в файлу в файловой системе). На практике рекомендуется использование *приватных* или *относительных* имен. #### Приватное имя Каждая нода может использовать собственное приватное пространство имен (соответствующее имени ноды) для своих ресурсов. Например, нода `aruco_detect` может публиковать такие топики: * `/aruco_detect/markers` * `/aruco_detect/visualization` * `/aruco_detect/debug` Когда нода ссылается на свой приватный ресурс, вместо пространства имен (`/aruco_detect/`) используется символ `~`, например: * `~markers` * `~visualization` * `~debug` Таким образом, создание топика `foo` в приватном пространство имен из Python будет выглядеть так: ```python private_foo_pub = rospy.Publisher('~foo', String, queue_size=1) ``` #### Относительное имя Несколько нод также могут объединяться в общее пространство имен (например, при одновременной работе нескольких роботов). Для того, чтобы ссылаться на топики с учетом общего пространства имен, в названии ресурса опускается начальный символ `/`. Пример создание топика `foo` с учетом общего пространства имен: ```python relative_foo_pub = rospy.Publisher('foo', String, queue_size=1) ``` > **Hint** В общем случае всегда рекомендуется использовать приватные или относительные имена ресурсов и никогда не использовать глобальные. ## Работа на нескольких машинах Основная статья: https://wiki.ros.org/ROS/Tutorials/MultipleMachines. Преимуществом использования ROS является возможность распределения нод на несколько машин в сети. Например, ноду, осуществляющую распознавание образом на изображении можно запустить на более мощном компьютере; ноду, управляющую коптером можно запустить непосредственно на Raspberry Pi, подключенном к полетному контроллеру и т. д. ## Дополнительные материалы * Учебник по ROS от Voltbro - http://docs.voltbro.ru/starting-ros/. * Другие книги по ROS - https://wiki.ros.org/Books. [^1]: Также встречается перевод "узел".