Олимпиада НТИ 2019

Работа с MQTT

MQTTopen in new window – протокол для обмена сообщениями между различными устройствами. Этот протокол используется для отправки команд дрону на Олимпиаде НТИ 2019. Для отправки сообщения оно публикуется в определенный топик; все подписчики этого топика получают это сообщение.

Подписка на топики

В образе Клевера для Олимпиады НТИ 2019 предустановлена библиотека paho-mqtt для Python. Пример работы с этой библиотекой описан ниже:

import paho.mqtt.client as mqtt  # Импортирование библиотеки mqtt

# Callback, вызываемый при получении от сервера подтверждения о подключении
def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))

    # Если подписываться на топик в on_connect, то при обрыве соединения
    # и повторном подключении произойдёт автоматическое переподписание
    client.subscribe("/xxx")

# Callback, вызываемый при появлении сообщения в одном из топиков, на который
# подписан клиент
def on_message(client, userdata, msg):
    # В объекте msg хранится топик, в который пришло сообщение (в поле topic)
    # и само сообщение (в поле payload)
    print(msg.topic, str(msg.payload))

# Инициализация клиента MQTT
client = mqtt.Client()
# Здесь указываются callback'и, вызываемые при подключении и получении сообщения
client.on_connect = on_connect
client.on_message = on_message

# Подключение к MQTT-брокеру. Первый параметр - имя или адрес брокера, второй - порт
# (по умолчанию 1883), третий - максимальное время между сообщениями в секундах
# (по умолчанию 60).
client.connect('192.168.1.199', 1883, 60)

# Метод loop_start создаёт поток, в котором будет производиться опрос сервера и
# вызов callback'ов.
client.loop_start()
# Далее продолжается ваша программа

Более подробная документация доступна на странице библиотеки в PyPIopen in new window.

Отправка сообщений

Для отправки сообщений можно использовать метод publish клиента:

import paho.mqtt.client as mqtt
# Создание подключения - аналогично предыдущему примеру кода
# ...
client.publish(topic='/xxx', payload='connected')

Данный код опубликует сообщение connected в топик /xxx.

Проверка

Для проверки вы можете опубликовать любое сообщение в топик с помощью команды hbmqtt_pub:

hbmqtt_pub --url mqtt://192.168.1.199:1883 -t /xxx -m 'сообщение'

Где 192.168.1.199 – IP-адрес MQTT-брокера, сообщение – сообщение для публикации, /xxx – необходимый топик для публикации.

Чтобы проверить публикацию сообщений от клиента, воспользуйтесь командой hbmqtt_sub:

hbmqtt_sub --url mqtt://192.168.1.199:1883 -t /xxx

Отправленные в топик /xxx сообщения будут показаны в терминале.

Работа с Клевером

Для выполнения команд на Клевере:

  • подключитесь в Wi-Fi сети NTI;
  • подключитесь к вашему Клеверу по SSH по его IP-адресу (подробнее см. подключение по SSH);

ВНИМАНИЕ

После подключения к своему дрону по SSH, смените пароль SSH-доступа, чтобы другие участники не смогли несанкционированно подключаться к нему. Для этого используйтеopen in new window команду passwd.

Для редактирования файлов на Клевере вы можете использовать консольные редакторы nano или vim. Также вы можете загружать файлы используя PyCharm или WinSCP.

Для автономного полета используйте API модуля simple_offboard.

СОВЕТ

При использовании русских букв в скрипте на Python 2, добавьте в начало программы следующую строку:

# coding: utf8

Пример программы, выполняющей взлет, полет в точку в системе координат площадки и посадку на Python:

# coding: utf8

import rospy
from clever import srv
from std_srvs.srv import Trigger

rospy.init_node('flight')

get_telemetry = rospy.ServiceProxy('get_telemetry', srv.GetTelemetry)
navigate = rospy.ServiceProxy('navigate', srv.Navigate)
land = rospy.ServiceProxy('land', Trigger)

# Взлет на 1 метр со скоростью 1 метр в секунду
navigate(x=0, y=0, z=1, speed=1, frame_id='body', auto_arm=True)

# Ждем 5 секунд
rospy.sleep(5)

# Полет на координаты x=3, y=2, z=1 площадки с углом по рысканью 3.14 радиан со скоростью 0.5 метров в секунду
navigate(x=3, y=2, z=1, yaw=3.14, speed=0.5, frame_id='aruco_map')

# Ждем 5 секунд
rospy.sleep(5)

# Посадка
land()

Пример взлета на высоту 1 метр из командной строки:

rosservice call /navigate "{x: 0.0, y: 0.0, z: 2, yaw: 0.0, yaw_rate: 0.0, speed: 0.5, frame_id: 'body', auto_arm: true}"

Для более подробной информации и описания других команд смотрите API simple_offboard и примеры кода.

Работа с светодиодной лентой

В используемой версии Клевера LED-лента подключена напрямую к Raspberry Pi. При включении всех светодиодов ленты на полную мощность возможно повреждение цепей питания микрокомпьютера.

Сигнальный провод ленты подключен к GPIO-пину 18.

Про работу с LED-лентой можно прочитать в соответствующей статье.

Работа с LED-лентой через ROS

В образ Клевера для Олимпиады НТИ включена нода ROS, работающая со светодиодной подсветкой. С её помощью можно управлять светодиодами, не запуская свою программу из-под sudo. По умолчанию эта нода выключена, но её можно включить, если в файле /home/pi/catkin_ws/src/ros_ws281x/launch/clever4.launch изменить строку

    <arg name="enable" default="false"/>

на

    <arg name="enable" default="true"/>

и перезапустить службу rosled:

sudo systemctl restart rosled

Пример работы со светодиодной лентой:

import rospy
# Загружаем из ноды LED-ленты описание сервиса SetLeds...
from ros_ws281x.srv import SetLeds
# ...и сообщений LEDState и LEDStateArray. Сообщение LEDState
# содержит номер светодиода и его цвет, LEDStateArray - массив
# сообщений LEDState
from ros_ws281x.msg import LEDState, LEDStateArray
# Для задания цвета используется стандартное сообщение ColorRGBA
from std_msgs.msg import ColorRGBA

# Количество светодиодов в ленте
NUM_LEDS = 60

# Прокси к сервису установки состояния светодиодов
set_leds = rospy.ServiceProxy("/led/set_leds", SetLeds, persistent=True)

# Вспомогательная функция заполнения всей ленты указанным цветом.
# red, green, blue - интенсивность красного, зелёного, синего цвета
# (задаётся числом от 0 до 255)
def fill_strip(red, green, blue):
    # Создаём сообщение для setLeds
    led_msg = LEDStateArray()
    led_msg.leds = []
    # Для каждого светодиода указываем его новое состояние
    for i in range(NUM_LEDS):
        led = LEDState(i, ColorRGBA(red, green, blue, 0))
        # Записываем состояние светодиода в сообщение
        led_msg.leds.append(led)
    # Вызываем сервис. После этого вся лента должна стать указанного цвета
    set_leds(led_msg)


# Заполняем ленту разными цветами
fill_strip(255, 0, 0)
rospy.sleep(2.0)
fill_strip(0, 255, 0)
rospy.sleep(2.0)
fill_strip(0, 0, 255)
rospy.sleep(2.0)
# Выключение ленты
fill_strip(0, 0, 0)