ROS (Robot Operating System)
ROS (Robot Operating System, операционная система для роботов) — это структура программной системы (фреймворк), предоставляющая функционал для распределенной работы по программированию роботов. ROS (под названием Switchyard) была первоначально разработана в 2007 году в Лаборатории искусственного интеллекта Стэнфордского университета.
При разработке робота обычно приходится реализовывать свою архитектуру, свой протокол обмена сообщениями, драйвер пульта управления, логику навигации и пр. И даже если имеется возможность использовать для этих задач различные готовые библиотеки, то все равно остается серьезная проблема — объединить их для робота в единую систему. Разработчики ROS позиционируют свою систему именно как операционную — для программ взаимодействия и управления роботом ROS играет роль операционной системы, предоставляя программам управления свои интерфейсы, библиотеки и готовые приложения. ROS работает под уже готовой OC (Ubuntu Linux), в которой реализует свой дополнительный слой абстракции — конкретно для управления роботами. ROS обеспечивает стандартные службы операционной системы, такие как аппаратную абстракцию, низкоуровневый контроль устройств, реализацию часто используемых функций, передачу сообщений между процессами и управление пакетами.
ROS развивается в двух направлениях: в качестве уже описанной здесь операционной системы и в виде поддерживаемых пользователями пакетов (ros-pkg), организованных в наборы (стеки), реализующие различные функции робототехники. Так, ROS содержит вспомогательные библиотеки и приложения для роботов: преобразование систем координат, утилиты для визуализации данных и распознавания объектов, стек навигации и многое другое. Реализованы для ROS и драйверы, позволяющие единым образом работать со многими устройствами: джойстиками, устройствами GPS, камерами, лазерными дальномерами и пр.
ROS основан на архитектуре графов, где обработка данных происходит в узлах, которые могут получать и передавать между собой сообщения (структурированные данные). Комбинируя готовые узлы ROS и, по необходимости, дописывая собственные, можно существенно сократить время разработки и позволить себе сконцентрироваться только на тех задачах, которые действительно нужно решить.
На данный момент под управлением ROS работает уже много роботов. Вот неполный список: PR2, TurtleBot, PR1, HERB, STAIR I и II, Nao, Husky A200, iRobot Create, Lego Mindstorms NXT.
ROS выпускается в соответствии с условиями лицензии BSD и c открытым исходным кодом. Она бесплатна для использования как в исследовательских, так и в коммерческих целях. Пакеты из ros-pkg распространяются на условиях различных открытых лицензий.
Дистрибутивы ROS
Самым первым дистрибутивом ROS был Box Turtle — релиз от 2 марта 2010 года.
C Turtle (релиз от 2 августа 2010 года) — вторая версия дистрибутива ROS, содержащая обновление библиотек, входящих в ROS Box Turtle.
Diamondback (релиз от 2 марта 2011 года) — третий дистрибутив ROS. Содержит новые стеки, включая поддержку сенсора Kinect, стабильный релиз библиотеки Point Cloud Library. Diamondback стал меньше, проще и более конфигурируем, чем ROS C Turtle.
Electric Emys (релиз от 30 августа 2011 года) — четвертый дистрибутив ROS. Он содержит стабильные версии библиотек arm_navigation и PCL, а также расширяет поддержку новых платформ, таких как Android и Arduino.
ROS Fuerte Turtle (релиз от 23 апреля 2012 года) — стал пятым дистрибутивом ROS, в нем произведены значительные улучшения, облегчающие интегрирование с другими системами программного обеспечения.
Установка ROS
Шаги по установке ROS Fuerte Turtle под Ubuntu Linux расписаны на официальном сайте системы http://www.ros.org/wiki/fuerte/Installation/Ubuntu. Рассмотрим ее установку на компьютер с операционной системой Ubuntu 12.04 (Precise).
Добавляем адрес сервера ROS, чтобы менеджер пакетов знал откуда брать пакеты ROS:
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu precise main" >
/etc/apt/sources.list.d/ros-latest.list'
Получаем ключ:
wget http://packages.ros.org/ros.key -O - | sudo apt-key add -
Обновляем список пакетов — тем самым сервер ROS.org будет проиндексирован:
sudo apt-get update
Отдаем команду установки ROS Fuerte (рекомендованная конфигурация Desktop- Full):
sudo apt-get install ros-fuerte-desktop-full
Разработчики ROS стремятся интегрировать в систему лучшие открытые робототехнические библиотеки, сохраняя при этом модульность системы, чтобы пользователь мог установить только те модули, которые ему действительно необходимы. Некоторые библиотеки вынесены из ROS и устанавливаются в ОС стандартным образом, что позволяет использовать эти библиотеки и без ROS. Установим необходимый нам пакет rosserial:
sudo apt-get install ros-fuerte-ros-comm
Отдельно необходимо установить и пакеты rosinstall и rosdep:
sudo apt-get install python-rosinstall python-rosdep
В начале новой сессии bash необходимо прописать установку переменных окружения ROS:
echo "source /opt/ros/fuerte/setup.bash" >> ~/.bashrc . ~/.bashrc
На этом установка закончена.
Узлы и темы в ROS
Узел — это исполняемый файл пакета ROS. Узлы ROS используют клиентские библиотеки ROS для связи с другими узлами. Клиентские библиотеки ROS позволяют реализовывать узлы ROS на различных языках программирования, например:
Rospy — клиентская библиотека для Python;
Roscpp — клиентская библиотека для С++;
Rosjava — клиентская библиотека для Java.
Узлы могут публиковать сообщения по теме (publisher), а также подписаться на тему для приема сообщений (subscriber). Сообщения — тип данных, используемых для публикации или подписки на тему. Типы сообщений описываются в файлах сообщений msg — простых текстовых файлах с полем типа и полем имени в строке. Типы полей, которые можно использовать:
int8;
int16;
int32;
int64;
float32;
float64;
string;
time;
duration;
other msg files;
variable-length array[];
fixed-length array[C].
Файлы msg используются для генерации исходного кода для сообщений на разных языках. Файлы msg хранятся в подкаталоге msg каталога пакета.
Узлы могут также предоставлять или использовать службы (Service). Службы позволяют узлам послать запрос и получить ответ.
Файлы служб srv — такие же простые текстовые файлы, как и файлы msg, но они состоят из двух частей: запроса и ответа. Эти две части, разделяются линией: - - - . Пример файла srv:
int64 A int64 B
---
int64 Sum
В этом примере, A и В — это запрос, а Sum — это ответ. Файлы srv хранятся в подкаталоге srv каталога пакета.
Пакет rosserial
Библиотека rosserial устанавливает соединение точка-точка (point-to-point connection) через последовательный порт с недорогими контроллерами (типа Arduino) так, что вы можете посылать сообщения ROS туда и обратно.
Библиотека rosserial состоит из общего P2P-протокола, библиотеки для работы с Arduino и узлов для ПК.
Библиотека для работы с Arduino находится в папке проекта serial, в каталоге serial_arduino/libraries. Копируем папку ros_lib в библиотечный каталог libraries Arduino IDE.
![]() |
Подключение библиотеки ros_lib |
Подготовка сообщения (publisher) на Arduino
Создадим скетч на Arduino, демонстрирующий создание узла ROS, публикующего сообщения в тему. Соединяем Arduino с компьютером, на котором запущена ROS по последовательному порту (в рассматриваемом случае это порт /dev/ttyUSB0). К контроллеру подключен датчик температуры DS18B20. Будем отправлять в ROS значения температуры с датчика, используя библиотеки OneWire.h и ros_lib.
В каждую программу ROS Arduino необходимо включить заголовочный файл ros.h и файлы заголовков для всех типов сообщений, которые мы будем использовать, — в нашем случае это std_msgs/Float32.h:
#include <ros.h>
#include <std_msgs/Float32.h>
Далее нам необходимо создать экземпляр объекта узла serial_node, что позволит нашей программе выступать в качестве подписчика (subscriber), либо публиковать сообщения (publisher):
ros::NodeHandle nh;
Создаем экземпляр publisher для нашего узла serial_node, публикующий сообщения типа std_msgs::Float32 в тему temperature:
std_msgs::Float32 float32_msg;
ros::Publisher chatter("temperature", &float32_msg);
В подпрограмме setup() необходимо инициализировать узел и объявить о роли узла chatter в качестве publisher:
nh.initNode(); nh.advertise(chatter);
В цикле loop() после считывания данных с датчика температуры публикуем сообщение в тему и вызываем ros::spinOnce(), где обрабатываются все функции обратного вызова соединения.
chatter.publish( &float32_msg ); nh.spinOnce();
Код данного скетча представлен в примере
#include <OneWire.h>
OneWire ds(10); // линия 1-Wire будет на pin 10
#include <ros.h>
#include <std_msgs/Float32.h>
ros::NodeHandle nh; std_msgs::Float32 float32_msg;
ros::Publisher chatter("temperature", &float32_msg);
void setup(void)
{
nh.initNode(); nh.advertise(chatter);
}
void loop(void)
{
byte i;
byte present = 0; byte data[12]; byte addr[8];
if ( !ds.search(addr)) { ds.reset_search(); return;
}
ds.reset(); ds.select(addr);
ds.write(0x44,1); // запускаем конвертацию delay(1000); // скорее всего достаточно 750 ms present = ds.reset();
ds.select(addr);
ds.write(0xBE); // считываем ОЗУ датчика
for ( i = 0; i < 9; i++) { // обрабатываем 9 байтов data[i] = ds.read();
}
Serial.print(" CRC=");
Serial.print( OneWire::crc8( data, 8), HEX); Serial.println();
// высчитываем температуру int HighByte, LowByte, Temp; float Tempf1,Tempf2; LowByte = data[0];
HighByte = data[1];
Temp = (HighByte << 8) + LowByte; Tempf1=Temp/16; Tempf2=(Temp%16)*100/16; float32_msg.data=Tempf1+Tempf2/100;
// публикуем сообщение chatter.publish( &float32_msg ); nh.spinOnce();
}
Теперь проверим работу данного скетча. Первое, что необходимо запустить, — это команда roscore. Команда rosnode отображает информацию об узлах ROS, которые работают в настоящий момент. Команда rosnode list выдает список этих активных узлов. В терминале увидим:
/rosout
Соответственно, есть только один работающий узел: rosout. Этот узел работает всегда, т. к. он собирает и логирует отладочные сообщения узлов.
Команда rosrun позволяет назначить имя пакета, чтобы непосредственно запустить его узел:
$ rosrun [package_name] [node_name]
Запускаем узел serial_node.py пакета rosserial_python, который соединяет нашу Arduino с остальной частью ROS. Необходимо выставить используемый последовательный порт:
rosrun rosserial_python serial_node.py /dev/ttyUSB0
В терминале набираем:
$ rosnode list
И смотрим список активных узлов:
/rosout
/serial_node
Утилита rxgraph, являющаяся частью пакета rxtools, позволяет визуально показать узлы и темы, запущенные в настоящий момент. Набираем в терминале:
$ rxgraph
![]() |
Утилита rxgraph демонстрирует cписок активных узлов и тем |
Утилита rostopic позволяет получить информацию о темах ROS. Команда rostopic echo показывает данные, опубликованные в теме. Набираем в терминале:
$ rostopic echo /temperature
и видим постоянно поступающие с Arduino данные датчика температуры
![]() |
Публикация сообщений из Arduino в тему temperature |
Создание подписки (subscriber) на Arduino
Теперь рассмотрим пример использования Arduino в качестве узла subscriber для приема сообщений. В этом примере мы будем включать/выключать светодиод, подключенный к выводу 13 Arduino, получая сообщения из ROS.
Включаем заголовочный файл ros.h и файлы заголовков для всех типов сообщений, которые мы будем использовать, — в нашем случае это std_msgs/Empty.h (для пустых сообщений):
#include <ros.h>
#include <std_msgs/Empty.h>
Далее необходимо создать экземпляр объекта узла serial_node, что позволит нашей программе выступать в качестве подписчика (subscriber), либо публиковать сообщения (publisher):
ros::NodeHandle nh;
Создаем экземпляр subscriber для нашего узла, публикующий пустые сообщения типа std_msgs::Float32 в тему toggle_led:
ros::Subscriber<std_msgs::Empty> sub("toggle_led", &messageCb );
Создаем для нашего узла функцию обратного вызова messageCb. Эта функция должна постоянно получать сообщение в качестве аргумента. Для нашей функции обратного вызова messageCb назначим тип сообщения std_msgs::Empty. По получении сообщения функция инвертирует значение сигнала на выводе 13 Arduino, при этом включая/выключая светодиод.
void messageCb( const std_msgs::Empty& toggle_msg){ digitalWrite(13, HIGH-digitalRead(13)); // blink the led
}
В подпрограмме setup() необходимо инициализировать узел и объявить о роли узла в качестве подписчика на сообщения:
nh.initNode(); nh.subscribe(sub);
И наконец, в цикле loop() вызываем ros::spinOnce(), где обрабатываются все функции обратного вызова соединения:
nh.spinOnce();
Код данного скетча представлен в примере.
#include <ros.h>
#include <std_msgs/Empty.h> ros::NodeHandle nh;
void messageCb( const std_msgs::Empty& toggle_msg){ digitalWrite(13, HIGH-digitalRead(13)); // blink the led
}
ros::Subscriber<std_msgs::Empty> sub("toggle_led", &messageCb );
void setup()
{
pinMode(13, OUTPUT); nh.initNode(); nh.subscribe(sub);
}
void loop()
{
nh.spinOnce(); delay(1);
}
Запускаем узел serial_node.py пакета rosserial_python, который соединяет нашу Arduino с остальной частью ROS. Необходимо выставить используемый последовательный порт (здесь использована другая плата Arduino, подключенная к порту ttyACM0):
rosrun rosserial_python serial_node.py /dev/ttyACM0
Переходим на компьютер с запущенной ROS и проверяем список активных узлов:
$ rosnode list
Смотрим результат:
/rosout
/serial_node
Наш узел запущен как подписчик на сообщения по теме toggle_led, но никаких сообщений он пока не получил. Связь по темам осуществляется путем отправки сообщений ROS между узлами. Для общения издателя и абонента издатель (publisher) и абонент (subscriber) должны отправлять и получать сообщения одинакового типа. Это означает, что тип темы определяется типом сообщений, которые в ней публикуются. Тип сообщения, отправляемого в тему, может быть определен с помощью команды rostopic type:
$ rostopic type toggle_led
Результат:
std_msgs/Empty
Теперь используем rostopic с сообщениями — rostopic pub публикует данные в тему:
rostopic pub [topic] [msg_type] [args]
Отправим единичное сообщение:
rostopic pub toggle_led std_msgs/Empty --once
Светодиод должен изменить значение на противоположное.
Для отправки сообщения в цикле (-r) с определенной частотой введем команду:
rostopic pub toggle_led std_msgs/Empty -r 1
Эта команда публикует сообщение с частотой 1 Гц в тему toggle_led. Светодиод будет мигать с частотой 2 раза в секунду.
Связь через ROS двух плат Arduino
Теперь создадим пример передачи сообщений через ROS между двумя платами Arduino. На одной плате Arduino, соединенной с ROS, находится датчик температуры DS18B20. Подключим к ROS по другому последовательному порту вторую плату Arduino, к которой подключен дисплей WH0802. На него мы и будем выводить показания температуры с датчика, расположенного на первой плате Arduino. Скетч для публикации показаний температуры у нас уже есть. Скетч для получения сообщений с показаниями температуры, публикуемыми в тему temperature, представлен в примере
// подключить библиотеку LiquidCrystal
#include <LiquidCrystal.h>
// создание экземпляра объекта LiquidCrystal LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
// rosserial
#include <ros.h>
#include <std_msgs/Empty.h>
#include <std_msgs/Float32.h>
ros::NodeHandle nh;
void messageCb( const std_msgs::Float32& toggle_msg){ digitalWrite(13, HIGH-digitalRead(13)); // blink the led lcd.setCursor(0, 0);
lcd.print("Temp="); lcd.setCursor(0, 1); lcd.print(toggle_msg.data);}
ros::Subscriber<std_msgs::Float32> sub("temperature", &messageCb );
void setup() { lcd.begin(8, 2); pinMode(13, OUTPUT); nh.initNode(); nh.subscribe(sub);
}
void loop() { nh.spinOnce(); delay(1000);
}
Теперь посмотрим, как реализовать передачу в ROS. Запустить два узла serial_node с одним именем не получится. Одна из особенностей ROS состоит в том, что вы можете переназначить имена (Names) узлов из командной строки:
$ rosrun rosserial_python serial_node.py /dev/ttyUSB0 name:=serial1
$ rosrun rosserial_python serial_node.py /dev/ttyACM0 name:=serial2
Теперь посмотрим список активных узлов командой rosnode_list:
/rosout
/serial1
/serial2
А командой rxgraph посмотрим узлы и темы. При этом показания температуры отображаются на дисплее WH0802.
![]() |
Утилита rxgraph — cписок активных узлов и тем |