A biblioteca Loramesher implementa um protocolo de roteamento de vetor à distância para comunicar mensagens entre os nós da LORA. Para a interação com o Lora Radio Chip, aproveitamos o Radiolib, uma biblioteca de comunicação versátil que suporta diferentes módulos da série Lora.
Neste momento, Loramesher foi testado dentro dos seguintes módulos:
Você pode solicitar que outro módulo seja adicionado à biblioteca abrindo um problema.
Você pode verificar library.json para obter mais detalhes. Basicamente, usamos o Radiolib que implementa a comunicação de baixo nível para os diferentes módulos Lora e Freertos para agendar tarefas de manutenção.
Há, nos arquivos de origem desta primeira implementação, um exemplo para testar as novas funcionalidades. Este exemplo é uma implementação de um contador, enviando uma mensagem de transmissão a cada 10 segundos. Para facilitar o entendimento, removeremos funções adicionais que não são necessárias para fazer com que o microcontrolador trabalhe com a biblioteca Loramesher.
Como prova de conceito, enviaremos um contador numérico sobre Lora. Seu valor será incrementado a cada 10 segundos, e o pacote TE será transmitido. Para começar, precisamos implementar o tipo de dados que usaremos.
Nesse caso, enviaremos apenas um uint32_t , que é o próprio contador.
uint32_t dataCounter = 0;
struct dataPacket {
uint32_t counter = 0;
};
dataPacket* helloPacket = new dataPacket;
Para inicializar a nova implementação, você pode configurar os parâmetros LORA que a biblioteca usará. Se o seu nó precisar receber mensagens no aplicativo, consulte a seção Função de pacotes recebidos.
Você pode configurar parâmetros diferentes para a configuração do LORA. Usando o LoRaMesherConfig você pode configurar os seguintes parâmetros (obrigatórios*):
Aqui está um exemplo de como configurar o Loramesher usando esta configuração:
//Get the LoraMesher instance
LoraMesher& radio = LoraMesher::getInstance();
//Get the default configuration
LoraMesher::LoraMesherConfig config = LoraMesher::LoraMesherConfig();
//Change some parameters to the configuration
//(TTGO T-Beam v1.1 pins)
config.loraCS = 18
config.loraRst = 23
config.loraIrq = 26
config.loraIo1 = 33
config.module = LoraMesher::LoraModules::SX1276_MOD;
//Initialize the LoraMesher. You can specify the LoRa parameters here or later with their respective functions
radio.begin(config);
//After initializing you need to start the radio with
radio.start();
//You can pause and resume at any moment with
radio.standby();
//And then
radio.start();
Esteja ciente das leis locais que se aplicam às frequências de rádio
Se o seu nó precisar receber pacotes de outros nós, você deve seguir as próximas etapas:
A função que recebe uma notificação cada vez que a biblioteca recebe um pacote para o aplicativo se parece com este:
/**
* @brief Function that process the received packets
*
*/
void processReceivedPackets(void*) {
for (;;) {
/* Wait for the notification of processReceivedPackets and enter blocking */
ulTaskNotifyTake(pdPASS, portMAX_DELAY);
//Iterate through all the packets inside the Received User Packets FiFo
while (radio.getReceivedQueueSize() > 0) {
Serial.println("ReceivedUserData_TaskHandle notify received");
Serial.printf("Queue receiveUserData size: %dn", radio.getReceivedQueueSize());
//Get the first element inside the Received User Packets FiFo
AppPacket<dataPacket>* packet = radio.getNextAppPacket<dataPacket>();
//Print the data packet
printDataPacket(packet);
//Delete the packet when used. It is very important to call this function to release the memory of the packet.
radio.deletePacket(packet);
}
}
}
Há algumas coisas importantes que precisamos estar cientes:
void* nos parâmetros.ulTaskNotifyTake(pdPASS,portMAX_DELAY) ou equivalente. Esta função permite à biblioteca notificar a função para processar pacotes pendentes.radio.getReceivedQueueSize() .radio.getNextAppPacket<T>() onde t é o tipo de seus dados.radio.deletePacket(packet) . Ele liberará a memória que foi alocada para o pacote. Se não for executado, pode causar vazamentos de memória e erros de memória. TaskHandle_t receiveLoRaMessage_Handle = NULL;
/**
* @brief Create a Receive Messages Task and add it to the LoRaMesher
*
*/
void createReceiveMessages() {
int res = xTaskCreate(
processReceivedPackets,
"Receive App Task",
4096,
(void*) 1,
2,
&receiveLoRaMessage_Handle);
if (res != pdPASS) {
Serial.printf("Error: Receive App Task creation gave error: %dn", res);
}
}
radio.setReceiveAppDataTaskHandle(receiveLoRaMessage_Handle);
Nesta seção, mostraremos o que há dentro de um AppPacket .
class AppPacket {
uint16_t dst; //Destination address, normally it will be local address or BROADCAST_ADDR
uint16_t src; //Source address
uint32_t payloadSize = 0; //Payload size in bytes
T payload[]; //Payload
size_t getPayloadLength() { return this->payloadSize / sizeof(T); }
};
Funcionalidades a serem usadas depois de obter o pacote com AppPacket<T>* packet = radio.getNextAppPacket<T>() :
packet->getPayloadLength() ele obterá o tamanho da carga útil no número de tradio.deletePacket(packet) Ele lançará a memória alocada para este pacote. Nesta seção, apresentaremos como você pode criar e enviar pacotes. Neste exemplo, usaremos a estrutura de dados AppPacket .
void loop() {
helloPacket->counter = dataCounter++;
//Create packet and send it.
radio.createPacketAndSend(BROADCAST_ADDR, helloPacket, 1);
//Or if you want to send large and reliable payloads you can call this function too.
radio.sendReliable(dstAddr, helloPacket, 1);
//Wait 10 seconds to send the next packet
vTaskDelay(10000 / portTICK_PERIOD_MS);
}
Na figura anterior, podemos ver que estamos usando o Hellopacket, adicionamos o contador e criamos e enviamos o pacote usando o Loramesher.
A parte mais importante deste código é a função que chamamos no radio.createPacketAndSend() :
Ao receber o pacote, precisamos entender o que a fila nos devolverá. Por esse motivo, na próxima subseção, explicaremos como implementar um processamento simples de pacotes.
/**
* @brief Print the counter of the packet
*
* @param data
*/
void printPacket(dataPacket data) {
Serial.printf("Hello Counter received n %dn", data.counter);
}
/**
* @brief Iterate through the payload of the packet and print the counter of the packet
*
* @param packet
*/
void printDataPacket(AppPacket<dataPacket>* packet) {
//Get the payload to iterate through it
dataPacket* dPacket = packet->payload;
size_t payloadLength = packet->getPayloadLength();
for (size_t i = 0; i < payloadLength; i++) {
//Print the packet
printPacket(dPacket[i]);
}
}
processReceivedPackets() , chamamos a função printDataPacket() .packet->payload .packet->getPayloadLength() . Isso nos informará o tamanho da carga útil, nos tipos de dados, para um determinado pacote. No nosso caso, sempre enviamos apenas um datapacket.printPacket(dPacket[i]) , que imprimirá o contador recebido. Consulte nosso artigo de acesso aberto "Implementação de uma biblioteca Lora Mesh" para obter uma descrição detalhada. Se você usar a biblioteca Loramesher, no trabalho acadêmico, cite o seguinte:
@ARTICLE{9930341,
author={Solé, Joan Miquel and Centelles, Roger Pueyo and Freitag, Felix and Meseguer, Roc},
journal={IEEE Access},
title={Implementation of a LoRa Mesh Library},
year={2022},
volume={10},
number={},
pages={113158-113171},
doi={10.1109/ACCESS.2022.3217215}}