#include <Arduino.h>
#include "esp_task_wdt.h"
#include "esp_system.h"
// =========================
// PINES
// =========================
const int flotadorCisterna = 23; // HIGH = hay agua en cisterna
const int flotadorProducto = 34; // LOW = tanque producto no lleno
const int selectorLogicaMotoCisterna = 25; // LOW=GPIO32 activo en HIGH, HIGH=GPIO32 activo en LOW
const int motoCisterna = 32; // salida configurable
const int motoOsmosis = 33; // salida normal
const int ledEstado = 2; // LED integrado
// =========================
// TIEMPOS
// =========================
//const unsigned long TIEMPO_ON = 600000UL; // 10 minutos
//const unsigned long TIEMPO_OFF = 600000UL; // 10 minutos
//const unsigned long REINICIO_1H = 3600000UL; // 1 hora
// Para pruebas rápidas en Wokwi, puedes usar esto:
const unsigned long TIEMPO_ON = 10000UL; // 10 segundos
const unsigned long TIEMPO_OFF = 10000UL; // 10 segundos
const unsigned long REINICIO_1H = 60000UL; // 1 minuto
// =========================
// FILTROS
// =========================
const int MUESTRAS = 5;
const int RETARDO_MUESTRA = 5; // ms
const unsigned long DEBOUNCE_SELECTOR = 50; // ms
// =========================
// WATCHDOG
// =========================
const uint32_t WDT_TIMEOUT_MS = 8000; // 8 segundos
// =========================
// VARIABLES
// =========================
RTC_DATA_ATTR int contadorReinicios = 0;
bool selectorEstable = LOW;
bool selectorUltimaLectura = LOW;
unsigned long selectorUltimoCambio = 0;
bool estadoCiclo = true; // cuando la condición reaparece, arranca en ON
unsigned long tiempoCambio = 0;
unsigned long inicioSistema = 0;
// =========================
// FUNCIONES
// =========================
void blinkInicio(int veces, int delayMs) {
for (int i = 0; i < veces; i++) {
digitalWrite(ledEstado, HIGH);
delay(delayMs);
digitalWrite(ledEstado, LOW);
delay(delayMs);
}
}
const char* textoResetReason(esp_reset_reason_t reason) {
switch (reason) {
case ESP_RST_UNKNOWN: return "UNKNOWN";
case ESP_RST_POWERON: return "POWERON";
case ESP_RST_EXT: return "EXTERNAL";
case ESP_RST_SW: return "SOFTWARE";
case ESP_RST_PANIC: return "PANIC";
case ESP_RST_INT_WDT: return "INT_WDT";
case ESP_RST_TASK_WDT: return "TASK_WDT";
case ESP_RST_WDT: return "OTHER_WDT";
case ESP_RST_DEEPSLEEP: return "DEEPSLEEP";
case ESP_RST_BROWNOUT: return "BROWNOUT";
case ESP_RST_SDIO: return "SDIO";
default: return "UNMAPPED";
}
}
void diagnosticoVisualReset() {
esp_reset_reason_t reason = esp_reset_reason();
switch (reason) {
case ESP_RST_POWERON:
blinkInicio(1, 400); // 1 parpadeo = encendido normal
break;
case ESP_RST_SW:
blinkInicio(2, 200); // 2 parpadeos = reinicio programado/software
break;
case ESP_RST_TASK_WDT:
blinkInicio(5, 100); // 5 parpadeos = watchdog de tarea
break;
case ESP_RST_INT_WDT:
case ESP_RST_WDT:
blinkInicio(5, 100); // 5 parpadeos = watchdog
break;
default:
blinkInicio(3, 150); // 3 parpadeos = otro error/motivo
break;
}
}
bool leerEntradaFiltrada(int pin) {
int high = 0;
int low = 0;
for (int i = 0; i < MUESTRAS; i++) {
if (digitalRead(pin) == HIGH) high++;
else low++;
delay(RETARDO_MUESTRA);
}
return (high > low);
}
void actualizarSelector() {
bool lectura = digitalRead(selectorLogicaMotoCisterna);
if (lectura != selectorUltimaLectura) {
selectorUltimaLectura = lectura;
selectorUltimoCambio = millis();
}
if (millis() - selectorUltimoCambio >= DEBOUNCE_SELECTOR) {
selectorEstable = selectorUltimaLectura;
}
}
void escribirMotoCisterna(bool activar) {
if (selectorEstable == HIGH) {
// relevador activo en LOW
digitalWrite(motoCisterna, activar ? LOW : HIGH);
} else {
// relevador activo en HIGH
digitalWrite(motoCisterna, activar ? HIGH : LOW);
}
}
void aplicarSalidas(bool activar) {
escribirMotoCisterna(activar);
digitalWrite(motoOsmosis, activar ? HIGH : LOW);
digitalWrite(ledEstado, activar ? HIGH : LOW);
}
void apagarTodo() {
aplicarSalidas(false);
}
void initWatchdog() {
esp_task_wdt_config_t wdt_config = {
.timeout_ms = WDT_TIMEOUT_MS,
.idle_core_mask = (1 << portNUM_PROCESSORS) - 1,
.trigger_panic = true
};
esp_task_wdt_init(&wdt_config);
esp_task_wdt_add(NULL);
}
void feedWatchdog() {
esp_task_wdt_reset();
}
void imprimirEstados(bool cisterna, bool producto, bool condicion, bool salidasActivas) {
Serial.print("Reinicios=");
Serial.print(contadorReinicios);
Serial.print(" | Reset=");
Serial.print(textoResetReason(esp_reset_reason()));
Serial.print(" | Cisterna=");
Serial.print(cisterna ? "HIGH" : "LOW");
Serial.print(" | Producto=");
Serial.print(producto ? "HIGH" : "LOW");
Serial.print(" | Sel25=");
Serial.print(selectorEstable ? "HIGH" : "LOW");
Serial.print(" | Modo32=");
Serial.print(selectorEstable ? "ACTIVO_LOW" : "ACTIVO_HIGH");
Serial.print(" | Condicion=");
Serial.print(condicion ? "OK" : "NO");
Serial.print(" | Ciclo=");
Serial.print(estadoCiclo ? "ON" : "OFF");
Serial.print(" | Moto32=");
Serial.print(salidasActivas ? "ON" : "OFF");
Serial.print(" | Moto33=");
Serial.println(salidasActivas ? "ON" : "OFF");
}
// =========================
// SETUP
// =========================
void setup() {
Serial.begin(115200);
delay(300);
contadorReinicios++;
pinMode(flotadorCisterna, INPUT);
pinMode(flotadorProducto, INPUT); // GPIO34 no tiene pull interno
pinMode(selectorLogicaMotoCisterna, INPUT_PULLDOWN);
pinMode(motoCisterna, OUTPUT);
pinMode(motoOsmosis, OUTPUT);
pinMode(ledEstado, OUTPUT);
selectorUltimaLectura = digitalRead(selectorLogicaMotoCisterna);
selectorEstable = selectorUltimaLectura;
selectorUltimoCambio = millis();
apagarTodo();
// Diagnóstico visual de motivo de reinicio
diagnosticoVisualReset();
initWatchdog();
inicioSistema = millis();
tiempoCambio = millis();
Serial.println("==============================================");
Serial.println("SISTEMA INICIADO");
Serial.print("Contador de reinicios: ");
Serial.println(contadorReinicios);
Serial.print("Motivo de reinicio: ");
Serial.println(textoResetReason(esp_reset_reason()));
Serial.println("Condicion de operacion:");
Serial.println("flotadorCisterna = HIGH");
Serial.println("flotadorProducto = LOW");
Serial.println("GPIO25 cambia solo la logica de GPIO32:");
Serial.println("LOW -> motoCisterna activa en HIGH");
Serial.println("HIGH -> motoCisterna activa en LOW");
Serial.println("Temporizador:");
Serial.println("10 min ON / 10 min OFF");
Serial.println("WDT activo + reinicio preventivo 1 hora");
Serial.println("==============================================");
}
// =========================
// LOOP
// =========================
void loop() {
feedWatchdog();
// Reinicio preventivo cada 1 hora
if (millis() - inicioSistema >= REINICIO_1H) {
Serial.println(">>> REINICIO PREVENTIVO DE 1 HORA <<<");
delay(100);
ESP.restart();
}
actualizarSelector();
bool cisterna = leerEntradaFiltrada(flotadorCisterna);
bool producto = leerEntradaFiltrada(flotadorProducto);
// activar solo si:
// flotadorCisterna = HIGH
// flotadorProducto = LOW
bool condicion = (cisterna == HIGH && producto == LOW);
unsigned long ahora = millis();
if (!condicion) {
estadoCiclo = true; // al volver la condición, arranca en ON
tiempoCambio = ahora;
apagarTodo();
imprimirEstados(cisterna, producto, condicion, false);
delay(100);
return;
}
// Temporizador 10 min ON / 10 min OFF
if (estadoCiclo) {
if (ahora - tiempoCambio >= TIEMPO_ON) {
estadoCiclo = false;
tiempoCambio = ahora;
Serial.println(">>> CAMBIO DE CICLO: OFF <<<");
}
} else {
if (ahora - tiempoCambio >= TIEMPO_OFF) {
estadoCiclo = true;
tiempoCambio = ahora;
Serial.println(">>> CAMBIO DE CICLO: ON <<<");
}
}
aplicarSalidas(estadoCiclo);
imprimirEstados(cisterna, producto, condicion, estadoCiclo);
delay(100);
}(click to edit)
motoCisterna
motoOsmosis
flotadorCisterna
flotadorProducto
changeLogica_flotadCist