#include #include #include #include #include #include #include "LoRa.h" #include #include #include #include #include "Preferences.h" // Para salvar SSID/senha //lora #define BAND 915E6 // Frequência (915MHz para América do Sul) #define SCK 5 // GPIO5 -- SX1278's SCK #define MISO 19 // GPIO19 -- SX1278's MISnO #define MOSI 27 // GPIO27 -- SX1278's MOSI #define SS 18 // GPIO18 -- SX1278's CS #define RST 14 // GPIO14 -- SX1278's RESET #define DI0 26 // GPIO26 -- SX1278's IRQ(Interrupt Request) int packetCount = 0; int lastSendTime = 0; int interval = 2000; // Intervalo entre envios (2 segundos) String lastReceived = ""; int lastRSSI = 0; String message = ""; String boiaState = ""; bool comunicacaoLora = false; int lastLora = 0; int loraTimeOut = 3000; // Configuração do Display OLED #define OLED_SDA 4 #define OLED_SCL 15 #define OLED_RST 16 SSD1306 display(0x3c, OLED_SDA, OLED_SCL); SPIClass spiSD(HSPI); // Configuração do SD Card - Pinos seguros #define SD_CS 17 // Chip Select (CS) #define SD_MOSI 23 // Master Out Slave In (MOSI) #define SD_MISO 2 // Master In Slave Out (MISO) #define SD_SCK 13 // Serial Clock (SCK) // RTC DS3231 (usa I2C - SDA=21, SCL=22 por padrão) // ATENÇÃO: Como SDA/SCL do display já usam 4/15, // vamos usar I2C secundário nos pinos 21/22 TwoWire I2C_RTC = TwoWire(1); RTC_DS3231 rtc; //SPIClass sdSPI(VSPI); bool sdOk = false; bool rtcOk = false; int releState = 0; // Controle de alternância do display unsigned long lastDisplayToggle = 0; bool showNetworkInfo = true; String resposta = ""; bool deviceConnected = false; char dataHora[32]; #define RELELIGA 32 #define RELEDESLIGA 25 const float THRESHOLD_LIGAR = 3.0; const float THRESHOLD_DESLIGAR = 1.0; bool pinoLigado = false; //flow sensor #define FLOW_SENSOR_PIN 33 // GPIO para o sensor de fluxo volatile int pulseCount = 0; float flowRate = 0.0; float flowMilliLitres = 0.0; float totalMilliLitres = 0.0; unsigned long oldTimeFlow = 0; WiFiServer server(80); // Interrupção do sensor de fluxo void IRAM_ATTR pulseCounter() { pulseCount++; } void inicializaFlowSensor() { pinMode(FLOW_SENSOR_PIN, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(FLOW_SENSOR_PIN), pulseCounter, FALLING); oldTimeFlow = millis(); Serial.println("Sensor de fluxo inicializado no GPIO 33"); } void calculaFluxo() { unsigned long currentTime = millis(); if ((currentTime - oldTimeFlow) >= 1000) { // Calcula a cada 1 segundo detachInterrupt(digitalPinToInterrupt(FLOW_SENSOR_PIN)); // Fator de calibração: pulsos por litro (para YF-S201 é ~7.5 pulsos/L) float calibrationFactor = 7.5; // Cálculo da vazão flowRate = ((1000.0 / (currentTime - oldTimeFlow)) * pulseCount) / calibrationFactor; oldTimeFlow = currentTime; // Calcula volume em mL no último segundo flowMilliLitres = (flowRate / 60) * 1000; // Adiciona ao total totalMilliLitres += flowMilliLitres; if (flowRate > THRESHOLD_LIGAR && !pinoLigado) { digitalWrite(RELELIGA, LOW); pinoLigado = true; delay(100); digitalWrite(RELELIGA, HIGH); } if (flowRate < THRESHOLD_DESLIGAR && pinoLigado) { delay(100); digitalWrite(RELELIGA, HIGH); delay(100); digitalWrite(RELEDESLIGA, LOW); delay(100); digitalWrite(RELEDESLIGA, HIGH); pinoLigado = false; } pulseCount = 0; attachInterrupt(digitalPinToInterrupt(FLOW_SENSOR_PIN), pulseCounter, FALLING); } } //lora // UUIDs originais para SSID e Senha #define UUID_SSID "c505b1de-4a31-11ef-9d90-47f7f9b3a434" #define UUID_PASS "c505b49e-4a31-11ef-9d90-47f7f9b3a434" #define CHARACTERISTIC_UUID "beb5483e-36e1-4688-b7f5-ea07361b26a8" // Novas UUIDs para comando scan, resultado e status WiFi #define UUID_SCAN_CMD "c505b600-4a31-11ef-9d90-47f7f9b3a434" #define UUID_SCAN_RESULT "c505b601-4a31-11ef-9d90-47f7f9b3a434" #define UUID_WIFI_STATUS "c505b602-4a31-11ef-9d90-47f7f9b3a434" #define UUID_BOMB_CMD "c505b603-4a31-11ef-9d90-47f7f9b3a434" #define UUID_BOMB_RESULT "c505b604-4a31-11ef-9d90-47f7f9b3a434" BLEServer* pServer; BLECharacteristic* pScanCmdChar; BLECharacteristic* pBombCmdChar; BLECharacteristic* pScanResultChar; BLECharacteristic* pWifiStatusChar; BLECharacteristic* pBombStatusChar; String ip, rede; Preferences preferences; // NVS BLEAdvertising* pAdvertising; uint16_t connId = 0; // guarda o ID da conexão para desconectar String performWifiScanAsJson() { int n = WiFi.scanNetworks(true); // show_hidden = true int o = WiFi.scanComplete(); if (o >= 0) { // scan terminou // gerar JSON e enviar Notify WiFi.scanDelete(); // limpa resultado } String json = "["; for (int i = 0; i < n; i++) { String ssid = WiFi.SSID(i); if (ssid.length() == 0) ssid = ""; json += "{\"ssid\":\"" + ssid + "\",\"rssi\":" + String(WiFi.RSSI(i)) + "}"; if (i < n - 1) json += ","; } json += "]"; return json; } void ligaRele(int rele) { digitalWrite(rele, LOW); delay(1000); digitalWrite(rele, HIGH); releState = 0; } // --- Callbacks para comando SCAN --- class ScanCmdCallbacks : public BLECharacteristicCallbacks { void onWrite(BLECharacteristic* pCharacteristic) { String value = pCharacteristic->getValue(); if (value == "SCAN") { Serial.println("Comando SCAN recebido via BLE..."); // Pausa Advertising BLE pAdvertising->stop(); delay(200); // Scan WiFi String jsonResult = performWifiScanAsJson(); // Reativa Advertising BLE pAdvertising->start(); // Envia resultado via Notify pScanResultChar->setValue(jsonResult.c_str()); pScanResultChar->notify(); Serial.println("Scan concluído e JSON enviado via BLE Notify:"); Serial.println(jsonResult); } if (value == "OFF") { WiFi.disconnect(true); WiFi.mode(WIFI_OFF); resposta = "Wi-Fi desligado"; Serial.println("Wi-Fi desligado via BLE"); if (deviceConnected && pCharacteristic != NULL) { pCharacteristic->setValue(resposta.c_str()); pCharacteristic->notify(); } } } }; class BombCmdCallbacks : public BLECharacteristicCallbacks { void onWrite(BLECharacteristic* pCharacteristic) { String value = pCharacteristic->getValue(); if (value == "ONBOMB") { Serial.println("Comando ONBOMB recebido"); releState = 1; String resposta = "Bomba ligada"; ligaRele(RELELIGA); if (deviceConnected && pCharacteristic != NULL) { pBombStatusChar->setValue("Bomba ligada"); pBombStatusChar->notify(); } } if (value == "OFFBOMB") { Serial.println("Comando OFFBOMB recebido"); releState = 2; ligaRele(RELEDESLIGA); if (deviceConnected && pCharacteristic != NULL) { pBombStatusChar->setValue("Bomba desligada"); pBombStatusChar->notify(); } } } }; // --- Callbacks para SSID/Password --- class WifiCredentialsCallbacks : public BLECharacteristicCallbacks { void onWrite(BLECharacteristic* pCharacteristic) { String value = pCharacteristic->getValue(); static String ssid = ""; static String pass = ""; if (pCharacteristic->getUUID().toString() == UUID_SSID) { ssid = String(value.c_str()); Serial.println("SSID recebido: " + ssid); } else if (pCharacteristic->getUUID().toString() == UUID_PASS) { pass = String(value.c_str()); Serial.println("Senha recebida: " + pass); } if (ssid.length() > 0 && pass.length() > 0) { Serial.println("Tentando conectar à rede WiFi..."); WiFi.begin(ssid.c_str(), pass.c_str()); int timeout = 0; while (WiFi.status() != WL_CONNECTED && timeout < 20) { // ~10s delay(500); Serial.print("."); timeout++; } Serial.println(); if (WiFi.status() == WL_CONNECTED) { Serial.println("WiFi conectado! IP: " + WiFi.localIP().toString()); pWifiStatusChar->setValue("OK"); pWifiStatusChar->notify(); // Salva SSID/senha na NVS preferences.begin("wifi", false); preferences.putString("ssid", ssid); preferences.putString("pass", pass); preferences.end(); } else { Serial.println("Falha na conexão WiFi"); pWifiStatusChar->setValue("ERROR"); pWifiStatusChar->notify(); } ssid = ""; pass = ""; } } }; // botoes class MyServerCallbacks : public BLEServerCallbacks { void onConnect(BLEServer* pServer) { deviceConnected = true; connId = pServer->getConnId(); Serial.println("Cliente BLE conectado"); }; void onDisconnect(BLEServer* pServer) { deviceConnected = false; Serial.println("Cliente BLE desconectado"); // pServer->startAdvertising(); // Reinicia advertising pAdvertising->start(); } }; void setup() { Serial.begin(115200); delay(2000); Serial.println("=== HELTEC ESP32 + SD CARD + RTC ==="); // Inicializa display inicializaDisplay(); // Inicializa I2C secundário para RTC (pinos 21, 22) I2C_RTC.begin(21, 22, 100000); // SDA=21, SCL=22, 100kHz delay(100); // Inicializa I2C secundário para RTC (pinos 21, 22) //Wire1.begin(21, 22); // SDA=21, SCL=22 inicializaLora(); // Inicializa RTC inicializaRTC(); // Inicializa SD Card inicializaSD(); inicializaFlowSensor(); // Se tudo OK, grava dados iniciais if (sdOk && rtcOk) { gravaLogInicial(); } atualizaDisplay(); pinMode(RELELIGA, OUTPUT); pinMode(RELEDESLIGA, OUTPUT); digitalWrite(RELELIGA, HIGH); digitalWrite(RELEDESLIGA, HIGH); // --- Inicializa BLE --- BLEDevice::init("Water Pump Ranger"); pServer = BLEDevice::createServer(); pServer->setCallbacks(new MyServerCallbacks()); BLEService* pService = pServer->createService(BLEUUID((uint16_t)0x180A)); BLECharacteristic* pSSIDChar = pService->createCharacteristic(UUID_SSID, BLECharacteristic::PROPERTY_WRITE); BLECharacteristic* pPassChar = pService->createCharacteristic(UUID_PASS, BLECharacteristic::PROPERTY_WRITE); pSSIDChar->setCallbacks(new WifiCredentialsCallbacks()); pPassChar->setCallbacks(new WifiCredentialsCallbacks()); pScanCmdChar = pService->createCharacteristic(UUID_SCAN_CMD, BLECharacteristic::PROPERTY_WRITE); pScanCmdChar->setCallbacks(new ScanCmdCallbacks()); pBombCmdChar = pService->createCharacteristic(UUID_BOMB_CMD, BLECharacteristic::PROPERTY_WRITE); pBombCmdChar->setCallbacks(new BombCmdCallbacks()); pScanResultChar = pService->createCharacteristic(UUID_SCAN_RESULT, BLECharacteristic::PROPERTY_NOTIFY); pScanResultChar->addDescriptor(new BLE2902()); pBombStatusChar = pService->createCharacteristic(UUID_BOMB_RESULT, BLECharacteristic::PROPERTY_NOTIFY); pBombStatusChar->addDescriptor(new BLE2902()); pWifiStatusChar = pService->createCharacteristic(UUID_WIFI_STATUS, BLECharacteristic::PROPERTY_NOTIFY); pWifiStatusChar->addDescriptor(new BLE2902()); pService->start(); pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(pService->getUUID()); pAdvertising->start(); Serial.println("BLE iniciado e advertising ativo."); // --- Inicializa WiFi --- WiFi.setHostname("Water111-Pump_Ranger"); WiFi.mode(WIFI_STA); WiFi.disconnect(true); Serial.println("WiFi pronto."); //conecta automaticamente usando NVS --- preferences.begin("wifi", false); String savedSSID = preferences.getString("ssid", ""); String savedPass = preferences.getString("pass", ""); preferences.end(); if (savedSSID.length() > 0) { Serial.println("Reconectando ao WiFi salvo: " + savedSSID); WiFi.begin(savedSSID.c_str(), savedPass.c_str()); int timeout = 0; while (WiFi.status() != WL_CONNECTED && timeout < 20) { delay(500); Serial.print("."); timeout++; } Serial.println(); if (WiFi.status() == WL_CONNECTED) { ip = WiFi.localIP().toString(); rede = WiFi.SSID(); Serial.println("Reconexão bem sucedida! IP: " + WiFi.localIP().toString()); } else { Serial.println("Falha na reconexão automática"); } } server.begin(); } void inicializaLora() { SPI.begin(SCK, MISO, MOSI, SS); LoRa.setPins(SS, RST, DI0); // Inicializa LoRa if (!LoRa.begin(BAND)) { Serial.println("Erro ao iniciar LoRa!"); //displayMessage("ERRO LoRa!", "Verifique", "conexões"); while (1) ; } LoRa.setSpreadingFactor(10); // SF7 - SF12 (maior = mais alcance, menor velocidade) LoRa.setSignalBandwidth(125E3); // 125kHz LoRa.setCodingRate4(5); // 4/5 LoRa.setPreambleLength(8); // Preâmbulo LoRa.enableCrc(); // Habilita CRC Serial.println("LoRa inicializado com sucesso!"); Serial.println("Frequência: " + String(BAND / 1E6) + " MHz"); } void sendMessage() { //packetCount++; //String state = voltagemMean < 30 ? "alto" : "baixo"; //String message = "Boia em nivel " + state; // Envia mensagem LoRa.beginPacket(); LoRa.print(dataHora); LoRa.endPacket(); Serial.println("Enviado: " + String(dataHora)); //displayMessage("ENVIANDO", message, ""); }; void loop() { int packetSize = LoRa.parsePacket(); if (packetSize) { receiveMessage(); } if (millis() - lastLora > loraTimeOut) { comunicacaoLora = false; } else{ comunicacaoLora = true; } // Envia mensagem a cada intervalo definido if (millis() - lastSendTime > interval) { sendMessage(); //releState = 20; lastSendTime = millis(); } static unsigned long ultimaGravacao = 0; // digitalWrite(ENBTN, HIGH); if (millis() - lastDisplayToggle > 2000) { lastDisplayToggle = millis(); showNetworkInfo = !showNetworkInfo; } // Calcula fluxo de água calculaFluxo(); // Grava dados a cada 10 segundos if (millis() - ultimaGravacao > 10000) { ultimaGravacao = millis(); if (sdOk && rtcOk) { //gravaDadosComTimestamp(); } } atualizaDisplay(); verifyFlow(); checaRele(); //checkBoia(); //static unsigned long lastCheck = 0; // if (millis() - lastCheck > 10000) { // lastCheck = millis(); // if (WiFi.status() != WL_CONNECTED) { // Serial.println("WiFi caiu, tentando reconectar..."); // preferences.begin("wifi", false); // String savedSSID = preferences.getString("ssid", ""); // String savedPass = preferences.getString("pass", ""); // preferences.end(); // if (savedSSID.length() > 0) { // WiFi.begin(savedSSID.c_str(), savedPass.c_str()); // int timeout = 0; // while (WiFi.status() != WL_CONNECTED && timeout < 20) { // delay(500); // Serial.print("."); // timeout++; // } // Serial.println(); // if (WiFi.status() == WL_CONNECTED) { // ip = WiFi.localIP().toString(); // rede = WiFi.SSID(); // Serial.println("Reconexão WiFi bem sucedida! IP: " + WiFi.localIP().toString()); // } else { // Serial.println("Falha na reconexão WiFi"); // } // } // } // } } void registraInformacao() { DateTime now = rtc.now(); char secondString[3]; // Character array to hold the second (e.g., "05", "59") // Size 3: 2 digits + null terminator char minuteString[3]; sprintf(secondString, "%02d", now.second()); sprintf(minuteString, "%02d", now.minute()); if (minuteString == "00" && secondString == "00") { // Nome do arquivo baseado na data char nomeArquivo[32]; sprintf(nomeArquivo, "/dados_%04d_%02d_%02d.csv", now.year(), now.month(), now.day()); // Verifica se é um arquivo novo (precisa de cabeçalho) bool arquivoNovo = !SD.exists(nomeArquivo); File arquivo = SD.open(nomeArquivo, FILE_APPEND); if (!arquivo) { Serial.println("Erro ao abrir arquivo de dados"); return; } // Adiciona cabeçalho se arquivo novo if (arquivoNovo) { arquivo.println("timestamp,data,hora,estado bomba,nivel boia,fluxo agua"); } String releStateString = ""; if (releState == 1) { releStateString = "Ligado"; } if (releState == 2) { releStateString = "Desligado"; } // Grava dados arquivo.printf("%lu,%02d/%02d/%04d,%02d:%02d:%02d,%s,%s,%.1f,\n", now.unixtime(), now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second(), releStateString, lastReceived == "1" ? "Alto" : "Baixo", flowRate); arquivo.close(); } } void checkBoia() { if (message == "1") { releState = 2; } } void checaRele() { // Serial.print("Relestate: "); // Serial.println(releState); if (releState == 1) { digitalWrite(RELELIGA, LOW); } else if (releState == 2) { digitalWrite(RELEDESLIGA, LOW); } releState = 0; } void verifyFlow() { Serial.print("Flow na funcao: "); Serial.println(flowRate); if (flowRate >= 3.0 && releState == 0) { //ligaRele(RELELIGA); } if (flowRate <= 1.0 && releState == 1) { releState = 2; ligaRele(RELEDESLIGA); //flowRate = 0.0; } registraInformacao(); } void inicializaDisplay() { pinMode(OLED_RST, OUTPUT); digitalWrite(OLED_RST, LOW); delay(20); digitalWrite(OLED_RST, HIGH); display.init(); display.flipScreenVertically(); display.clear(); display.setFont(ArialMT_Plain_10); display.drawString(0, 0, "Inicializando..."); display.display(); Serial.println("Display OK"); } void receiveMessage() { // Lê mensagem recebida while (LoRa.available()) { lastLora = millis(); message += (char)LoRa.read(); Serial.print("mensagem "); Serial.println(message); if (message == "1") { releState = 2; } } int rssi = LoRa.packetRssi(); float snr = LoRa.packetSnr(); lastReceived = message; lastRSSI = rssi; message = ""; // Debug serial Serial.println("========================================"); Serial.println("Mensagem recebida: " + message); Serial.println("RSSI: " + String(rssi) + " dBm"); Serial.println("SNR: " + String(snr) + " dB"); Serial.println("========================================"); // Atualiza display //displayMessage("RECEBIDO", message, "RSSI: " + String(rssi) + "dBm"); } void inicializaRTC() { Serial.print("Inicializando RTC DS3231... "); if (!rtc.begin(&I2C_RTC)) { Serial.println("RTC não encontrado!"); rtcOk = false; return; } rtcOk = true; Serial.println("OK"); // Verifica se RTC perdeu energia if (rtc.lostPower()) { Serial.println("RTC perdeu energia, configurando data/hora..."); // Configura data/hora atual (ajuste conforme necessário) rtc.adjust(DateTime(F(__DATE__), F(__TIME__))); } // Mostra data/hora atual DateTime now = rtc.now(); Serial.printf("Data/Hora atual: %02d/%02d/%04d %02d:%02d:%02d\n", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second()); // Configurações do DS3231 rtc.disable32K(); // Desabilita saída 32kHz rtc.clearAlarm(1); // Limpa alarme 1 rtc.clearAlarm(2); // Limpa alarme 2 rtc.writeSqwPinMode(DS3231_OFF); // Desabilita onda quadrada } void inicializaSD() { Serial.print("Inicializando SD Card... "); // Configura SPI customizado //sdSPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS); spiSD.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS); if (!SD.begin(SD_CS, spiSD)) { Serial.println("FALHOU!"); sdOk = false; return; } sdOk = true; Serial.println("OK"); // Mostra informações do cartão uint64_t cardSize = SD.cardSize() / (1024 * 1024); Serial.printf("Tamanho do cartão: %llu MB\n", cardSize); Serial.printf("Espaço usado: %llu MB\n", SD.usedBytes() / (1024 * 1024)); } void gravaLogInicial() { DateTime now = rtc.now(); File arquivo = SD.open("/system_log.txt", FILE_APPEND); if (!arquivo) { Serial.println("Erro ao abrir log do sistema"); return; } arquivo.println("==============================="); arquivo.printf("SISTEMA INICIADO: %02d/%02d/%04d %02d:%02d:%02d\n", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second()); arquivo.printf("ESP32 ID: %08X\n", (uint32_t)ESP.getEfuseMac()); arquivo.printf("RAM Livre: %d KB\n", ESP.getFreeHeap() / 1024); arquivo.printf("SD Card: %llu MB\n", SD.cardSize() / (1024 * 1024)); arquivo.println("Status: RTC + SD funcionando"); if (rede.length() > 0) { arquivo.printf("Rede: %s \n", rede); } if (ip.length() > 0) { arquivo.printf("IP: %s \n", ip); } arquivo.println("==============================="); arquivo.close(); Serial.println("Log inicial gravado com sucesso!"); } void gravaDadosComTimestamp() { DateTime now = rtc.now(); // Nome do arquivo baseado na data char nomeArquivo[32]; sprintf(nomeArquivo, "/dados_%04d_%02d_%02d.csv", now.year(), now.month(), now.day()); // Verifica se é um arquivo novo (precisa de cabeçalho) bool arquivoNovo = !SD.exists(nomeArquivo); File arquivo = SD.open(nomeArquivo, FILE_APPEND); if (!arquivo) { Serial.println("Erro ao abrir arquivo de dados"); return; } // Adiciona cabeçalho se arquivo novo if (arquivoNovo) { arquivo.println("timestamp,data,hora,temperatura,umidade,bateria,ram_livre"); } // Simula dados de sensores float temperatura = 20.0 + random(0, 150) / 10.0; // 20.0 a 35.0°C float umidade = 40.0 + random(0, 400) / 10.0; // 40.0 a 80.0% int bateria = random(70, 101); // 70% a 100% int ramLivre = ESP.getFreeHeap() / 1024; // KB // Grava dados arquivo.printf("%lu,%02d/%02d/%04d,%02d:%02d:%02d,%.1f,%.1f,%d,%d\n", now.unixtime(), now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second(), temperatura, umidade, bateria, ramLivre); arquivo.close(); Serial.printf("Dados gravados: %02d/%02d/%04d %02d:%02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second()); Serial.println(""); } void atualizaDisplay() { display.clear(); // Status dos módulos display.drawString(0, 0, "Status Sistema:"); display.drawString(0, 12, rtcOk ? "RTC: OK ✓" : "RTC: ERRO ✗"); display.drawString(64, 12, sdOk ? "SD: OK ✓" : "SD: ERRO ✗"); // Se RTC OK, mostra data/hora if (rtcOk) { DateTime now = rtc.now(); //char dataHora[32]; sprintf(dataHora, "%02d/%02d/%04d %02d:%02d:%02d", now.day(), now.month(), now.year(), now.hour(), now.minute(), now.second()); display.drawString(0, 26, dataHora); } if (showNetworkInfo) { String boiaState = lastReceived == "1" ? "alto" : "baixo"; if (!comunicacaoLora) { display.drawString(0, 38, "Boia sem comunicacao"); } else { display.drawString(0, 38, "Boia em nivel " + boiaState); } // Mostra Wi-Fi e IP if (WiFi.status() == WL_CONNECTED) { //display.drawString(0, 26, "WiFi: " + WiFi.SSID()); display.drawString(0, 50, "IP: " + WiFi.localIP().toString()); } } else { if (WiFi.status() == WL_CONNECTED) { display.drawString(0, 50, "REDE: " + WiFi.SSID()); } char flowText[32]; sprintf(flowText, "Fluxo: %.1f L/min", flowRate); display.drawString(0, 38, flowText); } char flowText[32]; sprintf(flowText, "Fluxo: %.1f L/min", flowRate); //Serial.println(flowText); display.display(); boiaState = ""; //lastReceived = ""; } // Funções auxiliares para ajustar RTC manualmente void ajustarRTC(int ano, int mes, int dia, int hora, int minuto, int segundo) { rtc.adjust(DateTime(ano, mes, dia, hora, minuto, segundo)); Serial.printf("RTC ajustado para: %02d/%02d/%04d %02d:%02d:%02d\n", dia, mes, ano, hora, minuto, segundo); } // Função para listar arquivos do SD void listarArquivos() { if (!sdOk) return; Serial.println("\n--- ARQUIVOS NO SD CARD ---"); File root = SD.open("/"); File arquivo = root.openNextFile(); while (arquivo) { if (!arquivo.isDirectory()) { Serial.printf("%-20s %8d bytes\n", arquivo.name(), arquivo.size()); } arquivo = root.openNextFile(); } Serial.printf("Espaço total: %llu MB\n", SD.totalBytes() / (1024 * 1024)); Serial.printf("Espaço usado: %llu MB\n", SD.usedBytes() / (1024 * 1024)); }