/* Sistema NÉCTAR - ESP32 (standalone + webserver) * Versão: Estabilidade Máxima (FreeRTOS, AsyncWebServer) * * Otimizações Chave: * 1. Tarefas FreeRTOS separadas para Áudio, Display e Sensores, * eliminando conflitos e travamentos. * 2. Webserver Assíncrono para acesso rápido via Wi-Fi sem bloquear o sistema. * 3. Sensor de Solo completo: usa porta Analógica (AO 34) e Digital (DO 36). * 4. Inicialização de display imediata, independentemente do status do Wi-Fi. * * Mapeamento: * - I2C (SDA/SCL): GPIO 21 / GPIO 22 * - Sensores Lentos: BME280 (I2C), AHTX0 (I2C) * - Display: LCD 1602 (I2C - Alimentado a 5V) e OLED 128x64 (I2C) */ // --- Bibliotecas #include #include #include // Bibliotecas Assíncronas (Requer ESPAsyncWebServer e AsyncTCP no platformio.ini) #include "ESPAsyncWebServer.h" #include "AsyncTCP.h" // Bibliotecas I2C #include #include #include #include // FreeRTOS #include "freertos/FreeRTOS.h" #include "freertos/task.h" // --- WiFi (use suas credenciais) const char* ssid = "firefox"; const char* password = "firefox2025"; // --- Webserver Assíncrono AsyncWebServer server(80); // --- Displays LiquidCrystal_I2C lcd(0x27, 16, 2); #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1); // --- Sensores Pinos const int PIN_SOLO = 34; // AO sensor umidade solo (AOUT) const int PIN_LDR = 35; // AO LDR divisor const int PIN_SOLO_DO = 36; // DO sensor umidade solo (Digital Input Only) const int PIN_CHUVA_DO = 32; // DO do sensor chuva (digital) const int PIN_PIR = 27; // PIR const int PIN_BOTAO_PESAGEM = 13; // botão (INPUT_PULLUP) // --- LEDs Pinos const int LED_AZUL = 2; const int LED_VERDE = 4; const int LED_AMARELO = 16; const int LED_VERMELHO = 17; // --- BME (colmeia) e AHT10 (agrofloresta) Adafruit_BME280 bmeNinho; // endereço 0x76 Adafruit_AHTX0 ahtAgro; // --- Variáveis de Cache (Compartilhadas entre Tasks) // Variáveis voláteis são usadas para garantir que o compilador não otimize o acesso // e que os dados sejam lidos da memória correta entre as diferentes tasks. volatile bool sensores_iniciados = false; volatile float tN_cache = 0.0, hN_cache = 0.0, pN_cache = 0.0; // Ninho volatile float tA_cache = 0.0, hA_cache = 0.0; // Agro volatile int soloVal_cache = 0, ldrVal_cache = 0; volatile int soloDO_cache = 1; // 1 = Sem umidade critica volatile bool pir_cache = false; volatile bool botPes_cache = false; // --- Paginação e Estados volatile int pagina = 0; volatile bool lcdMostraNinho = true; volatile unsigned long ultimaTrocaOLED = 0; volatile unsigned long ultimaTrocaLCD = 0; const unsigned long INTERVAL_OLED = 5000; // 5s const unsigned long INTERVAL_LCD = 10000; // 10s // --- Buffer para gráfico simples de "som" (simulado com LDR) const int NBINS = 8; int bins[NBINS]; // valores 0..height const int GRAPH_H = 40; // altura máxima do gráfico (px) // --- IP string String localIPstr = "Aguardando IP..."; // --- Declaração de Protótipos das Funções String makeHTML(); void task_Audio(void* pvParameters); void task_DisplayUpdate(void* pvParameters); void task_SensorReadings(void* pvParameters); void drawSoundBars(); // --- Funções Auxiliares de Status String soloStatus(int v) { if (v > 3000) return "Seco"; else if (v > 1500) return "Umido"; else return "Molhado"; } String chuvaStatus(int v) { if (v == HIGH) return "Sem Chuva"; else return "Chuva"; } String luzStatus(int v) { if (v < 1000) return "Escuro"; else if (v < 3000) return "Meia-luz"; else return "Sol Forte"; } String soloDOStatus(int v) { if (v == HIGH) return "OK"; else return "CRITICO"; // Umidade critica atingida no potenciometro DO } // --- Webserver: Funções Assíncronas void onIndexRequest(AsyncWebServerRequest *request) { // A função makeHTML() agora lê as variáveis de cache volatile request->send(200, "text/html", makeHTML()); } // --- Geração do HTML (Webserver Assíncrono) String makeHTML() { String html = "Sistema NÉCTAR"; html += ""; // Usando classes Tailwind para um visual moderno e responsivo html += ""; // Dados Colmeia html += "
"; html += "

COLMEIA (NINHO)

"; html += "

Temperatura: " + String(tN_cache, 1) + " °C

"; html += "

Umidade: " + String(hN_cache, 0) + " %

"; html += "

Pressão: " + String(pN_cache, 0) + " hPa

"; html += "

Movimento (PIR): " + String(pir_cache ? "ATIVO" : "Parado") + "

"; html += "

Pesagem (Botão): " + String(botPes_cache ? "Pesando" : "Normal") + "

"; html += "
"; // Dados Agrofloresta html += "
"; html += "

AGROFLORESTA

"; html += "

Temperatura: " + String(tA_cache, 1) + " °C

"; html += "

Umidade: " + String(hA_cache, 0) + " %

"; html += "

Umidade Solo (AO): " + soloStatus(soloVal_cache) + " (" + String(soloVal_cache) + ")

"; html += "

Umidade Solo (DO): " + soloDOStatus(soloDO_cache) + "

"; html += "

Luz: " + luzStatus(ldrVal_cache) + " (" + String(ldrVal_cache) + ")

"; html += "

Chuva: " + chuvaStatus(digitalRead(PIN_CHUVA_DO)) + "

"; html += "
"; // Status e Rede html += "
"; html += "

STATUS E REDE

"; html += "

IP Local: " + localIPstr + "

"; html += "

LED Status: " + (digitalRead(LED_VERMELHO) ? "CRITICO" : (digitalRead(LED_AMARELO) ? "ATENCAO" : "OK")) + "

"; html += "
"; html += ""; return html; } // --- I2S: Funções do Gráfico de Som (Simulado) void readAudioComputeBins() { // Simula a intensidade sonora usando o LDR como fonte de variância // (Embora o I2S tenha sido removido, mantemos a função para preencher o gráfico) int ldr = analogRead(PIN_LDR); int noiseLevel = map(ldr, 0, 4095, 0, 80); // Mapeia o LDR para uma intensidade de ruído for (int b = 0; b < NBINS; b++) { // Adiciona um pouco de aleatoriedade para parecer mais 'vivo' int variation = random(-5, 5); int level = constrain(noiseLevel + variation, 0, GRAPH_H); bins[b] = level; } } // --- Helper: desenha gráfico de barras (bins) na OLED void drawSoundBars() { int barW = SCREEN_WIDTH / NBINS; for (int i = 0; i < NBINS; i++) { int x = i * barW; int h = bins[i]; int yTop = SCREEN_HEIGHT - 1 - h; oled.fillRect(x + 1, yTop, barW - 2, h, SSD1306_WHITE); } }