// LoRaService.h #ifndef LoRaService_h #define LoRaService_h #include "SerialService.h" #include "CanService.h" #include #include #include class LoRaService { public: enum LoRaMode { NORMAL, WAKE_UP, POWER_SAVING, CONFIG }; enum CodigosFuncoes { CfgTx = 0xC0, CfgRx = 0xC1, Msg = 0xE1, WrongMode = 0xFC, BeginMsg = 0xAA, EndMsg = 0x55, }; struct LoRaParametrosModel { uint8_t address; uint8_t baudAndAirRate; uint8_t packetSizeAndPower; uint8_t channel; uint8_t tranModeAndWorCycle; }; LoRaService(CanService* canService) { _canService = canService; } bool Conectado = false; bool Configurado = false; byte AddressBase = 0x01; // Inicializa pinos e UART void Inicializar(uint8_t baudRate) { Configurado = false; _baudRate = encontrarChavePorValor(CodigosBaudRates, baudRate, 9600); PrintTela("[LRA] Iniciando LoRa nos pinos TXD=" + String(_pinTXD) + ", RXD=" + String(_pinRXD) + ", baudRate=" + String(_baudRate) + "..."); // Inicializa GPIOs pinMode(_pinM0, OUTPUT); pinMode(_pinM1, OUTPUT); pinMode(_pinAUX, INPUT); _serialLoRa = &Serial2; if (Conectado) { _serialLoRa->end(); } _serialLoRa->begin(_baudRate, SERIAL_8N1, _pinTXD, _pinRXD); // Coloca o módulo em modo normal setLoRaMode(NORMAL); vTaskDelay(500); // Aguarda estabilização // Testa conexão Conectado = checkLoRaConnected(); if (Conectado) { PrintTela("[LRA] E220 detectado!"); if (LraTaskRxHandle == NULL) { lraQueueRx = xQueueCreate(50, sizeof(std::vector)); if (lraQueueRx == NULL) { PrintTela("[LRA] Falha ao criar fila RX!"); } xTaskCreatePinnedToCore(LoRaService::LraTaskRxWrapper, "LraTaskRx", 4096, this, 14, &LraTaskRxHandle, APP_CPU_NUM); } if (LraTaskTxHandle == NULL) { lraQueueTx = xQueueCreate(50, sizeof(std::vector)); if (lraQueueTx == NULL) { PrintTela("[LRA] Falha ao criar fila TX!"); } xTaskCreatePinnedToCore(LoRaService::LraTaskTxWrapper, "LraTaskTx", 4096, this, 13, &LraTaskTxHandle, APP_CPU_NUM); } if (LraTaskProcessHandle == NULL) { xTaskCreatePinnedToCore(LoRaService::LraTaskProcessWrapper, "LraTaskProcess", 4096, this, 15, &LraTaskProcessHandle, APP_CPU_NUM); } } else { PrintTela("[LRA] E220 não detectado."); } } std::vector MontarMensagemCAN(CanMessagePosicaoDados posicao) { std::vector data; data.push_back(static_cast(posicao)); data.push_back(_canService->ID_Num_sLRA); switch (posicao) { case CanMessagePosicaoDados::Status: { data.push_back(Conectado ? 1 : 0); data.push_back(Configurado ? 1 : 0); break; } case CanMessagePosicaoDados::Dados1: { data.push_back(parametrosAtual.address); data.push_back(parametrosAtual.baudAndAirRate); data.push_back(parametrosAtual.packetSizeAndPower); data.push_back(parametrosAtual.channel); data.push_back(parametrosAtual.tranModeAndWorCycle); break; } } return data; } void EnviarComando(std::vector dados) { if (dados.size() < 2) return; CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)dados[0]; uint8_t idNum = dados[1]; switch (posicao) { case CanMessagePosicaoDados::Command1: { bool sucesso = requisitarParametrosLoRa(); EnviarDadosCAN(MontarMensagemCAN(CanMessagePosicaoDados::Dados1)); break; } default: { AdicionarMensagemFilaLoRa(dados); } } } bool AdicionarMensagemFilaLoRa(std::vector dados) { if (xQueueSend(lraQueueTx, &dados, 0) != pdTRUE) { PrintTela("[LRA] Fila TX cheia! Mensagem descartada."); return false; } else { PrintTela("[LRA] Mensagem adicionada na fila TX"); return true; } } std::vector ConfigurarModulo(std::vector data) { std::vector status; if (data.size() < 2) return status; CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)data[0]; uint8_t idNum = data[1]; PrintTela("[LRA] Recebido comando ConfigurarModulo: posicao = " + String((int)posicao)); switch (posicao) { case CanMessagePosicaoDados::Config1: { if (data.size() < 7) return data; _pinM0 = data[2]; _pinM1 = data[3]; _pinAUX = data[4]; _pinRXD = data[5]; _pinTXD = data[6]; break; } case CanMessagePosicaoDados::Config2: { if (data.size() < 7) return data; if (_pinRXD == 0 || _pinTXD == 0) { PrintTela("[LRA] Erro ao iniciar E220, pinout ainda nao definido!"); return status; } uint8_t addr = data[2]; // address uint8_t baud = data[3]; // baudAndAirRate uint8_t packet = data[4]; // packetSizeAndPower uint8_t channel = data[5]; // channel uint8_t worCycle = data[6]; // tranModeAndWorCycle uint8_t baudField = baud & 0xE0; // bits 5,6,7 (baudrate) Inicializar(baudField); EnviarDadosCAN(MontarMensagemCAN(CanMessagePosicaoDados::Status)); if (Conectado) { if (requisitarParametrosLoRa()) { EnviarDadosCAN(MontarMensagemCAN(CanMessagePosicaoDados::Dados1)); Configurado = parametrosAtual.address == addr && parametrosAtual.baudAndAirRate == baud && parametrosAtual.packetSizeAndPower == packet && parametrosAtual.channel == channel && parametrosAtual.tranModeAndWorCycle == worCycle; if (!Configurado) { Configurado = configurarModuloLoRa(addr, baud, packet, channel, worCycle); } } } break; } } return status; } private: bool recebendo = false; int bytesEsperados = -1; int addrRemetente = -1; int addrDestinatario = -1; std::vector buffer; unsigned long ultimoByteRecebido = 0; const unsigned long timeoutRecebimentoMs = 100; // por exemplo, 100ms void reiniciarEstado() { recebendo = false; bytesEsperados = -1; addrRemetente = -1; addrDestinatario = -1; buffer.clear(); } QueueHandle_t lraQueueRx; TaskHandle_t LraTaskRxHandle = NULL; static void LraTaskRxWrapper(void *pvParameters) { LoRaService *service = static_cast(pvParameters); service->LraTaskRx(pvParameters); } void LraTaskRx(void* pvParameters) { LoRaService* instance = (LoRaService*)pvParameters; while (true) { if (!Conectado || !Configurado) { vTaskDelay(1000); continue; } if (recebendo && (millis() - ultimoByteRecebido > timeoutRecebimentoMs)) { PrintTela("[LRA] Timeout de recebimento. Reiniciando estado."); reiniciarEstado(); } while (currentMode == LoRaMode::NORMAL && instance->_serialLoRa->available()) { uint8_t byteRecebido = instance->_serialLoRa->read(); ultimoByteRecebido = millis(); // <- atualiza o tempo PrintTela("byteRecebido=" + String(byteRecebido)); if (!recebendo) { if (byteRecebido == CodigosFuncoes::BeginMsg) { reiniciarEstado(); PrintTela("[LRA] Iniciou o recebimento dos dados LoRa"); recebendo = true; } } else { if (bytesEsperados == -1) { bytesEsperados = byteRecebido; //PrintTela("[LRA] Definindo bytesEsperados = " + String(bytesEsperados)); } else if (addrRemetente == -1) { addrRemetente = byteRecebido; //PrintTela("[LRA] Definindo addrRemetente = " + String(addrRemetente)); } else if (addrDestinatario == -1) { addrDestinatario = byteRecebido; //PrintTela("[LRA] Definindo addrDestinatario = " + String(addrDestinatario)); if (addrDestinatario != parametrosAtual.address) { PrintTela("[LRA] Mensagem para outro ID, descartando"); reiniciarEstado(); } } else { buffer.push_back(byteRecebido); bool protocoloCompleto = addrRemetente != -1 && addrDestinatario != -1 && buffer.size() == bytesEsperados + 2; // +2 = checksum + end PrintTela("[LRA] protocoloCompleto = " + String(protocoloCompleto) + ", bufferSize = " + buffer.size() + ", bytesEsperados = " + String(bytesEsperados)); if (protocoloCompleto) { if (buffer.back() == CodigosFuncoes::EndMsg) { buffer.pop_back(); // remove 0x55 (EndMsg) uint8_t checksumRecebido = buffer.back(); buffer.pop_back(); // remove checksum uint8_t soma = 0; for (size_t i = 0; i < buffer.size(); ++i) { soma += buffer[i]; } if ((soma % 256) == checksumRecebido) { PrintTela("[LRA] Mensagem LoRa recebida de " + String(addrRemetente) + " com checksum válido"); if (buffer.size() >= 2) { if (xQueueSend(lraQueueRx, &buffer, 0) != pdTRUE) { PrintTela("[LRA] Fila RX cheia! Mensagem descartada."); } else { PrintTela("[LRA] Mensagem adicionada na fila RX"); } } } else { PrintTela("[LRA] Checksum inválido. Descartando."); } } reiniciarEstado(); } } } } vTaskDelay(1); } } QueueHandle_t lraQueueTx; TaskHandle_t LraTaskTxHandle = NULL; static void LraTaskTxWrapper(void *pvParameters) { LoRaService *service = static_cast(pvParameters); service->LraTaskTx(pvParameters); } void LraTaskTx(void* pvParameters) { LoRaService *service = static_cast(pvParameters); std::vector msg; while (true) { if (!Conectado || !Configurado) { vTaskDelay(1000); continue; } if (xQueueReceive(service->lraQueueTx, &msg, portMAX_DELAY) == pdTRUE) { service->EnviarDadosLoRa(msg); } vTaskDelay(1); } } TaskHandle_t LraTaskProcessHandle = NULL; static void LraTaskProcessWrapper(void *pvParameters) { LoRaService *service = static_cast(pvParameters); service->LraTaskProcess(pvParameters); } void LraTaskProcess(void* pvParameters) { LoRaService *service = static_cast(pvParameters); std::vector msg; while (true) { if (!Conectado || !Configurado) { vTaskDelay(1000); continue; } if (xQueueReceive(service->lraQueueRx, &msg, portMAX_DELAY) == pdTRUE) { service->EnviarDadosCAN(msg); } vTaskDelay(1); } } CanService* _canService; HardwareSerial* _serialLoRa; uint8_t _pinTXD; uint8_t _pinRXD; uint8_t _pinM0; uint8_t _pinM1; uint8_t _pinAUX; int _baudRate = 9600; LoRaMode currentMode = NORMAL; LoRaParametrosModel parametrosAtual; int commandTimeout = 1000; // Métodos internos // Define o modo do LoRa bool setLoRaMode(LoRaMode mode) { if (mode == currentMode) return true; // Ajusta os pinos M0 e M1 conforme o modo desejado switch (mode) { case NORMAL: digitalWrite(_pinM0, LOW); digitalWrite(_pinM1, LOW); break; case WAKE_UP: digitalWrite(_pinM0, HIGH); digitalWrite(_pinM1, LOW); break; case POWER_SAVING: digitalWrite(_pinM0, LOW); digitalWrite(_pinM1, HIGH); break; case CONFIG: digitalWrite(_pinM0, HIGH); digitalWrite(_pinM1, HIGH); break; } PrintTela("[LRA] Modo alterado de " + String(currentMode) + " para " + String(mode)); currentMode = mode; vTaskDelay(100); // Aguarda sinalização inicial de troca // ESPERA o AUX ir para HIGH, indicando que a troca completou unsigned long timeout = millis() + commandTimeout; while (digitalRead(_pinAUX) == LOW) { if (millis() > timeout) { PrintTela("[LRA] Erro: Timeout aguardando AUX após mudança de modo."); return false; } } vTaskDelay(100); // Manual pede 2ms após AUX ficar HIGH return true; } // Verifica se o E220 responde bool checkLoRaConnected() { PrintTela("[LRA] Verificando se o E220 esta conectado..."); if (!setLoRaMode(CONFIG)) { PrintTela("[LRA] Falha ao mudar para CONFIG!"); return false; } bool conectado = false; // Lista de possíveis baud rates (em ordem dos mais prováveis) const uint32_t baudRates[] = { 9600, 19200, 38400, 57600, 115200, 4800, 2400, 1200 }; const int totalBauds = sizeof(baudRates) / sizeof(baudRates[0]); int tentativaAtual = -1; // -1 = usar o baudrate já setado primeiro while (!conectado && tentativaAtual < totalBauds) { if (tentativaAtual >= 0) { // Se estamos tentando outra taxa, mudar a serial: _serialLoRa->end(); _serialLoRa->begin(baudRates[tentativaAtual], SERIAL_8N1, _pinTXD, _pinRXD); _baudRate = baudRates[tentativaAtual]; PrintTela("[LRA] Tentando BaudRate: " + String(_baudRate)); } // Limpa buffers antes de testar limparBufferLoRa(); _serialLoRa->write(CodigosFuncoes::CfgRx); unsigned long startTime = millis(); while (millis() - startTime < commandTimeout) { if (_serialLoRa->available()) { uint8_t dado = _serialLoRa->read(); if (dado == CodigosFuncoes::CfgRx) { conectado = true; break; } } vTaskDelay(1); } tentativaAtual++; } limparBufferLoRa(); setLoRaMode(NORMAL); if (conectado) { PrintTela("[LRA] E220 conectado com baudRate " + String(_baudRate)); } else { PrintTela("[LRA] Falha ao conectar no E220!"); } return conectado; } // Método que monta o comando de configuração bool configurarModuloLoRa(byte addr, byte baud, byte packet, byte channel, byte worCycle) { if (!setLoRaMode(CONFIG)) { PrintTela("[LRA] Falha ao mudar para CONFIG!"); return false; } PrintTela("[LRA] Configurando modulo..."); limparBufferLoRa(); uint8_t command[9] = { CodigosFuncoes::CfgTx, // Código para configurar 0x00, // Registro inicial 0x06, // Quantidade de registros 0x00, // Reservado addr, baud, packet, channel, worCycle }; _serialLoRa->write(command, sizeof(command)); PrintTela("[LRA] Comando de configuração enviado!"); unsigned long startTime = millis(); bool sucesso = false; while (millis() - startTime < commandTimeout) { if (_serialLoRa->available()) { uint8_t cabecalho = _serialLoRa->read(); if (cabecalho == CodigosFuncoes::CfgRx) { // 0xC1 é Resposta de Configuração sucesso = true; PrintTela("[LRA] Confirmação de configuração recebida!"); } else { PrintTela("[LRA] Resposta inesperada: 0x" + String(cabecalho)); } break; } vTaskDelay(10); } setLoRaMode(NORMAL); return sucesso; } // Método que requisita a leitura dos parâmetros atuais bool requisitarParametrosLoRa() { if (!setLoRaMode(CONFIG)) { PrintTela("[LRA] Falha ao mudar para CONFIG!"); return false; } PrintTela("[LRA] Requisitando parametros do modulo..."); limparBufferLoRa(); uint8_t command[3] = { CodigosFuncoes::CfgRx, 0x00, 0x0B }; _serialLoRa->write(command, sizeof(command)); PrintTela("[LRA] Comando de requisição enviado!"); unsigned long startTime = millis(); bool sucesso = false; while (!sucesso && millis() - startTime < commandTimeout) { if (_serialLoRa->available()) { uint8_t cabecalho = _serialLoRa->read(); if (cabecalho == CodigosFuncoes::CfgRx) { // Cabeçalho de resposta correta PrintTela("[LRA] Cabecalho recebido corretamente."); uint8_t startAddr = _serialLoRa->read(); // Deve ser 0x00 uint8_t dataLen = _serialLoRa->read(); // Deve ser 0x0B if (startAddr == command[1] && dataLen == command[2]) { PrintTela("[LRA] Parâmetros recebidos corretamente."); // Agora ler os registros uint8_t addh = _serialLoRa->read(); // byte 0 uint8_t addl = _serialLoRa->read(); // byte 1 parametrosAtual.address = addl; // usa só o ADDL (0~255) // Continua normalmente parametrosAtual.baudAndAirRate = _serialLoRa->read(); // byte 2 parametrosAtual.packetSizeAndPower = _serialLoRa->read(); // byte 3 parametrosAtual.channel = _serialLoRa->read(); // byte 4 parametrosAtual.tranModeAndWorCycle = _serialLoRa->read(); // byte 5 // Lê e ignora os 5 bytes restantes (6~10) for (int i = 0; i < (0x0B - 6); i++) { _serialLoRa->read(); } sucesso = true; } else { PrintTela("[LRA] Dados Corrompidos: startAddr=" + String(startAddr) + ", dataLen=" + String(dataLen)); } } else if (cabecalho == CodigosFuncoes::WrongMode) { PrintTela("[LRA] Modo invalido!"); } } vTaskDelay(10); } limparBufferLoRa(); setLoRaMode(NORMAL); return sucesso; } // Limpa buffer anterior void limparBufferLoRa() { while (_serialLoRa->available()) { _serialLoRa->read(); } } std::vector MontarFrameLoRa(uint16_t destino, uint8_t canal, const std::vector& payloadApp) { std::vector frame; // Cabeçalho fixo LoRa (modo Fixed) frame.push_back((destino >> 8) & 0xFF); // Addr High (geralmente 0x00) frame.push_back(destino & 0xFF); // Addr Low frame.push_back(canal); // Canal // Estrutura do payload frame.push_back(CodigosFuncoes::BeginMsg); // 0xAA frame.push_back(payloadApp.size()); // Tamanho do payload frame.push_back(parametrosAtual.address); // rementente frame.push_back(destino); // destinatario frame.insert(frame.end(), payloadApp.begin(), payloadApp.end()); // Calcula checksum (soma dos dados de payload) uint8_t soma = 0; for (uint8_t b : payloadApp) { soma += b; } uint8_t checksum = soma % 256; frame.push_back(checksum); // Checksum frame.push_back(CodigosFuncoes::EndMsg); // 0x55 PrintTela("Frame LoRa montado: ", false); for (int i = 0; i < frame.size(); i++) { PrintTela(String(frame[i]) + " ", false); } PrintTela(""); return frame; } bool EnviarDadosCAN(std::vector dados) { _canService->adicionarMensagemFila(dados); return true; } bool EnviarDadosLoRa(std::vector dados) { PrintTela("Enviando dados via LoRa para o endereco " + String(AddressBase) + " no canal " + String(parametrosAtual.channel)); std::vector dadosLora = MontarFrameLoRa(AddressBase, parametrosAtual.channel, dados); _serialLoRa->write(dadosLora.data(), dadosLora.size()); _serialLoRa->flush(); PrintTela("Dados enviados"); return true; } std::map CodigosTipoTransmissao = { {"Normal", 0x00}, {"Fixed", 0x40}, }; std::map CodigosBaudRates = { {1200, 0x00}, {2400, 0x20}, {4800, 0x40}, {9600, 0x60}, {19200, 0x80}, {38400, 0xA0}, {57600, 0xC0}, {115200, 0xE0}, }; std::map CodigosAirRates = { {2.4, 0x02}, {4.8, 0x03}, {9.6, 0x04}, {19.2, 0x05}, {38.4, 0x06}, {62.5, 0x67}, }; std::map CodigosPacketSizes = { {200, 0x00}, {128, 0x40}, {64, 0x80}, {32, 0xC0}, }; std::map CodigosWorCycles = { {500, 0x00}, {1000, 0x01}, {1500, 0x02}, {2000, 0x03}, {2500, 0x04}, {3000, 0x05}, {3500, 0x06}, {4000, 0x07}, }; std::map CodigosPowers = { {22, 0x00}, {17, 0x01}, {13, 0x02}, {10, 0x03}, }; template K encontrarChavePorValor(const std::map& mapa, V valorProcurado, K valorDefault) { for (const auto& par : mapa) { if (par.second == valorProcurado) { return par.first; } } return valorDefault; } }; #endif