Criamos um gateway entre Wi-Fi e LoRa para UDP

Eu tinha um sonho de infância — dar a cada dispositivo doméstico "sem Wi-Fi" um ingresso na rede, ou seja, um endereço IP e uma porta. Depois de algum tempo, percebi que não devia adiar. Precisava pegar e fazer.
Especificação técnica
Fazer um gateway com M5Stack equipado com o Módulo LoRa (Figura 1). O gateway será conectado a uma rede Wi-Fi, onde obterá um endereço IP local via DHCP. Com certa periodicidade, o gateway transmitirá em "LoRa-ether" seu nome (análogo ao SSID para Wi-Fi) e o intervalo de portas permitidas, para que outros dispositivos saibam que existe uma rede à qual podem se conectar e em qual intervalo podem escolher uma porta livre. Como este será um protótipo, a autenticação não será feita desta vez. Novos dispositivos-clientes encontrarão a rede LoRa disponível e enviarão a porta escolhida. Após o gateway receber a porta do novo cliente, ele verifica se está livre; se sim, registra o novo cliente e começa a ouvir essa porta em seu próprio servidor UDP assíncrono. Após o registro, o cliente receberá aprovação ou reprovação para o uso da porta solicitada. O funcionamento está descrito na Tabela 1.

Figura 1
Tabela 1
lado
direção e dados
lado
sessão
[ cliente ]
<— sinal-bóia —
[ gateway ]
0xA1
[ cliente ]
— porta escolhida —>
[ gateway ]
0xB1
[ cliente ]
<— aprovação ou reprovação —
[ gateway ]
0xA2
[ cliente ]
— pacote UPD —>
[ gateway ]
0xB2
[ cliente ]
<— pacote UPD —
[ gateway ]
0xA3
[ rede ]
<— pacote UPD —
[ gateway ]
0xC1
Na minha mesa estão diversos módulos para M5Stack entediados. Vamos pegar a LoRa e nos divertir com ela. O conceito dos módulos é ótimo! O que mais posso dizer? Mas, os módulos que tenho são da primeira versão, com uma antena interna horrível, feita de uma placa de circuito flexível e colada na parede lateral do case. Uma vez fiz testes em campo com esses módulos (pode-se ver no canal em russo no YouTube):

Naturalmente, tive que remover esses resíduos e soldar antenas espirais padrão, que vêm com o Ra-01. Após essa customização, a distância de comunicação melhorou significativamente, mas surgiu um problema colateral — a antena tem um diâmetro maior que a distância permitida entre os módulos. Tive que desistir do Módulo Final por um tempo durante o projeto.
As primeiras dificuldades da rigidez síncrona
Aparentemente, basta pegar a biblioteca WiFiUdp.h, onde está tudo para a operação confortável de um servidor UDP, mas não. A biblioteca é projetada para levantar um servidor síncrono, que, para nosso grande desgosto, não pode atender várias conexões ao mesmo tempo em um único fluxo. Essa biblioteca não se adequa à tarefa atual. Foi preciso beber muitas xícaras de chá e procurar uma biblioteca que permita levantar um servidor UDP assíncrono capaz de suportar múltiplas conexões simultaneamente. Essa biblioteca foi encontrada — AsyncUDP.h. Qual é a diferença entre um servidor síncrono e um assíncrono? Vamos considerar seis episódios na Figura 2, que ilustram trivialmente as opções de operação dos sockets.

Figura 2
Nos papéis principais:
Pessoa no papel de Socket;
Pombo no papel de Conexão;
Carta no papel de Dados.
Episódio A. Socket Síncrono sem Timeout
A pessoa ficará parada até que o pombo traga a carta.
Episódio B. Socket Síncrono com Timeout
A pessoa aguarda o tempo combinado com o pombo e, se ele não chegar a tempo, a pessoa irá embora.
Episódio C. Socket Síncrono com Multithreading
A pessoa está ociosa e observa como os pombos entregam as cartas por conta própria.
Episódio D. Socket Assíncrono (quando não há nada a receber)
A pessoa se dedica às suas atividades favoritas, mas não se esquece dos pombos.
Episódio E. Socket Assíncrono (quando há algo a receber)
A pessoa se distrai brevemente de suas atividades para receber uma carta do pombo.
Episódio F. Socket Assíncrono com Multithreading
A pessoa continua em suas tarefas e observa como os pombos entregam as cartas sozinhos.
Se você prestou atenção, deve ter notado que as coleiras nos pombos em cada episódio têm uma cor específica. E isso não é à toa. Nos episódios A e B, apenas um socket está ativo no servidor. No episódio C, já são dois sockets. Nos episódios D, E e F, já são três sockets. 'Por que são dois ali, e três aqui?' — você deve estar se perguntando. Isso é condicionalmente 2 e 3; na verdade, em vez de 2 pode ser 20, e em vez de três, 200. O objetivo é mostrar que os sockets assíncronos não sobrecarregam tanto o hardware quanto os síncronos.
Quanto cabe onde?
Vamos considerar a Tabela 1, que apresenta a estrutura de um pacote UDP e pensar no que pode ser feito com isso.
Tabela 1. Estrutura do Pacote UDP
Bits
0 — 15
16 — 31
0-31
Porta do remetente (Source port)
Porta do destinatário (Destination port)
32-63
Tamanho do datagrama (Length)
Checksum
64-…
Dados (Data)
Vamos adicionar um campo no início desta tabela Sessão (1 Byte). Isso é suficiente para este projeto. Com base na Sessão, o dispositivo saberá como lidar com o pacote a seguir. Agora vamos criar códigos para as sessões e registrá-los na tabela 2.
Tabela 2. Explicação das sessões
Código
Nome
Explicação
0xA1
Farol
O gateway transmite o nome da rede LoRa e a faixa de portas permitidas com uma certa periodicidade. Isso é necessário para que novos clientes vejam a rede disponível e para que clientes atuais possam determinar o nível de sinal quando não há transmissões.
0xB1
Solicitação
Quando o cliente detecta a rede, ele envia a porta preferida.
0xA2
Consentimento ou recusa
Se a porta solicitada pelo cliente estiver livre, o servidor responde com consentimento; caso contrário, com recusa.
0xB2
Up-link
Quando o cliente envia um pacote UDP para o gateway.
0xA3
Down-link
Quando o gateway envia um pacote UDP para o cliente.
0xC1
Continuação do Up-link
Quando o gateway envia um pacote UDP para a rede local.
Ótimo. Agora vamos discutir a composição das sessões na tabela 3.
Tabela 3. Sessões
Nome da sessão
Composição
Farol
Código da sessão (1 Byte) + Nome da rede LoRa (4 Bytes) + Porta inicial (2 Bytes) + Porta final (2 Bytes)
Solicitação
Código de transmissão (1 Byte) + Nome da rede LoRa (4 Bytes) + Porta preferida (2 Bytes)
Consentimento ou recusa
Código de transmissão (1 Byte) + Nome da rede LoRa (4 Bytes) + Porta preferida (2 Bytes) + Resultado (1 Byte)
Up-link
Código de transmissão (1 Byte) + Nome da rede LoRa (4 Bytes) + Endereço IP remoto (4 Bytes) + Porta remota (2 Bytes) + Endereço IP local (4 Bytes) + Porta local (2 Bytes) + Tamanho dos dados (2 Bytes) + Dados
Down-link
Código de transmissão (1 Byte) + Nome da rede LoRa (4 Bytes) + Endereço IP remoto (4 Bytes) + Porta remota (2 Bytes) + Endereço IP local (4 Bytes) + Porta local (2 Bytes) + Tamanho dos dados (2 Bytes) + Dados
Continuação do Up-link
Endereço IP remoto (4 Bytes) + Porta remota (2 Bytes) + Tamanho dos dados (2 Bytes) + Dados
Escrevi dois clientes para Arduino e para M5Stack. Você pode ver como isso funciona. Dentro do apartamento não há problemas, ainda não fiz testes de campo.
O código-fonte está disponível no GitHub em
Saiba mais sobre o dispositivo básico M5Stack e compre
Escolher módulos sem fio LoRa para o dispositivo básico pode ser feito
Ficarei feliz se este projeto for útil para você. Muito obrigado pelo seu tempo!
Lista de referências e/ou fontes:
Fonte: habr.com
