Шлюз для UDP между Wi-Fi и LoRa

Делаем шлюз между Wi-Fi и LoRa для UDP

Шлюз для UDP между Wi-Fi и LoRa

У меня была детская мечта — выдать каждому бытовому «безВайФайному» устройству билет в сеть, т. е. IP-адрес и порт. Спустя какое-то время я понял, что не стоит откладывать. Надо взять и сделать.

Техническое задание

Сделать шлюзом M5Stack с установленным Модулем LoRa (рисунок 1). Шлюз будет подключён к Wi-Fi-сети, в которой получить по DHCP локальный IP-адрес. Шлюз с определённой периодичностью будет вещать в «LoRa-эфир» своё имя (аналог SSID для Wi-Fi) и диапазон допустимых портов, чтоб другие устройства знали, что есть такая сеть, к которой можно подключиться и в каком диапазоне можно повыбирать свободный порт. Поскольку это будет прототип, аутентификация не в этот раз. Новые устройства-клиенты будут находить доступную LoRa-сеть и передавать ей выбранный порт. После того, как шлюз получил от нового клиента порт, проверяет свободен ли он, если да, то регистрирует нового клиента и начинает слушать данный порт на собственном асинхронном UDP-сервере. После регистрации клиент получит согласие или отказ на использование заявленного порта. Порядок работы отображён в таблице 1.

Шлюз для UDP между Wi-Fi и LoRa
Рисунок 1

Таблица 1

сторона
направление и данные
сторона
сеанс

[ клиент ] <— сигнал-маяк —
[ шлюз ] 0xA1

[ клиент ] — выбранный порт —>
[ шлюз ] 0xB1

[ клиент ] <— согласие или отказ —
[ шлюз ] 0xA2

[ клиент ] — UPD-пакет —>
[ шлюз ] 0xB2

[ клиент ] <— UPD-пакет —
[ шлюз ] 0xA3

[ сеть ] <— UPD-пакет —
[ шлюз ] 0xC1

Передо мной на столе лежат всякие Модули для M5Stack и скучают. Давайте возьмем LoR’у и развлечёмся с ней. Сама концепция модулей прекрасна! Что тут скажешь? Но, модули у меня первой ревизии, в которых ужаснейшая встроенная антенна, выполненная на гибкой печатной плате и приклеенная к боковой стенке корпуса. Я проводил однажды полевые тесты таких модулей (можно посмотреть на русскоязычном канале на YouTube):

Естественно, пришлось удалить эти рудименты и впаять стандартные спиралевидные антенны, которые идут в комплекте с Ra-01. После такой кастомизации дальность связи заметно улучшилась, но появился побочный момент — антенна имеет диаметр больший, чем допустимое расстояние между модулями. Пришлось отказаться от Завершающего модуля на время проекта.

Первые трудности от сихронной тугости

Казалось бы, бери библиотеку WiFiUdp.h, где всё есть для комфортного существования UDP-сервера, так нет. Библиотека расчитана на поднятие синхронного сервера, который, к глубокому сожалению, не может обслуживать одновременно несколько соединений в одном потоке. Такая библиотека не подходит для текущей задачи. Пришлось выпить много чашек чаю и поискать библиотеку, которая позволит поднять асинхронный UDP-сервер способный поддерживать много соединений одноврменно. Такая библиотека нашлась — AsyncUDP.h. В чём же отличие синхронного сервера от асинхронного? Давайте рассмотрим шесть эпизодов на рисунке 2, в которых тривиально отображены варианты работы сокетов.

Шлюз для UDP между Wi-Fi и LoRa

Рисунок 2

В главных ролях:

Человек в роли Сокета;

Голубь в роли Соединения;

Писмо в роли Данных.

Эпизод А. Синхронный сокет без таймаута

Человек будет стоять до тех пор, пока Голубь не принесёт ему Письмо.

Эпизод B. Синхронный сокет с таймаутом

Человек ждёт обговоренное с Голубем время, и, если тот не прилетит вовремя, то Человек уйдёт.

Эпизод C. Синхронный сокет с многопоточностью

Человек бездельничает и наблюдает, как Голуби сами по себе доставляет Письма.

Эпизод D. Асинхронный сокет (когда нечего ещё получать)

Человек занимается своими любимыми делами, но не забывает про Голубей.

Эпизод E. Асинхронный сокет (когда есть, что получить)

Человек ненадолго отвлёкся от своих дел, чтобы получить письмо от Голубя.

Эпизод F. Асинхронный сокет с многопоточностью

Человек занимается своими делами и наблюдает, как Голуби сами по себе доставляют Письма.

Если Вы были внимательны, то наверняка должны были заметить, что ошейники на Голубях в каждом эпизоде имеют определённый окрас. И это неспроста. В эпизоде A и B на сервере работает лишь один сокет и всё. В эпизоде C работают уже два сокета. В эпизодах D, E и F уже по три сокета. «Почему там два, а тут три?» — спросите Вы. Это условно 2 и 3, на самом деле вместо 2 может быть 20, а вместо трёх 200. Задача показать, что асинхронные сокеты не так калят железо, как синхронные.

Куда сколько чего вмещается?

Давайте рассмотрим таблицу 1, в которой приведена структура UDP-пакета и подумаем, что можно с этим сделать.

Таблица 1. Структура UDP-пакета

Биты
0 — 15
16 — 31

0-31
Порт отправителя (Source port)
Порт получателя (Destination port)

32-63
Длина датаграммы (Length)
Контрольная сумма (Checksum)

64-…
Данные (Data)

Добавим в самое начало данной таблицы ещё одно поле Сеанс (1 Байт). Этого для данного проекта хватит. Исходя из Сеанса устройство будет знать, что делать с пакетом дальше. Теперь придумаем коды для сеансов и запишем их в таблицу 2.

Таблица 2. Пояснение к сеансам

Код
Название
Пояснение

0xA1
Маяк
Шлюз вещает имя LoRa-сети и диапазон допустимых портов с определённой периодичностью. Это необходимо для того, чтоб новые клиенты увидели доступную сеть, а текущие клиенты, когда нет передач могли определять уровень сигнала.

0xB1
Заявка
Когда клиент обнаружил сеть, то он высылает предпочитаемый порт.

0xA2
Согласие или отказ
Если запрошенный клиентом порт свободен, то сервер отвечает согласием, а в противном случае отказом.

0xB2
Up-линк
Когда клиент передаёт UDP-пакет шлюзу.

0xA3
Down-линк
Когда шлюз передаёт UDP-пакет клиенту.

0xC1
Продолжение Up-линка
Когда шлюз высылает UDP-пакет в локальную сеть.

Хорошо. Теперь давайте обговорим состав сеансов в таблице 3.

Таблица 3. Сеансы

Название сеанса
Состав

Маяк
Код сеанса (1 Байт) + Имя LoRa-сети (4 Байт) + Начальный порт (2 Байт) + Конечный порт (2 Байт)

Заявка
Код передачи (1 Байт) + Имя LoRa-сети (4 Байт) + Предпочитаемый порт (2 Байт)

Согласие или отказ
Код передачи (1 Байт) + Имя LoRa-сети (4 Байт) + Предпочитаемый порт (2 Байт) + Результат (1 Байт)

Up-линк
Код передачи (1 Байт) + Имя LoRa-сети (4 Байт) + Удаленный IP-адрес (4 Байт) + Удалённый порт (2 Байт) + Локальный IP-адрес (4 Байт) + Локальный порт (2 Байт) + Размер данных (2 Байт) + Данные

Down-link
Код передачи (1 Байт) + Имя LoRa-сети (4 Байт) + Удаленный IP-адрес (4 Байт) + Удалённый порт (2 Байт) + Локальный IP-адрес (4 Байт) + Локальный порт (2 Байт) + Размер данных (2 Байт) + Данные

Продолжение Up-линка
Удаленный IP-адрес (4 Байт) + Удалённый порт (2 Байт) + Размер данных (2 Байт) + Данные

Написал два клиента для Ардуино и для M5Stack. На видео можете посмотреть как это работает. В пределах квартиры проблем никаких нет, полевые тесты ещё пока не делал.

Исходный код доступен на GitHub по ссылке

Узнать больше о Базовом устройстве M5Stack и купить можно тут

Выбрать беспроводные модули LoRa для Базового устройства можно тут

Буду рад, если данный проект будет Вам полезен. Большое спасибо за уделённое время!

Список литературы и (или) источников:

Источник: habr.com