Собираем простейшую ZigBee-сеть, программируем под Mbed, общаемся через MQTT
Собираем простейшую ZigBee-сеть, программируем под MBed, общаемся через MQTT
Эта статья — большой учебный практикум начального уровня по использованию XBee-модуля в связке с микроконтроллером, имеющим на борту Mbed OS. Стек XBee реализует ZigBee-стандарт и подходит для задач «Умного дома» и несложной офисной автоматизации, он отличается низким энергопотреблением, простотой использования, компактностью самих модулей, а также, что самое интересное, возможностью создания самоконфигурируемых mesh-сетей. Вы увидите из практикума, что это действительно так — мы посмотрим на структуру такой сети через удобную утилиту-визуализатор.
Предполагается, что вы уже знаете, что такое ZigBee и для чего он нужен. Теперь вам хочется подключить свой первый XBee-модуль и решить с его помощью свои задачи, не вдаваясь в программирование самого модуля, а лишь используя его как интерфейс связи. В конце будем все данные пересылать через самодельный MQTT-шлюз куда угодно, хоть на локальный сервер, хоть в Интернет. Мы решили показать всё на примере Mbed как самой простой и доступной новичкам RTOS. Вы убедитесь, что всё работает «из коробки», и можете сразу начать делать свой проект, даже если до этого вы имели дело только с Ардуино.
Руководство будет состоять из следующих частей:
- Подключение и конфигурация XBee-модуля
- Объединяем модули в сеть
- Настраиваем модуль, не вынимая его из шилда
- Получение данных
- Отправка данных
- Делаем простой MQTT-шлюз
Но вначале список компонентов: что желательно иметь, чтобы сделать всё вышеописанное.
Необходимые компоненты
- Две Mbed-совместимые платы. Рекомендуем учебную плату STM32 Nucleo как относительно недорогую и популярную в образовательных целях. Процесс программирования там упрощен максимально: собираете программу в онлайновой бесплатной IDE, скачиваете собранную прошивку и «бросаете» ее на плату, которая появится в системе как флешка. Какую конкретно модель брать, не принципиально, но берите не самую старую, обращайте внимание на объём памяти. Например, F401RE — даем просто для определенности, чтобы вы не запутались в их разнообразии и поначалу непонятных буквенных кодах STM-процессоров.
- Три XBee-модуля. Подойдет любой официальный модуль от Digi. Брать «неродные» модули, то есть, к примеру, MBee, не рекомендуется, хоть они и дешевле, так как они будут несовместимы, и потеряется вся фишка общего стандарта. Наличие/отсутствие антенны или разъёма для нее для нас неважно: модули прекрасно соединяются друг с другом, даже будучи разными в этом смысле. Также неважно наличие/отсутствие модификатора Pro: это серия с повышенной дальностью приемопередачи, для задачи просто попробовать это некритично, переплачивать за Pro не имеет смысла.
- Два шилда XBee Shield V2 (от SeeedStudio). Мы рекомендуем именно этот шилд из-за возможности конфигурации при помощи джамперов, к каким именно выводам микроконтроллера будут подключаться RX и TX XBee-модуля.
- Один USB-UART преобразователь с разъёмом для XBee. Он нужен для первоначального конфигурирования. Модули XBee сами по себе не снабжены интерфейсом USB (он им не к чему). USB нужен только и исключительно для коммуникации модуля с компьютером, а в устройстве он может работать по гораздо более простому UART.
Шаг разъёмов модулей XBee составляет 2.0 мм — для электроники нестандартный, в метрической системе (обычно мы привыкли видеть шаг 2.54 по американским стандартам). Поэтому, к сожалению, такие модули не вставляются в макетные платы и к ним всегда нужен переходник.
Здесь переходник подойдет любой, мы взяли вот этот от Waveshare:
Самое простое, что можно сделать с модулем — это подключить его к компьютеру напрямую по USB. Мы сразу получим доступ к его конфигурационным параметрам.
Установка XCTU
Для работы с XBee существует официальная программа XCTU. Скачайте ее и установите. В документации пишут, что в Ubuntu нужно добавить пользователя в группу dialout
для работы с портами не из-под суперпользователя — сделайте это, если ещё не сделали:
sudo usermod -a -G dialout
Скачайте zip-архив по ссылке, в нем будет файл .run
. Его нужно сделать исполняемым (через chmod +x имя_файла
в консоли или: правая кнопка мыши — Properties — Permissions) и запустить ./40002881_V.run
.
Важно запускать установку не из-под рута (без sudo
), иначе потом будут проблемы.
Установка выглядит примерно так:
Программа XCTU
После установки можно запустить программу запуском файла app
в той директории, куда вы установили программу (по умолчанию — ~/Digi/XCTU-NG
).
Внешний вид будет такой:
В этой программе вы можете добавить имеющийся у вас модуль, подключенный к USB-порту через переходник. Нажмите на кнопку Discover с лупой. Выскочит окошко с предложением выбрать порт — как вы видите, программа корректно определила наличие в системе порта /dev/ttyUSB0
, это и есть наш USB-UART адаптер.
Окошко предлагает выставить галочки для поиска. Конечно, есть соблазн поставить галочки сразу все, чтобы уж наверняка найти свой модуль. Но тогда поиск будет идти очень долго. На практике, имеет смысл оставить галочки по умолчанию, а варианты скорости передачи данных выбрать самые распространенные, как на картинке ниже. Обычно у новых модулей по умолчанию стоит 9600, и для учебных целей этого более чем достаточно, скорость нам тут некритична.
В итоге, если всё успешно, вам будет предложено выбрать найденный модуль:
Был замечен небольшой баг: иногда модуль с нестандартной скоростью не находился, помогала ручная перезагрузка модуля кнопкой Reset во время поиска и сброс его baud rate на стандартный (9600).
Меняем параметры модуля
Дальше занимаемся конфигурацией модуля. Выпадет куча параметров, большинство из которых поначалу непонятны. К счастью, для быстрого старта нужно будет поменять только несколько из них.
Идентификатор сети — PAN ID. Общий ключ сети, он должен быть одинаковым у всех устройств, чтобы они самостоятельно объединились в сеть. Поставьте любое число, в нашем случае мы сделали 42.
CE — Coordinator Enabled. Если здесь 1, то данный модуль выполняет роль координатора (Coordinator). Если стоит 0, то модуль выполняет роль роутера (Router) — фактически он просто обеспечивает прохождение пакетов по сети через себя. Большинство узлов сети обычно именно роутеры. Нам в нашей сети понадобится один координатор и два роутера, так что поставьте здесь единичку.
Бывает ещё роль «Конечное устройство» (End device) — это модуль, который не выполняет работ по передаче пакетов, а только общается с другими роутерами и выполняет свой полезный функционал. Обычно он просто находится в спящем режиме и через заданные промежутки времени просыпается и узнает, не пришло ли сообщений. Такой модуль может питаться от батарейки, в то время как роутер и координатор всегда должны находиться «онлайн» и как следствие нуждаются в постоянном питании для поддержания работоспособности всей сети. Мы сейчас не будем такой пример рассматривать, поскольку интереснее всем назначить роль роутеров и понаблюдать за тем, как сеть будет менять свою конфигурацию автоматически.
AP — API Enable. Режим работы. XBee-модули могут работать в двух режимах: AT или API. AT-режим проще для новичков, он представляет собой прозрачную замену последовательного порта и работу с модулем через AT-команды. Однако API-режим гораздо богаче по функционалу, он позволяет удаленно конфигурировать модули, содержит адрес отправителя, возвращает статус доставки пакета, и многое другое. Кроме того, библиотеки для взаимодействия с XBee предполагают использование устройства именно в API-режиме. Поэтому сразу замените на API-режим, это делается установкой единицы в соответствующем поле.
Название модуля — Node Identifier. Удобный параметр, чтобы дать вашему модулю человекочитаемое имя. Давайте дадим ему такое название — Coordinator.
После этого можно прошить настройки в модуль нажатием на кнопку с карандашом:
Сейчас мы посмотрим, какая сеть получится, если сконфигурировать три модуля. В программе XCTU есть достаточно удобный визуализатор сети, мы наглядно увидим топологию.
Сконфигурируем все три модуля поочередно со следующими параметрами:
- PAN ID — общее число, например 42
- AP (API Enable) — поставьте 1 (работа в API-режиме)
- NI (Node Identifier) — дайте модулям понятные имена (Coordinator, Lamp и Switch, если мы делаем, к примеру, модель системы Умного дома).
- CE (Coordinator Enable) — одному из модулей поставьте значение 1, он будет координатором.
После чего подайте питание на все модули. Расположите их так: координатор будет находиться в USB-UART преобразователе, а два других (роутеры) — будут находиться на платах XBee Shield поверх Nucleo.
Если всё сделали внимательно, то произойдет прекрасное. Модули автоматически соединятся в сеть. Вы сможете связываться с роутерами удаленно, через координатора.
Выглядит это так. Нажмите кнопку «Discover radio nodes in the same network».
Вы увидите, что автоматически обнаружились и добавились два модуля:
И вы можете менять их параметры «на лету»!
Что ещё прекрасно: теперь вы можете увидеть карту сети, если переключитесь вверху справа на Network working mode.
Раздвинув узлы сети мышкой, вы увидите треугольник. Обратите внимание, что трафик к крайнему правому модулю может идти двумя путями. И если вы переместите модули в пространстве, то увидите, что картинка поменялась, и теперь, возможно, другой модуль станет «крайним». В этом и суть самоконфигурируемой сети.
Конечно, интереснее будет работать с XBee-модулем не через компьютер, а управляя им при помощи программы на микроконтроллере. То есть соединив его с платой STM32Nucleo.
Обсудим вопрос, как модуль XBee может общаться с микроконтроллером. И начнем вот с какой небольшой задачи: как конфигурировать модуль, не вынимая его из шилда расширения? Ведь согласитесь, что двигать модуль туда-сюда неудобно, в то же время хочется экспериментировать с параметрами и странно, зачем нужен отдельный модуль USB-UART, ведь по идее такой есть в плате STM32Nucleo.
Решение задачи простое: нам нужно превратить плату Nucleo в мост между XBee-модулем и USB-преобразователем на плате.
Общее описание идеи
У микроконтроллера STM32, который мы используем, на борту есть несколько интерфейсов UART. Каждый такой интерфейс представляет собой канал коммуникации. Один из них подключен к преобразователю USB-UART, чтобы мы могли общаться с компьютером через USB в терминале. Два других пока не используются. К одному из них мы и подключим модуль XBee, у которого тоже есть такой канал коммуникации. Вы можете выбрать какой угодно UART, мы выбрали для определенности UART1.
Расположение выводов можно посмотреть в MBed вверху справа, нажатием кнопки выбора платы, и далее — вкладка Pinout. С непривычки может быть довольно сложно воспринимать эту цветастую картинку. Здесь очень много всего, поскольку у платы много интерфейсов, а еще есть две нумерации выводов: относительно микроконтроллера (PA_5, PA_6 — нумерация выводов), и относительно платы (D13, D12 — надписи на плате Nucleo, те самые цифры возле выводов).
Получится, что на микроконтроллере интерфейс UART1 будет общаться с XBee-модулем, а UART2 — как и раньше, с компьютером. Внутренний код будет перенаправлять UART1 на UART2 и наоборот.
По подключениям это будет выглядеть так:
Делаем «мост» из микроконтроллера
В нашем случае мы можем джамперами на шилде выставить те номера, к которым хотим подключить ZigBee-модуль. TX модуля связи будет заведен на вывод 2 платы (PA_9 на микроконтроллере), а RX — на вывод 8 (он же PA_10).
Выглядеть это будет так:
Код, который мы взяли, называется Serial Bridge. Это «мост» между двумя коммуникационными каналами. Мы загружаем в микроконтроллер код, который пробрасывает всё, что приходит на вход от компьютера через UART2, в UART1, и наоборот. Как будто мы вставили трубу (pipe) между двумя источниками информации (линуксоиды поймут). К UART1 мы подключили XBee-модуль.
Код очень простой, единственное, что в нем нужно поменять — это номера выводов, к которым подключено устройство (device). То есть сделать их PA_9 и PA_10, как указано выше.
#include "mbed.h"
// Make a serial bridge from a serial I/O device on mbed to the PC
Serial pc(USBTX, USBRX); // tx, rx
Serial device(PA_9, PA_10); // tx, rx
// Defaults to 9600 baud on each device - use .baud(baudrate) to change
int main() {
pc.printf("Hello!");
while(1) {
if(pc.readable()) {
device.putc(pc.getc());
}
if(device.readable()) {
pc.putc(device.getc());
}
}
}
Важно, что если вы перепутаете порядок выводов — к примеру, ошибетесь и напишете вместо PA_9, PA_10 — неправильный порядок PA_10, PA_9, то компилятор вам не сообщит об ошибке, при этом программа будет выдавать ошибку в консоли при перезагрузке:
pinmap not found for peripheral
и дальше двигаться не станет, то есть в принципе ничего работать не будет.
После того, как вы загрузите этот код и правильно выставите джамперы на шилде, к XBee-модулю можно будет спокойно подключаться с компьютера, так же, как вы это делали ранее с USB-UART адаптером, не вынимая его из шилда. Он будет исправно обнаруживаться программой XCTU.
Даже если вам не нужен этот функционал, всё равно проверьте работоспособность программы, поскольку в следующем примере мы будем общаться с XBee-модулем со стороны микроконтроллера, и соединение по UART1 должно быть уже налажено (то есть правильно поставлены джамперы и указаны номера выводов в программе).
4. Получение данных
Рассмотрим два простых примера: как на уровне микроконтроллера получать и отправлять данные, пользуясь XBee-модулем как внешним интерфейсом связи.
Есть официальная библиотека от производителей — компании Digi. Она лежит в репозитории Mbed, там есть полезные комментарии по использованию и логике кода.
Вначале изучим, как получать данные — там пример попроще. У нас будет XBee-модуль, подключенный к USB-UART преобразователю, отправлять приветствие плате Nucleo, и она будет печатать это приветствие в консоль.
Откройте проект в этой библиотеке. Как всегда, импортируйте его в онлайн-компилятор как программу.
Фикс библиотеки для S2C-модулей
Имейте в виду, что если у вас модули серии S2C:
то вам вместо стандартной библиотеки нужно использовать фикс: XBeeLib_Fixed. В противном случае, эти программы работать не будут. Она добавляется в проект просто удалением оттуда библиотеки XBeeLib, и импортом в проект XBeeLibFix. Больше ничего менять не нужно.
Итак: импортируйте эту библиотеку в онлайн-компилятор:
Появится окошко импорта проекта. Там нужно выбрать цель — куда импортируем:
В качестве целевого проекта выбираем наш пример XBeeZB_Receive_Data.
После чего произойдет импорт библиотеки в проект, и далее смелым движением удаляем неправильную версию XBeeLib.
Компиляция примера
Итак, вы импортировали пример и заменили в нем библиотеку при необходимости.
Посмотрите код примера, он достаточно простой. В нем определена callback-функция, которая вызывается при получении пакета. Эта функция печатает в консоль содержимое полученного пакета. Так что если мы ей пришлем приветствие, она напечатает его тоже.
Чтобы пример скомпилировался, нужно прописать в примере те выводы, которые у нас отвечают за коммуникацию с XBee-модулем, ведь программе неизвестно, к каким именно выводам мы подключили «железо».
Поэтому идем в файл config.h и в нем строчки:
//#define RADIO_TX NC /* TODO: specify your setup's Serial TX pin connected to the XBee module DIN pin */
//#define RADIO_RX NC /* TODO: specify your setup's Serial RX pin connected to the XBee module DOUT pin */
Раскомментируем, и вместо NC пишем, в соответствии с тем, к каким выводам мы подключили джамперы:
#define RADIO_TX PA_9
#define RADIO_RX PA_10
Аналогичным образом, модифицируем строчки:
//#define DEBUG_TX NC /* TODO: specify your setup's Serial TX for debugging */
//#define DEBUG_RX NC /* TODO: specify your setup's Serial RX for debugging (optional) */
Пишем:
#define DEBUG_TX USBTX
#define DEBUG_RX USBRX
Если при компиляции вы встречаете ошибку, что не получается найти device.h
— просто обновите библиотеку Mbed в дереве проекта (правый клик на ней → Update)
После чего программа успешно скомпилируется, и вы сможете загрузить ее в плату.
Запуск примера
Посмотрев в консоли, что пишет плата Nucleo, вы увидите следующее:
Как нам отправить данные? Самый простой вариант: через консоль в программе XCTU. Выберите в основном меню программы: Tools — Send packets.
Внизу видно окошко с надписью Send packets. Создайте новый пакет нажатием на «плюс» справа. Появится окно создания нового пакета. Выберите там вкладку HEX.
Введите туда такую посылку данных: 7E 00 19 10 01 00 00 00 00 00 00 FF FF FF FE 00 00 48 65 6C 6C 6F 20 58 42 65 65 21 5A
(кнопкой «Send selected packet» справа от списка пакетов)
Увидите результат в консоли слушающего модуля:
Обратите внимание, что он вывел только последнюю часть того набора байтов, который вы отправляли. Это — собственно payload (полезная нагрузка) сообщения. Также обратите внимание, что если вы отправите «просто байты» (любую случайную комбинацию байтов), то получатель их выводить не станет.
Если вы поместите набор байтов: 48 65 6C 6C 6F 20 58 42 65 65 21
в любой HEX-ASCIII конвертер (например, такой), то убедитесь, что это означает «Hello XBee!»
Очень простое задание на самостоятельное выполнение: модифицируйте код примера так, чтобы он выводил текст сообщения в ASCII, а не HEX, и вы бы могли читать этот текст в терминале.
По аналогии с предыдущим примером, рассмотрим теперь отправку данных.
Здесь всё, как и в прошлом примере. Только с той разницей, что теперь мы открываем пример XBeeZB_Send_Data.
Компиляция примера
Важно, что если у вас модуль S2C (на нем это явно написано),
то вы опять подключаете библиотеку с фиксом, иначе ничего работать у вас не будет. Как это сделать, описано в предыдущем примере.
Также, здесь для успешной компиляции нужно указать используемые выводы контроллера, их можно просто скопировать из предыдущего примера.
Смотрим сам код примера. В main
используется несколько методов для отправки данных. Выберем самый простой: отправить данные координатору. Нам не нужно даже прописывать адрес координатора, ведь он и так прописан в сети. Поэтому, не меняя сэмпл, комментируем пока все строчки в конце:
send_data_to_coordinator(xbee);
//send_broadcast_data(xbee);
//send_data_to_remote_node(xbee, remoteDevice);
//send_explicit_data_to_remote_node(xbee, remoteDevice);
И при запуске увидите вот что:
(если не видите — просто перезагрузите плату)
Как убедиться, что данные доходят до координатора? Например, в XCTU есть режим сетевой консоли. Включается кнопкой справа сверху. В этом режиме вы будете видеть все пакеты в сети. Конечно, при этом у вас должно быть установлено соединение по Serial-порту с координатором. И не забудьте нажать кнопку Open (открыть соединение) вверху слева, чтобы она стала зеленой.
Про каждый пакет в сети можно посмотреть подробную информацию, выбрав его в списке слева:
Прокрутив до конца содержание пакета, вы увидите и строчку с текстом «send_data_to_coordinator»:
Вы можете попробовать другие методы отправки данных, но там (к примеру, для отправки на отдельный выбранный узел) надо прописывать адрес узла. Адреса всех модулей вы видите в программе XCTU. Как прописать конкретные адреса, исчерпывающе описано в примере.
6. Делаем MQTT-шлюз
Всё замечательно, но теперь хотелось бы как-то удобно работать с этими данными вне сети XBee, например — в Интернете. Один из вариантов, как это сделать — поставить транслятор из XBee в популярный протокол MQTT. Тогда мы привычным образом сможем подписываться на уведомления о событиях и отправлять команды из пользовательского интерфейса, и находиться эта программа может где угодно (если использовать внешний, а не локальный MQTT-сервер), а не только на нашем компьютере.
Ниже инструкция. Если вкратце, то запущенная на компьютере программа будет обмениваться данными от координатора сети через USB-соединение. Эти данные она будет транслировать в протокол MQTT.
Установка и настройка XBMQ
Программа, которую мы будем использовать в роли MQTT-шлюза, называется XBMQ, она открытая и свободная. Существует в двух версиях:
Будет рассмотрена версия под Java, хотя это не слишком принципиально: программировать мы её всё равно не будем, а только установим и будем пользоваться.
Для работы программы понадобится библиотека RXTX, её можно установить просто из репозитория:
sudo apt-get install librxtx-java
И разумеется, понадобится JDK (Java Development Kit). Он существует в двух версиях — от Oracle и OpenJDK, рекомендуется второе. Скорее всего, OpenJDK уже стоит в вашей системе, если нет — доустановите его. Java нужна максимум 8-я, поскольку javax.xml.bind исключен из Java 11 и нужно выбрать альтернативу с JDK-8 как вариант по умолчанию, или создать конфигурацию для этого случая.
Скачиваем репозиторий программы XBMQ:
git clone https://github.com/angryelectron/xbmq-java
Скачав исходные коды, мы соберем бинарный файл программы. Команда
ant dist
соберет проект под все основные ОС. Итоговая собранная программа будет лежать в папке dist
.
Теперь сконфигурируем эту программу. Открываем файл
dist/xbmq.properties
и видим там:
#
# Xbmq Properties.
#
#port = /dev/ttyUSB0
#baud = 9600
#rootTopic = ab123
#broker = tcp://test.mosquitto.org:1883
#username = user
#password = password
Раскомментируем и меняем на свои параметры. Параметры здесь такие: порт подключения ZigBee-координатора, скорость, корневой топик (в него будут попадать все данные), адрес MQTT-сервера, имя пользователя и пароль (в случае, если сервер их требует).
В данном примере изменился только адрес сервера — на локальный mqtt-сервер (стандартный mosquitto из Linux). Всё остальное было оставлено по умолчанию:
port = /dev/ttyUSB0
baud = 9600
rootTopic = ab123
broker = tcp://127.0.0.1:1883
username = user
password = password
Перед следующим шагом у вас должен быть установлен и запущен локальный MQTT-сервер mosquitto:
sudo apt-get install mosquitto
Запуск XBMQ
И наконец, программу можно запускать. Подключив USB-UART преобразователь со вставленным модулем координатора, запустите программу:
./dist/xbmq.sh
При запуске скрипта увидим в консоли строчку:
INFO - Starting XBMQ gateway ab123/0013A2004154EA46
Последнее — это ровно в точности адрес нашего координатора. Если подписаться на все топики внешним MQTT-клиентом, то сразу же увидите 2 сообщения:
- В топике
ab123/0013A2004154EA46/online
— будет лежать 1, это Last Will (хороший пример того, как эти «особенные» параметры используются в реальной жизни) - В топике
ab123/0013A2004154EA46/log
будет лежать та самая отладочная фраза «Starting XBMQ gateway…», которую вы уже увидели в консоли
А теперь попробуйте послать сообщение с какого-нибудь другого внешнего XBee-модуля — например, возьмите пример с отправкой данных.
В результате, в программе MQTT.fx вы, если подпишетесь на все топики (#), увидите вот что:
Окно программы MQTT.fx
То есть запустив изученный ранее пример с отправкой сообщения координатору, мы увидим этот текст («send_data_to_coordinator») в составе MQTT-пакета. Общий родительский топик у нас тот, который задан в конфигурации программы (ab123, можете поменять его на свой). Дальше идет адрес координатора, затем — адрес модуля, от которого пришло сообщение. И наконец, этот топик называется DataOut, поскольку это исходящие данные.
Разумеется, такую картинку вы увидите и в любом другом MQTT-клиенте, будь то MQTTLens или даже просто mosquitto_sub
в консоли.
Пара финальных замечаний:
- По-хорошему, эта программа должна работать в режиме демона. Для этого есть файл xbmqd, и в README написано, как этим пользоваться.
- Имейте в виду, что, поскольку
xbmq
держит порт, мы не можем запускать эту программу одновременно с XCTU.
Теперь вы можете работать с вашей системой, используя протокол MQTT, и писать сложные, интересные программы!
Татьяна Волкова — Автор учебной программы трека по Интернету вещей «IT Академии Samsung», специалист по программам корпоративной социальной ответственности Исследовательского центра Samsung