O ROS2_LR_Protocol é um framework leve e robusto criado para facilitar a comunicação via rádio LoRa entre nós (como um gateway e robôs) no contexto de sistemas embarcados (frequentemente ESP32 com FreeRTOS).
O framework simplifica o gerenciamento do módulo LoRa ao encapsular a biblioteca padrão, gerir a comunicação assíncrona por meio de Tasks em background no FreeRTOS e garantir a integridade dos dados usando um protocolo unificado de tamanho fixo.
O protocolo define uma estrutura estrita em formato de bloco de dados compacto de 21 bytes, otimizado para evitar preenchimento de memória (padding) com __attribute__((packed)).
Para classificar o tráfego, utiliza-se a enumeração MsgType. Você pode estendê-la como necessário. Alguns exemplos integrados incluem:
- Comandos (Gateway -> Robô):
MSG_CMD_VEL,MSG_LED_COR - Respostas (Robô -> Gateway):
MSG_TELEMETRY - Genéricas/Customizadas:
MSG_USER_CUSTOM_1
O conteúdo das mensagens vai nos 16 bytes centrais (packet.data). Graças à diretiva union, a mesma região de memória trafega structs exclusivas sem desperdício e de forma tipada, facilitando a vida do desenvolvedor.
PayloadCmdVel: Transporta pares de valores flutuantes (float linear_x,float angular_z).PayloadLed: Transporta propriedades (color_code,blink_rate).PayloadTelemetry: Transporta telemetria de sensores (accel_x,accel_y, estado).raw_data[16]: Um coringa que o permite acoplar dados puros ou tratar a carga manualmente byte a byte.
Abaixo encontra-se a tabela de consulta rápida das principais funções oferecidas pelo LoRaHandler.
| Função / Método | Parâmetros | Retorno | Descrição | Quando Utilizar |
|---|---|---|---|---|
LoRaHandler(...) |
int ss: Pino CSint rst: Pino Resetint dio0: Pino de Interrupçãouint8_t myId: ID na rede |
N/A (Construtor) |
Constrói o objeto vinculando as portas de hardware e cria a trava (Mutex) do barramento SPI. | Na declaração global do firmware, preparando o objeto para inicialização. |
begin() |
long frequency: Frequência de operação do LoRa em Hz (ex: 915E6) |
bool: true se sucesso, false se rádio ausente |
Comunica-se via SPI com o módulo rádio para inicialização e ativa a Task paralela do FreeRTOS para ouvir a rede. | Dentro do setup() para ligar efetivamente o hardware. |
setCallback() |
PacketCallback callback: O ponteiro da sua função de processamento |
void |
Associa a sua rotina de código ao motor de recepção. A função será chamada quando um pacote passar pelo CRC. | No setup(), logo após o rádio estar operante. |
sendPacket() |
uint8_t targetId: ID Destinouint8_t type: O ID do MsgTypevoid* payloadData: A sua structsize_t payloadSize: Tamanho da struct |
void |
Limpa a payload, formata a mensagem, calcula o checksum CRC-16 e escreve de modo seguro (bloqueando o Mutex) no barramento do rádio. | Em qualquer rotina que necessite exportar comandos ou dados de sensores para outros nós. |
calculateCRC16() |
const uint8_t *data: Buffersize_t length: Tamanho |
uint16_t: Hash validado |
Privado. Roda o polinómio CCITT-FALSE (0x1021) pelos bytes em memória. |
Uso interno automático. Invocada na entrada e saída de rádio. |
A classe LoRaHandler (LoRaHandler.cpp / .h) encapsula a inicialização, envio, recebimento assíncrono e segurança do barramento SPI.
No seu firmware, você inicializa a comunicação instanciando a classe e fornecendo os pinos do SPI e o "ID" daquele nó.
#include "LoRaHandler.h"
// Definindo os pinos do barramento SPI
#define SS_PIN 5
#define RST_PIN 14
#define DIO0_PIN 2
// ID deste nó na rede (ex: 10)
#define MY_NODE_ID 10
// Cria o handler de forma global
LoRaHandler lora(SS_PIN, RST_PIN, DIO0_PIN, MY_NODE_ID);
void setup() {
Serial.begin(115200);
// Inicializa o LoRa na frequência escolhida (ex: 915 MHz)
// O método 'begin' automaticamente cria uma Task background (no Core 0) para ouvir a rede.
if (!lora.begin(915E6)) {
Serial.println("Erro: LoRa falhou ao iniciar!");
while (1);
}
}A forma correta de receber mensagens é usar callbacks. Quando a task de monitoramento (taskLoop) deteta um pacote lícito, livre de ruído, endereçado a você (target_id == MY_NODE_ID) ou endereçado via broadcast (target_id == 255), o framework notifica a sua função de serviço.
void aoReceberPacote(LoRaPacket& pacote) {
Serial.printf("Mensagem de %d recebida!\n", pacote.sender_id);
// Roteamento baseado no Tipo de Mensagem
switch (pacote.msg_type) {
case MSG_CMD_VEL:
Serial.printf("Comando de Velocidade: Linear=%.2f Angular=%.2f\n",
pacote.data.cmd.linear_x,
pacote.data.cmd.angular_z);
break;
case MSG_TELEMETRY:
Serial.printf("Telemetria: Acelerômetro X: %.2f\n", pacote.data.telemetry.accel_x);
break;
}
}
void setup() {
// ... lora.begin(915E6) ...
// Injeta a sua função no motor do LoRa
lora.setCallback(aoReceberPacote);
}Em qualquer parte do seu programa principal, você pode usar sendPacket sem se preocupar em corromper o receptor que está rodando noutro Core do ESP32.
void enviarLeituraSD() {
PayloadTelemetry dadosSensor;
dadosSensor.accel_x = 1.05;
dadosSensor.accel_y = 0.98;
dadosSensor.bumper_state = 1;
uint8_t destinoId = 1; // ID do Gateway receptor
// Parâmetros: ID Destino, Tipo da Mensagem, Ponteiro para os Dados, Tamanho dos Dados
lora.sendPacket(destinoId, MSG_TELEMETRY, &dadosSensor, sizeof(dadosSensor));
}Redes RF estão altamente expostas a "flip de bits" e corrupção devido a ruído ambiental. O pacote reserva seus últimos 2 bytes para um algoritmo matemático CRC-16.
- Transmissão: Ao invocar
sendPacket, a RAM do payload é formatada/zerada e é extraída uma assinatura polinomial sobre todos os bytes válidos (19 bytes iniciais). Ochecksumviaja no final do pacote. - Recepção: Apenas mensagens que fechem a re-validação matemática (
calculatedCRC == pacote.checksum) são repassadas à Callback do usuário. Se falhar por conta de lixo aéreo, interrupções ou colisões de canal, a mensagem é silenciosamente descartada.
O envio na função principal e a escuta por pacotes no background disputam os mesmos fios elétricos do rádio SPI. No FreeRTOS, a sobreposição é fatal.
A classe LoRaHandler encapsula um Mutex de Semáforo (_spiMutex). Tanto sendPacket() (Main) quanto taskLoop() (Task LoRa) obrigatoriamente retêm autorização atômica antes de falar com o rádio (xSemaphoreTake). Caso uma transação esteja no meio, a outra thread aguarda alguns décimos de segundo até que termine, prevenindo crashes no ESP32 ou estado inconsistente.