На фото представлены устройства, использованные для прототипирования. Как видно, за основу взята платформа Intel Edison, так как она поддерживает многие архитектуры, в том числе MIPS и ARM.
Всем привет. В этой статье я хотел бы поделиться опытом решения одной интересной проблемы, связанной с синхронизацией данных между IoT-устройствами и облачным приложением. Сначала я расскажу об основной идее и целях моего проекта, а затем подробно опишу его техническую сторону и реализацию: речь пойдет об ОС Contiki, базах данных, протоколах и подобных аспектах. В заключение я кратко перечислю технологии, использованные при построении системы.
Вкратце о проекте
Для начала давайте поговорим об основной идее проекта. Ниже схематично изображен принцип работы готовой системы:
Есть пользователь, который через облачный сервис или напрямую (по Wi-Fi) подключается к IoT- устройству. Также где-то в Интернете имеется облачный сервер приложения. Облаком может служить что угодно: скажем, инстанс AWS или Azure или
В общей сложности, может набраться больше 100 или даже больше 1000 устройств. Моя основная задача заключалась в том, чтобы обеспечить обмен данными между облаком и этими IoT-устройствами. Прежде чем двигаться дальше, стоит упомянуть, какие требования предъявлялись к системе:
- Она должна синхронизировать данные между IoT-устройствами.
- Она должна собирать данные с IoT-устройств.
- Она должна синхронизировать данные между IoT-устройствами и облаком.
Техническая реализация
Здесь все довольно просто: пользователь подключается к серверу приложения по HTTP(S), WebSocket или подобному протоколу. Небольшая задачка для читателей: как вы думаете, что можно использовать для соединения между сервером приложения и IoT-устройством?
Если вы подумали про MQTT, вы однозначно правы! Равно как и те, кто выбрал HTTP(S). На самом деле подойдет любой протокол — выбирайте на свой вкус! Мой же выбор пал на — барабанная дробь — асинхронную репликацию! Я имею в виду обычную для баз данных репликацию.
Вы можете спросить, зачем мне репликация. Ответ прост: репликация используется для синхронизации данных, поэтому я могу повсюду — включая облако и IoT-устройства — поддерживать одну версию базы данных. Однако репликацию довольно сложно реализовать. Хочешь репликацию — заведи базу данных, которая ее поддерживает, потому что — повторюсь — репликация естественно присуща базам данных.
Здесь я бы хотел сказать пару слов о тех базах данных, которые я рассматривал при работе над проектом: SQLite, Redis, MySQL, PostgreSQL и Tarantool.
Я сравнил их характеристики и попробовал запустить несколько штук — за исключением MySQL и PostgreSQL — прямо на IoT-устройстве. Ниже расскажу, что из этого вышло.
SQLite — однозначно хорошее решение для хранения данных непосредственно на IoT-устройстве, но у нее нет репликации, и она не поддерживает параллельный доступ из разных процессов.
Redis не поддерживает master-master репликацию и поэтому не может решить мою проблему, так как мне необходима двусторонняя репликация.
MySQL и PostgreSQL слишком тяжеловесны для IoT-устройства, так что я даже не пытался их устанавливать. Но если вы все-таки решите это сделать, смело делитесь своим опытом в комментариях.
Последней в моем списке шла база данных Tarantool. Сразу скажу, что я являюсь коммитером в проект Tarantool, поэтому хорошо знаю сам проект и людей, которые его разрабатывают. К тому же, в Tarantool есть master-master репликация. В общем, для меня это был определенно лучший вариант. Вы же можете использовать в своем проекте другую базу данных. Основная идея, которую я пытаюсь донести, в том, что IoT-устройства могут использовать базы данных с master-master репликацией для обмена данными.
До настоящего момента я лишь поверхностно знакомил вас с проектом. Теперь давайте немного погрузимся в его технические аспекты.
Начну с проблем, с которыми я столкнулся при использовании Tarantool. Во-первых, Tarantool не запускалась на архитектуре ARMv7. Во-вторых, Tarantool не запускалась в 32-битном окружении, что только усугубляло ситуацию. В итоге я смог решить эти проблемы. Ниже приведу правила разработки, которые мне в этом помогли.
- Используйте toolchain-файлы для CMake. В противном случае вы, так же как и я, потратите много времени на исправление CMake-файлов.
- Не используйте беззнаковый тип и другие типы, для которых не указан размер. В libc для этого есть специальные типы, такие как uint32_t. Иначе можно получить неопределенное поведение. Это правило применимо только к C/C++.
- Портируйте ваши автотесты.
Ожидается, что ваши автотесты можно запустить на IoT-устройстве. Если это не так, есть риск убить много времени на отладку.
Итак, у меня есть работающая база данных с master-master репликацией. Замечательно! Следующий шаг — соединить устройства, на которых эта база данных установлена, по 6LoWPAN. Напомню, у меня есть сеть из множества IoT-устройств, соединенных друг с другом по 6LoWPAN, с которых мне необходимо собрать все телеметрические данные.
Краткая схема работы готовой системы
Устройства с сенсорами передают телеметрические данные посредством радиоволн. Этот стандарт называется 6LoWPAN (IPv6 поверх маломощных беспроводных персональных сетей). Замечу, что я не использовал в проекте LoRaWAN. Возможно, я найду применение этой технологии в будущем, но в этой статье я сосредоточусь на 6LoWPAN. Итак, для сбора телеметрических данных я буду использовать шлюз, являющийся важной частью системы. Шлюз — это MIPS-устройство (MIPS — это семейство процессоров) с WAN-антенной для сбора данных, передаваемых посредством радиоволн. Кроме этого, на шлюзе установлено приложение 6LBR, конвертирующее полученные данные в IPv6-пакеты.
Приложение 6LBR
Изображение выше иллюстрирует принцип работы 6LBR. Шлюз с установленным на него 6LBR служит конвертером между беспроводной сенсорной сетью и любой другой. На картинке изображена конвертация из беспроводной сенсорной сети в IP-сеть лишь потому, что так 6LBR работает по умолчанию. Немного позже я объясню, как изменить это поведение.
Более подробную информацию можно найти на странице 6LBR на GitHub.
Вы можете спросить, что же мне дает использование 6LBR. Во-первых, я получаю стек IP, так что я могу использовать функционал стеков TCP и UDP в моих приложениях 6LBR. Во-вторых, я могу использовать любое устройство ввода-вывода с 6LBR. Скажем, можно записать сырые данные прямо в bash. =) К сожалению, 6LBR не пишет напрямую в MQTT. MQTT-брокеры ничего не знают о сырых данных, и с этим приходится мириться.
Зачем же мне понадобилась прямая запись в MQTT-брокер? Ответ прост: дело в legacy-коде.
Здесь я бы хотел сказать пару слов о приложениях 6LBR. В общем случае приложение 6LBR — это написанный на С код с API, позволяющим использовать стек IP и делать некоторые другие вещи. Разработка такого приложения сопряжена как минимум с двумя трудностями: сложная модель потоков и сложная модель памяти. Поэтому запаситесь терпением и приготовьтесь к частым аварийным завершениям вашей программы. Ниже приведен небольшой кусок разработанного мной приложения 6LBR (заранее прошу прощения: могу выложить только картинку с нарочно запутанным кодом, потому что исходники закрыты):
Обратите внимание на одну интересную вещь — PROCESS_YIELD(). В 6LBR есть кооперативная многозадачность, а это значит, что приложения 6LBR должны возвращать управление в каждой итерации цикла. Код не должен выполняться слишком долго.
Итак, давайте еще раз посмотрим, на какой стадии находится наш проект. С помощью шлюза и установленного на него приложения 6LBR я создал mesh network для чтения и записи данных внутри нее. Мне также удалось обернуть IP-пакеты в MQTT-сообщения, каждое из которых содержит информацию об устройстве, включая телеметрические данные. Кроме того, у меня появилась возможность манипулировать устройствами ввода-вывода: скажем, я могу записывать MQTT-сообщения на UART. Но затем я столкнулся с новой проблемой: Tarantool не работает с MQTT-брокерами. Ниже расскажу, как мне удалось обойти это ограничение.
Я решил использовать libmosquitto, написанную на чистом С MQTT-библиотеку, потому что она позволяет довольно просто интегрировать MQTT в мое приложение. Ниже приведен пример использования этой библиотеки для работы с MQTT-сообщениями (ссылка):
static
int
mosq_poll_one_ctx(mosq_t *ctx, int revents, size_t timeout, int max_packets)
{
/** XXX
* I'm confused: socket < 0 means MOSQ_ERR_NO_CONN
*/
int rc = MOSQ_ERR_NO_CONN;
int fd = mosquitto_socket(ctx->mosq);
if (fd >= 0) {
/** Wait until event
*/
revents = coio_wait(fd, revents, timeout);
if (revents != 0) {
if (revents & COIO_READ)
rc = mosquitto_loop_read(ctx->mosq, max_packets);
if (revents & COIO_WRITE)
rc = mosquitto_loop_write(ctx->mosq, max_packets);
}
/**
* mosquitto_loop_miss
* This function deals with handling PINGs and checking
* whether messages need to be retried,
* so should be called fairly _frequently_(!).
* */
if (ctx->next_misc_timeout < fiber_time64()) {
rc = mosquitto_loop_misc(ctx->mosq);
ctx->next_misc_timeout = fiber_time64() + 1200;
}
}
return rc;
}
Я могу взять ссылку на дескриптор сокета и использовать собственный событийный цикл для обработки некоторых событий. И это здорово! Хотел бы обратить ваше внимание на то, что в Tarantool, так же как и в 6LBR, есть кооперативная многозадачность. Для возвращения управления Tarantool использует coio_wait()
.
Ах да, забыл упомянуть, что Tarantool — это еще и сервер приложений на языке Lua. Сюрприз! Поэтому я портировал libmosquitto на Lua. Ниже привожу кусок кода, в котором вызывается функция, которую вы уже видели в предыдущем примере:
__poll_forever = function(self)
local mq = self.mqtt
while true do
self.connected, _ = mq:poll_one()
if not self.connected then
if self.auto_reconect then
self:__try_reconnect()
else
log.error(
"mqtt: the client is not currently connected, error %s", emsg)
end
end
fiber.sleep(self.POLL_INTERVAL)
end
end,
Я также портировал все функции из API libmosquitto. Посмотреть на результат можно здесь. По ссылке дан пример использования. Все что нужно сделать для сбора данных со всех устройств внутри mesh network — это вызвать функцию subscribe()
из определенного места и опубликовать метод get()
!
Заключение
Давайте посмотрим на то, что у нас получилось:
Соединение с сервером приложения установлено посредством предоставляемой Tarantool master-master репликации. Из этого вытекают два полезных свойства:
- Если сервер приложения изменяет какие-либо данные, эти обновленные данные доставляются на все IoT-устройства в сети.
- Если IoT-устройство изменяет какие-либо данные, эти обновленные данные доставляются на сервер приложения.
Именно эти свойства и являются решением моих проблем.
Я также могу соединить мои IoT-устройства посредством master-master репликации. Таким образом устройства и облако объединяются в кластер, который можно использовать для синхронизации всех данных. Все IoT-устройства и облако синхронизированы большую часть времени, за исключением случаев, когда между ними пропадает соединение. Как только соединение будет восстановлено, все данные снова синхронизируются. Просто замечательно!
Шлюз с установленным на него приложением 6LBR позволяет обмениваться данными между моими IoT-устройствами и другими IoT-устройствами. Он оборачивает каждое сообщение в MQTT-сообщение и передает его в канал UART.
IoT-устройство #N с установленным на него MQTT-брокером считывает эти сообщения из канала UART. MQTT-брокер перенаправляет сообщения в Tarantool по MQTT-соединению. Tarantool считывает их, затем для каждого сообщения сервер приложений Tarantool выполняет некоторый код.
IoT-устройство #N соединено со всеми остальными устройствами посредством предоставляемой Tarantool master-master репликации. Такая же репликация используется для соединения всех устройств с облаком.
На этом все! Я решил поставленную задачу и очень надеюсь, что мой опыт поможет вам в ваших собственных проектах в будущем. Подытожу: я использовал Tarantool и как основной фронтенд на моих выделенных серверах, и как сервер приложений. Если вас заинтересовала данная тема, рекомендую взглянуть на другую мою статью на английском языке. Оставайтесь на связи и следите на новостями!
Автор: Mail.Ru Group