// 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); MostrarLog("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) { MostrarLog("E220 detectado!"); if (LraTaskRxHandle == NULL) { lraQueueRx = xQueueCreate(50, sizeof(std::vector)); if (lraQueueRx == NULL) { MostrarLog("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) { MostrarLog("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 { MostrarLog("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) { MostrarLog("Fila TX cheia! Mensagem descartada."); return false; } else { MostrarLog("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]; MostrarLog("Recebido comando ConfigurarModulo: posicao = " + String((int)posicao)); switch (posicao) { case CanMessagePosicaoDados::Config1: { if (data.size() < 8) return data; AddressBase = data[2]; _pinM0 = data[3]; _pinM1 = data[4]; _pinAUX = data[5]; _pinRXD = data[6]; _pinTXD = data[7]; break; } case CanMessagePosicaoDados::Config2: { if (data.size() < 7) return status; if (_pinRXD == 0 || _pinTXD == 0) { MostrarLog("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 if (DebugMode) { String log = String("Parametros Config:") + " 0x" + String(addr, HEX) + " 0x" + String(baud, HEX) + " 0x" + String(packet, HEX) + " 0x" + String(channel, HEX) + " 0x" + String(worCycle, HEX); MostrarLog(log); } 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 = (uint8_t)parametrosAtual.address == (uint8_t)addr && (uint8_t)parametrosAtual.baudAndAirRate == (uint8_t)baud && (uint8_t)parametrosAtual.packetSizeAndPower == (uint8_t)packet && (uint8_t)parametrosAtual.channel == (uint8_t)channel && (uint8_t)parametrosAtual.tranModeAndWorCycle == (uint8_t)worCycle; MostrarLog("Modulo ja configurado: " + String(Configurado)); if (!Configurado) { Configurado = configurarModuloLoRa(addr, baud, packet, channel, worCycle); } } } EnviarDadosCAN(MontarMensagemCAN(CanMessagePosicaoDados::Status)); EnviarDadosCAN(MontarMensagemCAN(CanMessagePosicaoDados::Dados1)); break; } } return status; } private: bool DebugMode = true; void MostrarLog(String mensagem) { if (DebugMode) { PrintTela("[LORA] " + mensagem); } } 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* service = static_cast(pvParameters); while (true) { if (!Conectado || !Configurado) { vTaskDelay(1000); continue; } if (recebendo && (millis() - ultimoByteRecebido > timeoutRecebimentoMs)) { MostrarLog("Timeout de recebimento. Reiniciando estado."); reiniciarEstado(); } while (currentMode == LoRaMode::NORMAL && service->_serialLoRa->available()) { uint8_t byteRecebido = service->_serialLoRa->read(); ultimoByteRecebido = millis(); // <- atualiza o tempo MostrarLog("byteRecebido=" + String(byteRecebido)); if (!recebendo) { if (byteRecebido == CodigosFuncoes::BeginMsg) { reiniciarEstado(); MostrarLog("Iniciou o recebimento dos dados LoRa"); recebendo = true; } } else { if (bytesEsperados == -1) { bytesEsperados = byteRecebido; //MostrarLog("Definindo bytesEsperados = " + String(bytesEsperados)); } else if (addrRemetente == -1) { addrRemetente = byteRecebido; //MostrarLog("Definindo addrRemetente = " + String(addrRemetente)); } else if (addrDestinatario == -1) { addrDestinatario = byteRecebido; //MostrarLog("Definindo addrDestinatario = " + String(addrDestinatario)); if (addrDestinatario != parametrosAtual.address) { MostrarLog("Mensagem para outro ID, descartando"); reiniciarEstado(); } } else { buffer.push_back(byteRecebido); bool protocoloCompleto = addrRemetente != -1 && addrDestinatario != -1 && buffer.size() == bytesEsperados + 2; // +2 = checksum + end MostrarLog("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) { MostrarLog("Mensagem LoRa recebida de " + String(addrRemetente) + " com checksum válido"); if (buffer.size() >= 2) { if (xQueueSend(lraQueueRx, &buffer, 0) != pdTRUE) { MostrarLog("Fila RX cheia! Mensagem descartada."); } else { MostrarLog("Mensagem adicionada na fila RX"); } } } else { MostrarLog("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 = 200; // 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; } MostrarLog("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) { MostrarLog("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() { MostrarLog("Verificando se o E220 esta conectado..."); if (!setLoRaMode(CONFIG)) { MostrarLog("Falha ao verificar se o E220 esta conectado, erro 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]; MostrarLog("Tentando BaudRate: " + String(_baudRate)); } // Limpa buffers antes de testar limparBufferLoRa(); uint8_t command[1] = { CodigosFuncoes::CfgRx }; enviarDadosSerial(command, 1, "Check Conectado"); 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) { MostrarLog("E220 conectado com baudRate " + String(_baudRate)); } else { MostrarLog("Falha ao conectar no E220!"); } return conectado; } // Método que monta o comando de configuração bool configurarModuloLoRa(byte addr, byte baudAndAir, byte packetAndPower, byte channel, byte tranModeAndworCycle) { MostrarLog("Configurando modulo..."); if (!setLoRaMode(CONFIG)) { MostrarLog("Falha ao configurar modulo, erro ao mudar para CONFIG!"); return false; } limparBufferLoRa(); uint8_t command[9] = { CodigosFuncoes::CfgTx, // Código para configurar 0x00, // Registro inicial 0x06, // Quantidade de registros 0x00, // AddrH addr, // addrL baudAndAir, packetAndPower, channel, tranModeAndworCycle }; enviarDadosSerial(command, 9, "Config Modulo"); bool sucesso = receberParametrosModulo(command[1], command[2]); setLoRaMode(NORMAL); return sucesso; } // Método que requisita a leitura dos parâmetros atuais bool requisitarParametrosLoRa() { MostrarLog("Requisitando parametros do modulo..."); if (!setLoRaMode(CONFIG)) { MostrarLog("Falha ao requisitar parametros do modulo, erro ao mudar para CONFIG!"); return false; } limparBufferLoRa(); uint8_t command[3] = { CodigosFuncoes::CfgRx, 0x00, 0x0B }; enviarDadosSerial(command, 3, "Requisitar Parametros"); bool sucesso = receberParametrosModulo(command[1], command[2]); setLoRaMode(NORMAL); return sucesso; } bool receberParametrosModulo(uint8_t _startAddr, uint8_t _dataLen) { 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 MostrarLog("Cabecalho recebido corretamente."); uint8_t startAddr = _serialLoRa->read(); uint8_t dataLen = _serialLoRa->read(); if (startAddr == _startAddr && dataLen == _dataLen) { MostrarLog("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) parametrosAtual.baudAndAirRate = _serialLoRa->read(); // byte 2 parametrosAtual.packetSizeAndPower = _serialLoRa->read(); // byte 3 parametrosAtual.channel = _serialLoRa->read(); // byte 4 parametrosAtual.tranModeAndWorCycle = _serialLoRa->read(); // byte 5 if (DebugMode) { String log = "Recebido Parametros do Modulo: (" + String(dataLen) + " bytes): " + "0x" + String(startAddr, HEX) + " " + "0x" + String(dataLen, HEX) + " " + "0x" + String(addh, HEX) + " " + "0x" + String(addl, HEX) + " " + "0x" + String(parametrosAtual.baudAndAirRate, HEX) + " " + "0x" + String(parametrosAtual.packetSizeAndPower, HEX) + " " + "0x" + String(parametrosAtual.channel, HEX) + " " + "0x" + String(parametrosAtual.tranModeAndWorCycle, HEX); MostrarLog(log); } sucesso = true; } else { if (DebugMode) { String log = "Dados Corrompidos: (" + String(dataLen) + " bytes): 0x" + String(startAddr, HEX) + " 0x" + String(dataLen, HEX) + " "; for (size_t i = 0; i < dataLen; i++) { uint8_t c = _serialLoRa->read(); log += "0x" + String(c, HEX) + " "; } MostrarLog(log); } } } else if (cabecalho == CodigosFuncoes::WrongMode) { MostrarLog("Modo invalido!"); } } vTaskDelay(10); } limparBufferLoRa(); return sucesso; } void enviarDadosSerial(const uint8_t* dados, int tamanho, const String& descricao = "") { if (!_serialLoRa) return; _serialLoRa->write(dados, tamanho); _serialLoRa->flush(); if (DebugMode) { String log = "Enviando [" + descricao + "] (" + String(tamanho) + " bytes): "; for (size_t i = 0; i < tamanho; i++) { log += "0x" + String(dados[i], HEX) + " "; } MostrarLog(log); } } // 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 String log = "Frame LoRa montado: "; for (int i = 0; i < frame.size(); i++) { log += String(frame[i]) + " "; } MostrarLog(log); return frame; } bool EnviarDadosCAN(std::vector dados) { _canService->adicionarMensagemFila(dados); return true; } bool EnviarDadosLoRa(std::vector dados) { MostrarLog("Enviando dados via LoRa para o endereco " + String(AddressBase) + " no canal " + String(parametrosAtual.channel)); std::vector dadosLora = MontarFrameLoRa(AddressBase, parametrosAtual.channel, dados); enviarDadosSerial(dadosLora.data(), dadosLora.size(), "Dados LoRa"); return true; } std::map CodigosBaudRates = { {1200, 0x00}, {2400, 0x20}, {4800, 0x40}, {9600, 0x60}, {19200, 0x80}, {38400, 0xA0}, {57600, 0xC0}, {115200, 0xE0}, }; 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