Pull to refresh

Основы работы с Robotic Operating System 2: скажи миру «Hello, world!»

Reading time 6 min
Views 15K
    Доброй всем фазы вращения Земли!
    Сегодня мы продолжим знакомство с системой ROS, позволяющей легко и непринужденно создавать системы управления роботами. Для тех кто пропустил предыдущее занятие — примерный план работ.
  1. Установка, основные концепции
  2. Создаем свой пакет, знакомимся с сообщениями, простая программа
  3. Сервисы и параметры

    В прошлый раз мы установили, что базовым элементом ROS является пакет. Учебный процесс не может пройти мимо этого занимательного факта, так что достаньте лупу или микроскоп посильнее — будем изучать!

    В данном случае наиболее удобным будет построение своего пакета. Чтобы ничего не испортить, да и поддержать микрофлору ROS в хорошем состоянии, создадим отдельную директорию для всех экспериментов. В моем случае это будет ~/ros.

 . /opt/ros/electric/setup.sh
cd
mkdir ros
cd ./ros
export ROS_PACKAGE_PATH="~/ros:/opt/ros/electric/stacks"

    Последняя строка задает переменную окружения, хранящую пути к стандартному хранилищу пакетов (номер 2) и к пользовательским (в данном случае путь номер 1). Особо пытливый читатель может предположить, что мы создали пользовательский стэк. Увы, это будет ошибочное предположение — нам не хватает файла-манифеста stack.xml. Впрочем, это поправимо, но сейчас совершенно не нужно. А теперь самое время для таинства.

Заверните, пожалуйста


    Пакеты создаются командой roscreate-pkg и вызывается она следующим образом:
$ roscreate-pkg tutorial std_msgs rospy roscpp
 

    Первый аргумент — название пакета, затем следует список зависимостей (о них мы поговорим чуть ниже). Сейчас нужно наш пакет зарегистрировать при помощи уже известной по прошлому уроку команды rospack.
rospack profile
rospack find tutorial
/home/crady/ros/tutorial 

    Вроде бы все идет хорошо. Но что будет, если запустить наш пакет на другой машине? А если там другая версия ROS? А если это, а потом сразу в-о-о-о-н то? Отслеживание зависимостей должно помочь со всем этим. ROS выделяет 2 вида связей между пакетами — первой очереди и все остальные. Деление сразу становиться понятным, если посмотреть в манифест нашего модуля:
 cat ./ros/tutorial/manifest.xml 

<package>
  <description brief="tutorial">

     tutorial

  </description>
  <author>crady</author>
  <license>BSD</license>
  <review status="unreviewed" notes=""/>
  <url>http://ros.org/wiki/tutorial</url>
  <depend package="std_msgs"/>
  <depend package="rospy"/>
  <depend package="roscpp"/>

</package>
</source>

    Здесь в аттрибуте <depend> перечислены зависимости первой очереди. Все, что они требуют уже для себя — «косвенные» требования. Чтобы не лазить каждый раз в манифест, можно снова воспользоваться командой rospack:

$ rospack depends1 tutorial
std_msgs
rospy
roscpp

$ rospack depends tutorial
rospack
roslib
std_msgs
rosgraph_msgs
rosbuild
roslang
rospy
cpp_common
roscpp_traits
rostime
roscpp_serialization
xmlrpcpp
rosconsole
roscpp

    Осталось совсем немного теории и мы перейдем к самому вкусному — написанию кода.

Тут-то он мне и говорит...


    Предположим, что наш пакет готов и умеет… умеет… умеет вычислять энергию картинки с использованием MRF. Может быть когда-нибудь расскажу, как это и зачем, не в этом суть. Для того, чтобы в этом была какая-то польза, очевидно, наш исполняемый файл (как Вы помните с прошлого занятия, он называется узлом [node] в терминах ROS), должен на вход что-то получать (например, картинку) и что-то выдавать (допустим, число или даже ряд чисел). Так как у нас много самых разнообразных библиотек, есть смысл стандартизировать формат входных и выходных данных. Сделано это при помощи механизма сообщений (msg). Фактически, мы получаем следующую систему: издатель (Publisher) болтает с подписчиком (Subscriber) на определенную тему (Topic).
    Итак, у нас есть каркас будущего приложения и некоторые теоретические знания. Самое время нарастить на этих нелепых костях немного мяса и посмотреть, как же выглядят программы в ROS.

And so you code...


    Издателя сообщений мы напишем на замечательном языке Python. Поидее, это должен был бы быть hello-world, но мы пойдем своим путем и изменим текст собщения! Перейдем в папку с нашим пакетом:
$ cd $(rospack find tutorial)

    Создадим папку для скрипта:
$ mkdir scripts

     И создадим в ней текстовый файл нехитрого содержания:
$ vi ./scripts/talker.py

#!/usr/bin/env python

#Первым делом подгружаем манифест
#Любой узел всегда начинается с этого
import roslib
roslib.load_manifest('tutorial')

#Цепляем нужные нам библиотеки. rospy привязка ROS к Python, 
#String - обертка над очевидным типом данных для формирования сообщений
import rospy
from std_msgs.msg import String
def talker():
#Создаем шину под названием chatter для публикации сообщений типа String
    pub = rospy.Publisher('chatter', String)
#Говорим ROS, как называется наш узел
    rospy.init_node('talker')
#Пока ROS работает, публикуем строку
    while not rospy.is_shutdown():
        str = "Zooo! %s"%rospy.get_time()
        rospy.loginfo(str)
        pub.publish(String(str))
        rospy.sleep(1.0)

if __name__ == '__main__':
    try:
        talker()
    except rospy.ROSInterruptException: pass


    Так как это модуль-болтушка, необходимо указать формат сообщений, которые он публикует:
$ echo "string str" > ./msg/Str.msg

    Поддерживаются следующие типы данных:
  • int8, int16, int32, int64 (и uint*)
  • float32, float64
  • string
  • time, duration
  • other msg files
  • Массивы переменной и фиксированной длины (array[] и array[C], соответственно)

    Все, что нужно, это просто перечислить нужные поля в текстовом поле, причем 1 строка соответствует одному параметру. «И все?», спросит читатель. Ну, в общем и целом да. Формирование кода msg берет на себя местный make. Кстати, и нам пора им заняться.
 $ vi ./CMakeLists.txt

    Все, что нужно — раскомментировать следующую строчку:
rosbuild_genmsg()

    Это даст системе понять, что узел хочет поделиться с кем-нибудь информацией и нужно скомпилировать соответствующий код.
    Все, вводим в консоли
$ make
$ chmod +x ./scripts/listener.py

и наслаждаемся. Да, Python здесь тоже требует make, собственно, для сообщений. chmod помечает файл как исполняемый (это важно!).
    Тем временем займемся собеседником на не менее замечательном языке C++. Да, механизм msgs позволяет без проблем и лишних телодвижений общаться совершенно разным узлам.
    Создадим новый пакет:
$ roscreate-pkg tutorialSubscriber roscpp std_msgs
$ cd tutorialSubscriber/
$ vi ./src/listener.cpp

#include "ros/ros.h"
#include "std_msgs/String.h"

void chatterCallback(const std_msgs::String::ConstPtr& msg)
  {
    //Печатаем сообщение
    ROS_INFO("There something on the line! [%s]", msg->data.c_str());
  }

int main(int argc, char **argv)
  {
    //Создаем узел listener
    ros::init(argc, argv, "listener");
    ros::NodeHandle n;
    //указываем, что нужно слушать шину chatter, причем очередь сообщений у нас не более 1000, 
    //при получении нового - вызываем метод chatterCallback
    ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
    //spin начинает циклически вызывать наш callback до тех пор, пока ROS не прекратит работу
    ros::spin();

    return 0;
  }

    Осталось изменить немного make:
vi ./CMakeLists.txt

    Нужно привести предпоследнюю строчку к следующему виду:
rosbuild_add_executable(listener src/listener.cpp)

    Запускаем make и тут, но уже для компиляции исполняемого файла.
    Все, осталось оценить наши труды, открываем аж 4 терминала:
  1. roscore запустит master-процесс
  2. rosrun tutorial talker.py запустит Издателя
  3. rosrun tutorialSubcriber listener запустит подписчика
  4. rxgraph покажет нам схему соединения узлов

    На схеме видно, что текст сообщения будет печататься дважды — на узле listener и на /rosout, аналоге stdout.

Тем временем...


    А тем временем вечеринка набирает обороты. Люди дошли до кондиции и решили сыграть партейку в бильярд. Но что-то пошло не так…
    
Tags:
Hubs:
+4
Comments 4
Comments Comments 4

Articles