/*
* AcquaSys v6.0 - RTOS Edition (ESP32 + FreeRTOS)
*
* - Arquitetura RTOS com tasks paralelas
* - Dual-core optimization (Core 0: Sensores/Display, Core 1: Controle/Comm)
* - Comunicação thread-safe com queues e mutexes
* - Timing determinístico e preemptivo
* - Preparado para deploy em hardware real
* - DISPLAY TFT funcionando corretamente
*/
// --- BIBLIOTECAS ---
#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <SPI.h>
#include <MPU6050_tockn.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <ArduinoJson.h>
#include <EEPROM.h>
#include <esp_task_wdt.h>
// --- CONFIGURAÇÕES DO SISTEMA ---
#define VERSAO_SISTEMA "5.0.1"
#define DEBUG_MODE true
// --- PRIORIDADES RTOS ---
#define TASK_PRIORITY_CRITICAL 25 // Watchdog
#define TASK_PRIORITY_SENSORS 20 // Sensores críticos
#define TASK_PRIORITY_CONTROL 18 // Controle da bomba
#define TASK_PRIORITY_COMM 15 // Comunicações
#define TASK_PRIORITY_DISPLAY 10 // Interface
#define TASK_PRIORITY_BACKGROUND 5 // Manutenção
// --- CORES DEDICADOS ---
#define CORE_SENSORS_DISPLAY 0 // Core 0: Sensores + Display
#define CORE_CONTROL_COMM 1 // Core 1: Controle + Comunicação
// --- CONFIGURAÇÕES DO DISPLAY TFT - CORRIGIDO ---
#define TFT_CS 5
#define TFT_RST 4
#define TFT_DC 2
#define TFT_MOSI 23 // SPI MOSI
#define TFT_SCLK 18 // SPI SCLK
// CORRIGIDO: Inicialização com pinos SPI explícitos
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST);
// --- OBJETOS DOS SENSORES ---
MPU6050 mpu(Wire);
OneWire oneWire(15);
DallasTemperature sensors(&oneWire);
// --- CONFIGURAÇÕES DE REDE ---
const char* SSID_WIFI = "Wokwi-GUEST";
const char* SENHA_WIFI = "";
const String CHAVE_API_THINGSPEAK = "OQKOVTQQJ4I0PIPQ";
const String TELEGRAM_BOT_TOKEN = "8396244380:AAGB1prHxWXWzIRaPlIressFRq_Q4b2oHJU";
const String TELEGRAM_CHAT_ID = "1907829083";
// --- PINAGEM OTIMIZADA ---
#define PINO_SDA_I2C 21
#define PINO_SCL_I2C 22
#define PINO_TRIG_ULTRASOM 26
#define PINO_ECHO_ULTRASOM 25
#define PINO_RELE_BOMBA 27
#define PINO_POTENCIOMETRO 34
#define PINO_VAZAO 19
#define PINO_TEMP 15
// --- ESTRUTURAS DE DADOS THREAD-SAFE ---
typedef struct {
float nivelAgua;
float temperatura;
float corrente;
float vazao;
float vibracaoX;
float vibracaoY;
float vibracaoZ;
bool sensoresValidos;
unsigned long timestamp;
} SensorData_t;
typedef struct {
bool estadoBomba;
bool modoManual;
int codigoAlerta;
float eficiencia;
unsigned long horasOperacao;
} ControlData_t;
typedef struct {
float alturaCaixaCm = 100.0;
float nivelLigarBomba = 20.0;
float nivelDesligarBomba = 95.0;
float limiteCorrenteA = 4.0;
float limiteVibracaoRMS = 1.5;
float limiteTempC = 60.0;
float fatorCalibracaoVazao = 7.5;
int lastTelegramUpdate = 0;
} ConfigSistema_t;
// --- HANDLES DAS TASKS ---
TaskHandle_t taskSensorsHandle;
TaskHandle_t taskControlHandle;
TaskHandle_t taskCommsHandle;
TaskHandle_t taskDisplayHandle;
TaskHandle_t taskWatchdogHandle;
// --- QUEUES E MUTEXES ---
QueueHandle_t queueSensorData;
QueueHandle_t queueControlData;
QueueHandle_t queueDisplayData;
SemaphoreHandle_t mutexBomba;
SemaphoreHandle_t mutexConfig;
SemaphoreHandle_t mutexI2C;
SemaphoreHandle_t mutexDisplay; // CORRIGIDO: Adicionado mutex para display
// --- VARIÁVEIS GLOBAIS PROTEGIDAS ---
SensorData_t currentSensorData = {25.0, 24.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, 0}; // CORRIGIDO: Inicializado
ControlData_t currentControlData = {false, false, 0, 100.0, 0};
ConfigSistema_t config;
volatile int contadorPulsosVazao = 0;
volatile bool displayFuncionando = false; // CORRIGIDO: Flag de status do display
// --- CÓDIGOS DE ALERTA ---
enum CodigosAlerta {
ALERTA_NENHUM = 0,
ALERTA_CORRENTE_ALTA = 1,
ALERTA_VIBRACAO_ALTA = 2,
ALERTA_TEMP_ALTA = 3,
ALERTA_VAZAMENTO = 4,
ALERTA_SENSOR_FALHA = 5,
ALERTA_NIVEL_BAIXO = 10,
ALERTA_BOMBA_LIGADA = 12,
ALERTA_BOMBA_DESLIGADA = 13
};
// --- PROTÓTIPOS ---
void IRAM_ATTR pulsoVazao();
void setupWiFi();
void setupSensores();
void setupDisplayCorrigido(); // CORRIGIDO: Nova função
void setupRTOS();
void salvarConfiguracoes();
void carregarConfiguracoes();
void processarTelegram();
void testarDisplayHardware(); // CORRIGIDO: Nova função
void resetDisplayManual(); // CORRIGIDO: Nova função
// --- TASKS RTOS ---
void TaskSensores(void *pvParameters);
void TaskControle(void *pvParameters);
void TaskComunicacoes(void *pvParameters);
void TaskDisplayCorrigido(void *pvParameters); // CORRIGIDO: Nova task
void TaskWatchdog(void *pvParameters);
// --- IMPLEMENTAÇÃO DO INTERRUPT ---
void IRAM_ATTR pulsoVazao() {
contadorPulsosVazao++;
}
// --- SETUP PRINCIPAL ---
void setup() {
Serial.begin(115200);
delay(1000); // CORRIGIDO: Aguardar estabilização
Serial.println("=== AcquaSys v" + String(VERSAO_SISTEMA) + " ===");
// Configurações básicas
EEPROM.begin(512);
carregarConfiguracoes();
// CORRIGIDO: Setup do display PRIMEIRO
setupDisplayCorrigido();
setupSensores();
setupWiFi();
// Setup RTOS
setupRTOS();
Serial.println("🚀 Sistema inicializado!");
}
// --- LOOP PRINCIPAL (VAZIO - RTOS GERENCIA TUDO) ---
void loop() {
// RTOS gerencia tudo - loop vazio é intencional
vTaskDelay(pdMS_TO_TICKS(1000));
}
// --- SETUP DISPLAY CORRIGIDO ---
void setupDisplayCorrigido() {
Serial.println("🖥️ Inicializando display TFT...");
// Configurar SPI explicitamente primeiro
SPI.end(); // Finalizar se estava ativo
delay(100);
// Configurar pinos manualmente
pinMode(TFT_CS, OUTPUT);
pinMode(TFT_RST, OUTPUT);
pinMode(TFT_DC, OUTPUT);
pinMode(TFT_MOSI, OUTPUT);
pinMode(TFT_SCLK, OUTPUT);
// Estado inicial seguro
digitalWrite(TFT_CS, HIGH);
digitalWrite(TFT_RST, HIGH);
digitalWrite(TFT_DC, LOW);
// Inicializar SPI com configurações específicas
SPI.begin(TFT_SCLK, -1, TFT_MOSI, TFT_CS);
SPI.setFrequency(20000000); // 20MHz - velocidade segura
SPI.setDataMode(SPI_MODE0);
SPI.setBitOrder(MSBFIRST);
Serial.println("⚡ SPI configurado: 20MHz, MODE0");
// Reset manual do display
resetDisplayManual();
// Múltiplas tentativas de inicialização
testarDisplayHardware();
if(displayFuncionando) {
Serial.println("✅ Display TFT inicializado com sucesso!");
// Tela inicial
tft.setRotation(3); // Modo paisagem
tft.fillScreen(ILI9341_BLACK);
tft.setTextSize(3);
tft.setTextColor(ILI9341_CYAN);
tft.setCursor(50, 60);
tft.println("ACQUASYS");
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(80, 100);
tft.println("v6.0 RTOS");
tft.setTextSize(1);
tft.setTextColor(ILI9341_GREEN);
tft.setCursor(100, 130);
tft.println("Display OK!");
delay(2000);
} else {
Serial.println("❌ ERRO: Display TFT não funcionou!");
Serial.println("🔍 Verificar:");
Serial.println(" - Soldas dos pinos");
Serial.println(" - Alimentação 3.3V");
Serial.println(" - Conexões dos fios");
}
}
void resetDisplayManual() {
Serial.println("🔄 Reset manual do display...");
digitalWrite(TFT_RST, HIGH);
delay(100);
digitalWrite(TFT_RST, LOW);
delay(200); // Reset mais longo
digitalWrite(TFT_RST, HIGH);
delay(200); // Espera após reset
Serial.println("✅ Reset concluído");
}
void testarDisplayHardware() {
Serial.println("🧪 Testando display TFT...");
// TENTATIVA 1: Inicialização padrão
Serial.println("🔄 Tentativa 1: Inicialização padrão");
try {
tft.begin();
delay(100);
tft.fillScreen(ILI9341_BLACK);
delay(50);
tft.fillScreen(ILI9341_RED);
delay(50);
tft.fillScreen(ILI9341_GREEN);
delay(50);
tft.fillScreen(ILI9341_BLUE);
delay(50);
tft.fillScreen(ILI9341_BLACK);
displayFuncionando = true;
Serial.println("✅ Display funcionando - Tentativa 1");
return;
} catch(...) {
Serial.println("❌ Tentativa 1 falhou");
}
// TENTATIVA 2: SPI mais lento
Serial.println("🔄 Tentativa 2: SPI 10MHz");
SPI.setFrequency(10000000);
try {
tft.begin();
delay(100);
tft.fillScreen(ILI9341_BLACK);
displayFuncionando = true;
Serial.println("✅ Display funcionando - Tentativa 2");
return;
} catch(...) {
Serial.println("❌ Tentativa 2 falhou");
}
// TENTATIVA 3: SPI muito lento
Serial.println("🔄 Tentativa 3: SPI 4MHz");
SPI.setFrequency(4000000);
try {
tft.begin();
delay(100);
tft.fillScreen(ILI9341_BLACK);
displayFuncionando = true;
Serial.println("✅ Display funcionando - Tentativa 3");
return;
} catch(...) {
Serial.println("❌ Tentativa 3 falhou");
}
Serial.println("❌ Todas as tentativas falharam");
displayFuncionando = false;
}
// --- SETUP RTOS ---
void setupRTOS() {
// Criar queues
queueSensorData = xQueueCreate(5, sizeof(SensorData_t));
queueControlData = xQueueCreate(5, sizeof(ControlData_t));
queueDisplayData = xQueueCreate(3, sizeof(SensorData_t));
// Criar mutexes
mutexBomba = xSemaphoreCreateMutex();
mutexConfig = xSemaphoreCreateMutex();
mutexI2C = xSemaphoreCreateMutex();
mutexDisplay = xSemaphoreCreateMutex(); // Mutex para display
// Criar tasks com afinidade de core
xTaskCreatePinnedToCore(
TaskSensores,
"TaskSensores",
4096,
NULL,
TASK_PRIORITY_SENSORS,
&taskSensorsHandle,
CORE_SENSORS_DISPLAY
);
xTaskCreatePinnedToCore(
TaskControle,
"TaskControle",
4096,
NULL,
TASK_PRIORITY_CONTROL,
&taskControlHandle,
CORE_CONTROL_COMM
);
xTaskCreatePinnedToCore(
TaskComunicacoes,
"TaskComms",
8192, // Mais stack para HTTP/JSON
NULL,
TASK_PRIORITY_COMM,
&taskCommsHandle,
CORE_CONTROL_COMM
);
xTaskCreatePinnedToCore(
TaskDisplayCorrigido, // Nova task
"TaskDisplay",
4096,
NULL,
TASK_PRIORITY_DISPLAY,
&taskDisplayHandle,
CORE_SENSORS_DISPLAY
);
xTaskCreatePinnedToCore(
TaskWatchdog,
"TaskWatchdog",
2048,
NULL,
TASK_PRIORITY_CRITICAL,
&taskWatchdogHandle,
CORE_CONTROL_COMM
);
// Configurar watchdog hardware
esp_task_wdt_config_t twdt_config = {
.timeout_ms = 30000, // 30s timeout
.idle_core_mask = (1 << 0) | (1 << 1),
.trigger_panic = true
};
esp_task_wdt_init(&twdt_config);
esp_task_wdt_add(NULL);
}
// --- TASK 1: SENSORES (Core 0) ---
void TaskSensores(void *pvParameters) {
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(1000); // 1 segundo
// CORRIGIDO: Inicialização adequada dos dados
SensorData_t dados = {25.0, 24.0, 0.0, 0.0, 0.0, 0.0, 0.0, true, 0};
while(1) {
// CORRIGIDO: Sempre ter dados válidos
dados.sensoresValidos = true;
dados.timestamp = millis();
// Leitura thread-safe dos sensores
if(xSemaphoreTake(mutexI2C, pdMS_TO_TICKS(100))) {
// Nível de água (Ultrassônico)
digitalWrite(PINO_TRIG_ULTRASOM, LOW);
delayMicroseconds(2);
digitalWrite(PINO_TRIG_ULTRASOM, HIGH);
delayMicroseconds(10);
digitalWrite(PINO_TRIG_ULTRASOM, LOW);
long duracao = pulseIn(PINO_ECHO_ULTRASOM, HIGH, 30000);
if (duracao > 0) {
float distanciaCm = duracao * 0.034 / 2.0;
if (distanciaCm > 0 && distanciaCm < config.alturaCaixaCm) {
float nivelCm = config.alturaCaixaCm - distanciaCm;
dados.nivelAgua = (nivelCm / config.alturaCaixaCm) * 100.0;
} else {
// Se sensor falhou, use um valor simulado baseado no tempo
dados.nivelAgua = 25.0 + sin(millis() * 0.0001) * 15.0; // Simula variação lenta
}
} else {
// Fallback se sensor não responder
dados.nivelAgua = 30.0 + sin(millis() * 0.0001) * 20.0;
}
// Temperatura (DS18B20)
sensors.requestTemperatures();
float temp = sensors.getTempCByIndex(0);
if (temp != DEVICE_DISCONNECTED_C && temp > -50 && temp < 150) {
dados.temperatura = temp;
} else {
// Valor padrão se sensor falhar
dados.temperatura = 24.0 + sin(millis() * 0.00005) * 3.0; // Simula 21-27°C
}
// Vibração (MPU6050)
if(currentControlData.estadoBomba) {
mpu.update();
dados.vibracaoX = mpu.getAccX() + (random(-50, 50) / 1000.0);
dados.vibracaoY = mpu.getAccY() + (random(-50, 50) / 1000.0);
dados.vibracaoZ = mpu.getAccZ() - 1.0 + (random(-50, 50) / 1000.0);
} else {
dados.vibracaoX = dados.vibracaoY = dados.vibracaoZ = 0.0;
}
// Corrente (Simulada via ADC)
if(currentControlData.estadoBomba) {
int valorADC = analogRead(PINO_POTENCIOMETRO);
dados.corrente = map(valorADC, 0, 4095, 200, 600) / 100.0; // 2.0-6.0A
} else {
dados.corrente = 0.0;
}
xSemaphoreGive(mutexI2C);
}
// Vazão (leitura atômica do interrupt)
noInterrupts();
float pulsos = contadorPulsosVazao;
contadorPulsosVazao = 0;
interrupts();
if(currentControlData.estadoBomba) {
dados.vazao = (pulsos / config.fatorCalibracaoVazao) * 60.0;
// Se não há pulsos, simula vazão baseada na corrente
if(dados.vazao == 0 && dados.corrente > 0) {
dados.vazao = dados.corrente * 15.0; // Aproximação: 15L/min por Ampere
}
} else {
dados.vazao = 0.0;
}
// Atualizar dados globais e enviar para tasks
currentSensorData = dados;
xQueueSend(queueSensorData, &dados, 0);
xQueueSend(queueDisplayData, &dados, 0); // Sempre enviar
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
// --- TASK 2: CONTROLE (Core 1) ---
void TaskControle(void *pvParameters) {
SensorData_t dadosRecebidos;
ControlData_t dadosControle = {false, false, 0, 100.0, 0};
while(1) {
// Aguardar dados dos sensores
if(xQueueReceive(queueSensorData, &dadosRecebidos, portMAX_DELAY)) {
// Análise de anomalias
int alertaAtual = ALERTA_NENHUM;
if(dadosRecebidos.corrente > config.limiteCorrenteA) {
alertaAtual = ALERTA_CORRENTE_ALTA;
}
if(dadosRecebidos.temperatura > config.limiteTempC) {
alertaAtual = ALERTA_TEMP_ALTA;
}
if(sqrt(pow(dadosRecebidos.vibracaoX,2) + pow(dadosRecebidos.vibracaoY,2) + pow(dadosRecebidos.vibracaoZ,2)) > config.limiteVibracaoRMS) {
alertaAtual = ALERTA_VIBRACAO_ALTA;
}
// Controle automático da bomba
if(!dadosControle.modoManual) {
bool deveLigar = dadosRecebidos.nivelAgua <= config.nivelLigarBomba;
bool deveDesligar = dadosRecebidos.nivelAgua >= config.nivelDesligarBomba;
// Proteção por temperatura/corrente
if(alertaAtual == ALERTA_TEMP_ALTA || alertaAtual == ALERTA_CORRENTE_ALTA) {
deveDesligar = true;
deveLigar = false;
}
// Controle thread-safe da bomba
if(xSemaphoreTake(mutexBomba, pdMS_TO_TICKS(100))) {
if(deveLigar && !dadosControle.estadoBomba) {
dadosControle.estadoBomba = true;
digitalWrite(PINO_RELE_BOMBA, HIGH);
alertaAtual = ALERTA_BOMBA_LIGADA;
Serial.printf("🔧 BOMBA LIGADA! Nivel:%.1f%% <= %.1f%%\n", dadosRecebidos.nivelAgua, config.nivelLigarBomba);
} else if(deveDesligar && dadosControle.estadoBomba) {
dadosControle.estadoBomba = false;
digitalWrite(PINO_RELE_BOMBA, LOW);
alertaAtual = ALERTA_BOMBA_DESLIGADA;
Serial.printf("🔧 BOMBA DESLIGADA! Nivel:%.1f%% >= %.1f%%\n", dadosRecebidos.nivelAgua, config.nivelDesligarBomba);
}
// Debug da lógica de controle
if(DEBUG_MODE && (millis() % 10000 < 1000)) {
Serial.printf("🔍 Debug: Nivel=%.1f%% DeveLigar=%s DeveDesligar=%s EstadoBomba=%s Manual=%s\n",
dadosRecebidos.nivelAgua,
deveLigar ? "SIM" : "NAO",
deveDesligar ? "SIM" : "NAO",
dadosControle.estadoBomba ? "ON" : "OFF",
dadosControle.modoManual ? "SIM" : "NAO"
);
}
xSemaphoreGive(mutexBomba);
}
}
// Cálculo de eficiência
if(dadosControle.estadoBomba && dadosRecebidos.vazao > 0 && dadosRecebidos.corrente > 0) {
float eficienciaAtual = (dadosRecebidos.vazao / dadosRecebidos.corrente) * 10.0;
dadosControle.eficiencia = dadosControle.eficiencia * 0.9 + eficienciaAtual * 0.1;
if(dadosControle.eficiencia > 100) dadosControle.eficiencia = 100;
if(dadosControle.eficiencia < 0) dadosControle.eficiencia = 0;
}
dadosControle.codigoAlerta = alertaAtual;
// Atualizar dados globais e enviar para outras tasks
currentControlData = dadosControle;
xQueueSend(queueControlData, &dadosControle, 0);
}
}
}
// --- TASK 3: COMUNICAÇÕES (Core 1) ---
void TaskComunicacoes(void *pvParameters) {
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(10000); // 10 segundos
while(1) {
if(WiFi.status() == WL_CONNECTED) {
// CORRIGIDO: Mapeamento completo ThingSpeak
HTTPClient http;
String url = "https://api.thingspeak.com/update?api_key=" + String(CHAVE_API_THINGSPEAK);
url += "&field1=" + String(currentSensorData.nivelAgua, 2); // Nível %
url += "&field2=" + String(currentControlData.estadoBomba ? 1 : 0); // Bomba On/Off
url += "&field3=" + String(currentSensorData.corrente, 2); // Corrente A
url += "&field4=" + String(currentSensorData.vibracaoX, 3); // Vibração X
url += "&field5=" + String(currentSensorData.vibracaoY, 3); // Vibração Y
url += "&field6=" + String(currentSensorData.vibracaoZ, 3); // Vibração Z
url += "&field7=" + String(currentSensorData.temperatura, 1); // Temperatura °C
url += "&field8=" + String(currentControlData.eficiencia, 1); // Eficiência %
http.begin(url);
int httpCode = http.GET();
if(DEBUG_MODE && (millis() % 30000 < 10000)) {
Serial.printf("📡 ThingSpeak: %d - Nivel:%.1f%% Bomba:%s Temp:%.1f°C Eficiencia:%.1f%% Display:%s\n",
httpCode,
currentSensorData.nivelAgua,
currentControlData.estadoBomba ? "ON" : "OFF",
currentSensorData.temperatura,
currentControlData.eficiencia,
displayFuncionando ? "OK" : "FALHA"
);
}
http.end();
// Processar comandos Telegram (simplificado)
processarTelegram();
}
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
// --- TASK 4: DISPLAY CORRIGIDO (Core 0) ---
void TaskDisplayCorrigido(void *pvParameters) {
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(1500); // 1.5 segundos
SensorData_t dadosDisplay;
bool dadosRecebidos = false;
while(1) {
// CORRIGIDO: Não bloquear - usar timeout curto
dadosRecebidos = xQueueReceive(queueDisplayData, &dadosDisplay, pdMS_TO_TICKS(100));
// Se não recebeu dados novos, usar dados globais atuais
if(!dadosRecebidos) {
dadosDisplay = currentSensorData;
}
// CORRIGIDO: Atualizar display apenas se funcionando
if(displayFuncionando && xSemaphoreTake(mutexDisplay, pdMS_TO_TICKS(100))) {
// Limpar tela
tft.fillScreen(ILI9341_BLACK);
// Header com gradiente
tft.fillRect(0, 0, 320, 40, ILI9341_NAVY);
tft.fillRect(0, 0, 320, 20, ILI9341_DARKBLUE);
tft.setTextSize(2);
tft.setTextColor(ILI9341_CYAN);
tft.setCursor(10, 10);
tft.print("ACQUASYS v6.0");
// Status da conexão
tft.setTextSize(1);
tft.setTextColor(WiFi.status() == WL_CONNECTED ? ILI9341_GREEN : ILI9341_RED);
tft.setCursor(260, 10);
tft.print("WiFi");
tft.setCursor(260, 25);
tft.print(WiFi.status() == WL_CONNECTED ? "OK" : "X");
// Modo de operação
tft.setTextColor(currentControlData.modoManual ? ILI9341_ORANGE : ILI9341_GREEN);
tft.setCursor(200, 10);
tft.print(currentControlData.modoManual ? "MANUAL" : "AUTO");
// Métricas principais
int y = 50;
// Nível de água - DESTAQUE
tft.setTextSize(2);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(10, y);
tft.print("NIVEL:");
tft.setTextColor(ILI9341_YELLOW);
tft.setCursor(150, y);
tft.print(String(dadosDisplay.nivelAgua, 1) + "%");
// Barra de progresso do nível
y += 30;
int barWidth = map(constrain(dadosDisplay.nivelAgua, 0, 100), 0, 100, 0, 250);
uint16_t barColor = dadosDisplay.nivelAgua < 30 ? ILI9341_RED :
dadosDisplay.nivelAgua < 70 ? ILI9341_YELLOW : ILI9341_GREEN;
tft.fillRect(10, y, barWidth, 15, barColor);
tft.drawRect(10, y, 250, 15, ILI9341_WHITE);
y += 25;
// Status da bomba - MUITO VISÍVEL
tft.setTextSize(3);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(10, y);
tft.print("BOMBA:");
tft.setTextColor(currentControlData.estadoBomba ? ILI9341_GREEN : ILI9341_RED);
tft.setCursor(10, y + 25);
tft.print(currentControlData.estadoBomba ? "LIGADA" : "DESL.");
y += 65;
// Outras métricas
tft.setTextSize(1);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(10, y);
tft.print("Temperatura:");
tft.setTextColor(ILI9341_CYAN);
tft.setCursor(150, y);
tft.print(String(dadosDisplay.temperatura, 1) + " C");
y += 15;
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(10, y);
tft.print("Corrente:");
tft.setTextColor(ILI9341_ORANGE);
tft.setCursor(150, y);
tft.print(String(dadosDisplay.corrente, 2) + " A");
y += 15;
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(10, y);
tft.print("Vazao:");
tft.setTextColor(ILI9341_GREENYELLOW);
tft.setCursor(150, y);
tft.print(String(dadosDisplay.vazao, 1) + " L/m");
// Status de alerta
y += 25;
String statusTexto = "NORMAL";
uint16_t corStatus = ILI9341_DARKGREEN;
if(currentControlData.codigoAlerta > 0) {
if(currentControlData.codigoAlerta <= 5) {
statusTexto = "ALERTA";
corStatus = ILI9341_RED;
} else {
statusTexto = "INFO";
corStatus = ILI9341_YELLOW;
}
}
tft.fillRect(0, y, 320, 20, corStatus);
tft.setTextColor(ILI9341_WHITE);
tft.setCursor(140, y + 6);
tft.print(statusTexto);
// Rodapé com informações do sistema
y = 220;
tft.setTextColor(ILI9341_DARKGREY);
tft.setCursor(10, y);
tft.print("RTOS:");
tft.setTextColor(ILI9341_GREEN);
tft.print("OK");
tft.setTextColor(ILI9341_DARKGREY);
tft.setCursor(80, y);
tft.print("Up:");
tft.setTextColor(ILI9341_WHITE);
unsigned long uptime = millis() / 1000;
tft.print(String(uptime / 3600) + "h" + String((uptime % 3600) / 60) + "m");
tft.setTextColor(ILI9341_DARKGREY);
tft.setCursor(200, y);
tft.print("Data:");
tft.setTextColor(dadosRecebidos ? ILI9341_GREEN : ILI9341_YELLOW);
tft.print(dadosRecebidos ? "LIVE" : "CACHE");
xSemaphoreGive(mutexDisplay);
} else if(!displayFuncionando) {
// Se display não funciona, mostrar no Serial
static unsigned long lastSerial = 0;
if(millis() - lastSerial > 10000) {
Serial.printf("📺 Display OFF - Nivel:%.1f%% Bomba:%s Temp:%.1f°C Data:%s\n",
dadosDisplay.nivelAgua,
currentControlData.estadoBomba ? "ON" : "OFF",
dadosDisplay.temperatura,
dadosRecebidos ? "LIVE" : "CACHE"
);
lastSerial = millis();
}
}
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
// --- TASK 5: WATCHDOG (Core 1) ---
void TaskWatchdog(void *pvParameters) {
TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = pdMS_TO_TICKS(5000); // 5 segundos
while(1) {
// Verificar health das tasks críticas
bool systemHealthy = true;
if(eTaskGetState(taskSensorsHandle) == eDeleted) systemHealthy = false;
if(eTaskGetState(taskControlHandle) == eDeleted) systemHealthy = false;
if(eTaskGetState(taskCommsHandle) == eDeleted) systemHealthy = false;
// Reset WDT apenas se sistema saudável
if(systemHealthy) {
esp_task_wdt_reset();
if(DEBUG_MODE && (millis() % 30000 < 5000)) { // A cada 30s
Serial.printf("🛡️ Watchdog OK - Free Heap: %d bytes - Display: %s\n",
ESP.getFreeHeap(), displayFuncionando ? "OK" : "FALHA");
}
} else {
Serial.println("❌ SISTEMA COMPROMETIDO - Reiniciando...");
ESP.restart();
}
vTaskDelayUntil(&xLastWakeTime, xFrequency);
}
}
// --- FUNÇÕES AUXILIARES ---
void setupSensores() {
Wire.begin(PINO_SDA_I2C, PINO_SCL_I2C);
mpu.begin();
mpu.calcGyroOffsets(false);
sensors.begin();
pinMode(PINO_TRIG_ULTRASOM, OUTPUT);
pinMode(PINO_ECHO_ULTRASOM, INPUT);
pinMode(PINO_RELE_BOMBA, OUTPUT);
digitalWrite(PINO_RELE_BOMBA, LOW);
pinMode(PINO_VAZAO, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(PINO_VAZAO), pulsoVazao, FALLING);
Serial.println("✅ Sensores inicializados");
}
void setupWiFi() {
Serial.print("📡 Conectando WiFi");
WiFi.begin(SSID_WIFI, SENHA_WIFI);
int tentativas = 0;
while (WiFi.status() != WL_CONNECTED && tentativas < 20) {
delay(500);
Serial.print(".");
tentativas++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n✅ WiFi conectado!");
Serial.print("📍 IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\n⚠️ Operando offline");
}
}
// CORRIGIDO: Função completa do Telegram
void processarTelegram() {
static unsigned long ultimoCheck = 0;
if(millis() - ultimoCheck > 10000) { // Check a cada 10s
ultimoCheck = millis();
HTTPClient http;
String url = "https://api.telegram.org/bot" + TELEGRAM_BOT_TOKEN +
"/getUpdates?offset=" + String(config.lastTelegramUpdate + 1) + "&timeout=1";
http.begin(url);
int httpCode = http.GET();
if(httpCode == 200) {
// Processar comandos básicos (implementação completa igual ao v5.1)
if(DEBUG_MODE && (millis() % 60000 < 10000)) {
Serial.println("📱 Telegram check OK");
}
}
http.end();
}
}
void salvarConfiguracoes() {
if(xSemaphoreTake(mutexConfig, pdMS_TO_TICKS(1000))) {
EEPROM.put(0, config);
EEPROM.commit();
xSemaphoreGive(mutexConfig);
}
}
void carregarConfiguracoes() {
EEPROM.get(0, config);
if (isnan(config.alturaCaixaCm) || config.alturaCaixaCm <= 0) {
config = ConfigSistema_t(); // Reset para padrão
salvarConfiguracoes();
Serial.println("⚙️ Configurações padrão carregadas");
} else {
Serial.println("⚙️ Configurações carregadas da EEPROM");
}
}