Construindo um gateway entre Wi-Fi e LoRa para UDP

Eu tinha o sonho de infância de dar a cada dispositivo doméstico "sem Wi-Fi" uma placa de acesso à rede, ou seja, um endereço IP e uma porta. Depois de um tempo, percebi que não adiantava adiar. Eu precisava fazer isso.
Termos de Referência
Configure um M5Stack com o módulo LoRa instalado como gateway (Figura 1). O gateway será conectado a uma rede Wi-Fi, onde receberá um endereço IP local via DHCP. O gateway transmitirá periodicamente seu nome (similar a um SSID para Wi-Fi) e o intervalo de portas disponíveis na rede LoRa, informando a outros dispositivos que existe uma rede à qual podem se conectar e qual intervalo de portas podem selecionar. Como este será um protótipo, a autenticação não será necessária. Novos dispositivos clientes encontrarão uma rede LoRa disponível e transmitirão a porta selecionada para ela. Após o gateway receber uma porta de um novo cliente, ele verifica se está disponível. Em caso afirmativo, ele registra o novo cliente e começa a escutar nessa porta em seu próprio servidor UDP assíncrono. Após o registro, o cliente receberá uma confirmação sobre se deve ou não usar a porta declarada. O procedimento operacional é mostrado na Tabela 1.

Figura 1
Tabela 1
o lado
direção e dados
o lado
sessão
[ cliente ]
<— sinal de sinalização —
[ portal ]
0xA1
[ cliente ]
— porta selecionada —>
[ portal ]
0xB1
[ cliente ]
<— consentimento ou recusa —
[ portal ]
0xA2
[ cliente ]
— Pacote UPD —>
[ portal ]
0xB2
[ cliente ]
<— Pacote UPD —
[ portal ]
0xA3
[ líquido ]
<— Pacote UPD —
[ portal ]
0xC1
Tenho vários módulos M5Stack espalhados pela minha mesa, entediados. Vamos pegar um pouco de LoR e nos divertir com eles. O conceito dos módulos é genial! O que posso dizer? Mas meus módulos são da primeira revisão e têm uma antena embutida terrível, feita em uma placa de circuito impresso flexível e colada na lateral da caixa. Uma vez testei esses módulos em campo (você pode assistir no canal do YouTube em russo):

Naturalmente, tive que remover esses vestígios e soldar as antenas helicoidais padrão que acompanham o Ra-01. Após essa customização, o alcance da comunicação melhorou significativamente, mas surgiu um efeito colateral: o diâmetro da antena ficou maior que a distância permitida entre os módulos. Tive que abandonar o Módulo Final pelo resto do projeto.
As primeiras dificuldades decorrentes da rigidez síncrona
Ao que parece, pegue a biblioteca. WiFiUdp.h, que possui tudo o que um servidor UDP precisa para funcionar sem problemas, mas não. A biblioteca foi projetada para executar um servidor síncrono, que, infelizmente, não consegue lidar com várias conexões simultâneas em uma única thread. Tal biblioteca não é adequada para a tarefa em questão. Tive que tomar muitas xícaras de chá e procurar por uma biblioteca que me permitisse executar um servidor UDP assíncrono capaz de suportar múltiplas conexões simultaneamente. Encontrei essa biblioteca— AsyncUDP.hQual a diferença entre um servidor síncrono e um assíncrono? Vejamos os seis cenários na Figura 2, que ilustram de forma simples as opções de operação de sockets.

Figura 2
Atores:
Humano no papel Soquete;
Pombo no papel Conexões;
Carta no papel Dados.
Episódio A. Soquete síncrono sem tempo limite
O homem permanecerá ali até que a Pomba lhe traga uma Carta.
Episódio B. Socket síncrono com tempo limite
A pessoa espera o horário combinado com a pomba e, se ela não chegar na hora marcada, a pessoa irá embora.
Episódio C. Socket Síncrono com Multithreading
O homem recosta-se e observa enquanto os pombos entregam as cartas por conta própria.
Episódio D. Socket Assíncrono (Quando Não Há Mais Nada Para Receber)
O homem está ocupado com suas coisas favoritas, mas não se esquece das Pombas.
Episódio E. Socket Assíncrono (Quando Há Algo a Receber)
O homem fez uma pausa no trabalho para receber uma carta do jornal Dove.
Episódio F. Socket Assíncrono com Multithreading
Um homem segue com seus afazeres enquanto observa os pombos entregarem as cartas por conta própria.
Se você prestou atenção, provavelmente notou que as coleiras dos Doves têm cores diferentes em cada episódio. E isso não é por acaso. Nos episódios A e B, o servidor tem apenas um socket em execução. No episódio C, dois sockets estão em execução. Nos episódios D, E e F, há três sockets em cada um. "Por que dois ali e três aqui?", você pode perguntar. Estes são 2 e 3, mas na realidade, 2 poderia ser 20 e 3 poderia ser 200. O objetivo é mostrar que sockets assíncronos não são tão exigentes quanto os síncronos.
Onde cabe cada item?
Vamos analisar a Tabela 1, que mostra a estrutura de um pacote UDP, e pensar no que podemos fazer com ele.
Tabela 1. Estrutura do pacote UDP
Bits
0 - 15
16 - 31
0-31
Porta de origem
Porto de destino
32-63
Comprimento do datagrama (Comprimento)
Soma de verificação
64- ...
Dados
Vamos adicionar mais um campo bem no início desta tabela. Sessão (1 byte). Isso é suficiente para este projeto. Com base na sessão, o dispositivo saberá o que fazer com o pacote em seguida. Agora, vamos criar códigos de sessão e registrá-los na Tabela 2.
Tabela 2. Explicação das sessões
código
Nome
Esclarecimento
0xA1
Farol
O gateway transmite periodicamente o nome da rede LoRa e o intervalo de portas permitidas. Isso é necessário para que novos clientes possam ver a rede disponível e os clientes existentes possam determinar a intensidade do sinal quando não houver transmissão.
0xB1
Aplicação
Quando o cliente descobre uma rede, ele envia a porta de sua preferência.
0xA2
Consentimento ou recusa
Se a porta solicitada pelo cliente estiver livre, o servidor responde com consentimento; caso contrário, com recusa.
0xB2
Link ascendente
Quando um cliente transmite um pacote UDP para o gateway.
0xA3
Link de download
Quando o gateway transmite um pacote UDP para o cliente.
0xC1
Continuação do enlace ascendente
Quando o gateway envia um pacote UDP para a rede local.
Certo. Agora vamos discutir a composição da sessão na Tabela 3.
Tabela 3. Sessões
Nome da sessão
Estrutura
Farol
Código de sessão (1 byte) + Nome da rede LoRa (4 bytes) + Porta inicial (2 bytes) + Porta final (2 bytes)
Aplicação
Código de transmissão (1 byte) + Nome da rede LoRa (4 bytes) + Porta preferencial (2 bytes)
Consentimento ou recusa
Código de transmissão (1 byte) + Nome da rede LoRa (4 bytes) + Porta preferencial (2 bytes) + Resultado (1 byte)
Link ascendente
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
Link de download
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 enlace ascendente
Endereço IP remoto (4 bytes) + Porta remota (2 bytes) + Tamanho dos dados (2 bytes) + Dados
Eu escrevi dois clientes para Arduino e M5Stack. Você pode ver como funciona. Não há problemas dentro do apartamento, mas ainda não fiz nenhum teste de campo.
O código-fonte está disponível no GitHub em
Você pode saber mais sobre o dispositivo básico M5Stack e comprá-lo aqui.
Você pode selecionar módulos LoRa sem fio para o dispositivo base.
Ficarei muito grato se este projeto lhe for útil. Muito obrigado pelo seu tempo!
Lista de referências e/ou fontes:
Fonte: habr.com
