How I AUTOMATED a GATE with an ESP32 🚪⚡ | PCB + Motor + Remote Control


How I AUTOMATED a GATE with an ESP32 🚪⚡ | PCB + Motor + Remote Control





Complete Project Code

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Preferences.h>
#include <esp_task_wdt.h> // Watchdog para recuperacion automatica 24/7

#define WDT_TIMEOUT 5 // Tiempo de reinicio si el codigo se bloquea (segundos)

// ============================== LCD Y MEMORIA ==================================
LiquidCrystal_I2C lcd(0x27, 16, 2);
Preferences memoria;

// ============================== PINES ==========================================
#define PIN_CORRIENTE 36         
#define SENSOR_CERRADO 32        
#define SENSOR_ABIERTO 35        
#define PIN_PARADA_EMERGENCIA 34 

#define PIN_PULSADOR_TOGGLE 39   // Entrada pulsador externo (Activo en LOW)
#define PIN_REMOTO_ABRIR 12
#define PIN_REMOTO_CERRAR 14
#define PIN_ON_OFF_LAMPARA 26
#define PIN_REMOTO_EMERGENCIA 27 

#define PIN_BTN_ADD 2            
#define PIN_BTN_MINUS 13         
#define PIN_BTN_OK 15            

#define PIN_MOTOR_ARRIBA 19      
#define PIN_MOTOR_ABAJO 23       
#define PIN_MOSFET_PWM 18        
#define PIN_ALARMA 17            
#define CHAPA_PUERTA 16          
#define PIN_LAMPARA 4            

// ============================== VARIABLES CONFIGURACIÓN =====================
int cfg_tRampa = 3;        
int cfg_vMax = 20;         
int cfg_tVMax = 20;        
int cfg_vMin = 10;         

bool cfg_chapaEn = true;   
int cfg_chapaOn = 500;     
int cfg_chapaOff = 800;    

bool cfg_sirenaEn = true;  
bool cfg_sirenaModoCompleto = false; 
int cfg_sirenaTOn = 3;     

int cfg_limCorriente = 4000; 
long ciclosTotales = 0;      

// ============================== CONTROL Y ESTADOS ============================
enum EstadoPuerta { 
  DETENIDO, 
  CHAPA_ACTIVA, 
  PRE_RAMPA, 
  ACELERANDO, 
  VEL_MAXIMA, 
  DESACELERANDO, 
  VEL_LENTA,
  FRENADO_EMERGENCIA,  
  PAUSA_INVERSION,
  FALLO_SENSORES
};
EstadoPuerta estadoMotor = DETENIDO;

bool motorAbriendo = false;
unsigned long tiempoInicioFase = 0;
unsigned long tiempoInicioGlobalMovimiento = 0;
unsigned long ultimaLecturaCorriente = 0;
unsigned long ultimaAccionRemoto = 0;
float corrienteFiltrada = 0;
const float FACTOR_SUAVIZADO = 0.3;

const int PWM_STOP = 0; 
int pwmActualInt = 0; 

// --- VARIABLES DE TIEMPO / ANTIAPLASTAMIENTO ---
bool causaEsSobrecorriente = false;
bool paradaForzada = false; 
unsigned long pwmAlChoque = 0;
long posicionEstimadaMs = 0; 
unsigned long ultimoTiempoCalculo = 0; 

// --- VARIABLES LÁMPARA ---
bool estadoLampara = false; 
int ultimoEstadoPinLampara = LOW;
unsigned long tiempoReboteLampara = 0;
int estadoFiltroLampara = LOW;

// --- VARIABLES SIRENA ---
bool sirenaDebeSonar = false;
unsigned long tiempoUltimoSirena = 0;
bool estadoFisicoSirena = HIGH; 

// ============================== SISTEMA MENÚ ===================================
byte flecha[] = { B10000, B11000, B11100, B11110, B11100, B11000, B10000, B00000 };
int menuContext = 0; 
int menuCursor = 0;
bool isEditing = false;
int editTempVal = 0;
bool forzarRedraw = true;
unsigned long tiempoUltimoBoton = 0;
const unsigned long intervaloBoton = 200; 
int estadoAnteriorOK = HIGH; 
unsigned long ultimoRefrescoTest = 0;

String menuPrin[] = {"T. RAMP. VEL M.", "CHAPA", "SIRENA", "LIM. CORRIENTE", "TEST / INFO", "ATRAS"};
String menuTRamp[] = {"TIEMPO RAMPA", "VELOCIDAD MAX", "T. VEL MAX", "VELOCIDAD MIN", "ATRAS"};
String menuChapa[] = {"HABILITAR/DESHA", "TIEMPO ON", "TIEMPO OFF", "ATRAS"};
String menuSirena[] = {"HABILITAR/DESHA", "MODO", "TIEMPO ON Inic", "ATRAS"};
String menuTestInfo[] = {"TEST", "INFO", "ATRAS"};

// ============================== PROTOTIPOS =====================================
void cargarConfiguracion();
void guardarConfiguracion();
void maquinaMotor();
void iniciarMovimiento(bool esApertura);
void detenerSistema(String mensaje);
void iniciarFrenado(bool esSobrecorriente);
void fijarPWM(int pwm);
void gestionarBotones();
void dibujarMenu();
void mostrarSubMenu(int cursor, String menus[], int size);
int mapPWM(int vel_0_25);
void activarMotorDireccion(bool abriendo);

// ============================== SETUP ==========================================
void setup() {
  Serial.begin(115200);

  // 1. Apagado Físico Preventivo antes de configurar salidas
  pinMode(PIN_MOTOR_ARRIBA, OUTPUT); digitalWrite(PIN_MOTOR_ARRIBA, LOW);
  pinMode(PIN_MOTOR_ABAJO, OUTPUT);  digitalWrite(PIN_MOTOR_ABAJO, LOW);
  pinMode(PIN_MOSFET_PWM, OUTPUT);   analogWrite(PIN_MOSFET_PWM, 0);
  pinMode(CHAPA_PUERTA, OUTPUT);     digitalWrite(CHAPA_PUERTA, HIGH);
  pinMode(PIN_ALARMA, OUTPUT);       digitalWrite(PIN_ALARMA, HIGH);
  pinMode(PIN_LAMPARA, OUTPUT);      digitalWrite(PIN_LAMPARA, HIGH);

  // 2. Configuración de Entradas
  pinMode(PIN_CORRIENTE, INPUT);
  pinMode(SENSOR_CERRADO, INPUT_PULLUP);
  pinMode(SENSOR_ABIERTO, INPUT_PULLUP);
  pinMode(PIN_PARADA_EMERGENCIA, INPUT_PULLUP);
  
  pinMode(PIN_PULSADOR_TOGGLE, INPUT); // Entrada pulsador externo D39
  pinMode(PIN_REMOTO_ABRIR, INPUT);
  pinMode(PIN_REMOTO_CERRAR, INPUT);
  pinMode(PIN_REMOTO_EMERGENCIA, INPUT);
  pinMode(PIN_ON_OFF_LAMPARA, INPUT);
  
  pinMode(PIN_BTN_ADD, INPUT_PULLUP); 
  pinMode(PIN_BTN_MINUS, INPUT_PULLUP);
  pinMode(PIN_BTN_OK, INPUT_PULLUP);

  cargarConfiguracion();

  // 3. Inicialización del Watchdog (Compatible con ESP32 Core v3.x)
  esp_task_wdt_config_t twdt_config;
  twdt_config.timeout_ms = WDT_TIMEOUT * 1000;
  twdt_config.idle_core_mask = (1 << portNUM_PROCESSORS) - 1;
  twdt_config.trigger_panic = true;

  esp_task_wdt_init(&twdt_config);
  esp_task_wdt_add(NULL);

  lcd.init();
  lcd.backlight();
  lcd.createChar(0, flecha);
  lcd.setCursor(0, 0);
  lcd.print("SISTEMA 24/7 ");
  lcd.setCursor(0, 1);
  lcd.print("INICIANDO...    ");
  
  delay(1200); 
  estadoAnteriorOK = digitalRead(PIN_BTN_OK);
  menuContext = 0; 
  lcd.clear();
}

// ============================== LOOP PRINCIPAL =================================
void loop() {
  esp_task_wdt_reset();

  unsigned long actualMillis = millis();

  // --- COMPROBACIÓN DE SEGURIDAD: SENSORES CONTRADICTORIOS ---
  bool senCerrado = (digitalRead(SENSOR_CERRADO) == LOW);
  bool senAbierto = (digitalRead(SENSOR_ABIERTO) == LOW);

  if (senCerrado && senAbierto) {
    detenerSistema("ERROR SENSORES");
    estadoMotor = FALLO_SENSORES;
  }

  // --- BOTÓN LÁMPARA ---
  int lecturaLamparaActual = digitalRead(PIN_ON_OFF_LAMPARA);
  if (lecturaLamparaActual != ultimoEstadoPinLampara) {
    tiempoReboteLampara = actualMillis;
  }
  if ((actualMillis - tiempoReboteLampara) > 50) { 
    if (lecturaLamparaActual != estadoFiltroLampara) {
      estadoFiltroLampara = lecturaLamparaActual;
      if (estadoFiltroLampara == HIGH) { 
        estadoLampara = !estadoLampara;
        digitalWrite(PIN_LAMPARA, estadoLampara ? LOW : HIGH); 
      }
    }
  }
  ultimoEstadoPinLampara = lecturaLamparaActual;

  // --- BOTONES DE PARADA DE EMERGENCIA ---
  bool btnStop = (digitalRead(PIN_REMOTO_EMERGENCIA) == HIGH) || (digitalRead(PIN_PARADA_EMERGENCIA) == HIGH);
  if (btnStop && estadoMotor != DETENIDO && estadoMotor != FRENADO_EMERGENCIA && (actualMillis - ultimaAccionRemoto >= 500)) {
    ultimaAccionRemoto = actualMillis;
    iniciarFrenado(false); 
  }

  // --- BOTONES DE CONTROL REMOTO Y PULSADOR TOGGLE (D39) ---
  bool btnAbrir = (digitalRead(PIN_REMOTO_ABRIR) == HIGH);
  bool btnCerrar = (digitalRead(PIN_REMOTO_CERRAR) == HIGH);
  bool btnToggle = (digitalRead(PIN_PULSADOR_TOGGLE) == LOW); // Pulsador externo activo en LOW

  if ((btnAbrir || btnCerrar || btnToggle) && menuContext == 0 && (actualMillis - ultimaAccionRemoto >= 500)) {
    ultimaAccionRemoto = actualMillis;
    
    if (estadoMotor == DETENIDO) {
      bool errorNoSensor = (!senCerrado && !senAbierto && !paradaForzada);

      if (!errorNoSensor) {
        if (btnToggle) {
          // Lógica de funcionamiento paso a paso (Toggle)
          if (senAbierto) {
            iniciarMovimiento(false); // Si está abierta -> Cerrar
          } else if (senCerrado) {
            iniciarMovimiento(true);  // Si está cerrada -> Abrir
          } else {
            // Si está a medio camino, invierte el sentido respecto al último movimiento
            iniciarMovimiento(!motorAbriendo);
          }
        }
        else if (btnAbrir && !senAbierto) {
          iniciarMovimiento(true);
        }
        else if (btnCerrar && !senCerrado) {
          iniciarMovimiento(false);
        }
      }
    }
  }

  // --- FINALES DE CARRERA EN MOVIMIENTO ---
  if (estadoMotor != DETENIDO && estadoMotor != FRENADO_EMERGENCIA && estadoMotor != PAUSA_INVERSION && estadoMotor != FALLO_SENSORES) {
    if (motorAbriendo && senAbierto) {
      posicionEstimadaMs = ((long)cfg_tRampa * 2 + (long)cfg_tVMax) * 1000;
      paradaForzada = false; 
      detenerSistema("FIN CARRERA: ABIERTA");
    }
    if (!motorAbriendo && senCerrado) {
      posicionEstimadaMs = 0;
      paradaForzada = false; 
      detenerSistema("FIN CARRERA: CERRADA");
    }
  }

  // --- MONITOREO DE CORRIENTE (ANTIAPLASTAMIENTO) ---
  if (actualMillis - ultimaLecturaCorriente >= 100) {
    ultimaLecturaCorriente = actualMillis;
    corrienteFiltrada = (FACTOR_SUAVIZADO * analogRead(PIN_CORRIENTE)) + ((1 - FACTOR_SUAVIZADO) * corrienteFiltrada);
    
    if (estadoMotor >= ACELERANDO && estadoMotor <= VEL_LENTA && corrienteFiltrada >= cfg_limCorriente) {
      iniciarFrenado(true); 
    }
  }

  // --- MÁQUINAS DE ESTADO ---
  maquinaMotor();
  gestionarBotones();
  dibujarMenu();
}

// ============================== MOTOR Y SECUENCIAS =============================
int mapPWM(int vel_0_25) {
  if (vel_0_25 > 25) vel_0_25 = 25;
  if (vel_0_25 < 0) vel_0_25 = 0;
  return map(vel_0_25, 0, 25, 0, 255);
}

void fijarPWM(int pwm) {
  pwmActualInt = pwm;
  analogWrite(PIN_MOSFET_PWM, pwm);
}

void activarMotorDireccion(bool abriendo) {
  digitalWrite(PIN_MOTOR_ARRIBA, LOW);
  digitalWrite(PIN_MOTOR_ABAJO, LOW);
  delayMicroseconds(100); 

  if (abriendo) {
    digitalWrite(PIN_MOTOR_ARRIBA, HIGH);
  } else {
    digitalWrite(PIN_MOTOR_ABAJO, HIGH);
  }
}

void iniciarMovimiento(bool esApertura) {
  motorAbriendo = esApertura;
  paradaForzada = false; 
  
  fijarPWM(PWM_STOP); 
  bool estaTotalmenteCerrada = (digitalRead(SENSOR_CERRADO) == LOW);

  if (esApertura) {
    ciclosTotales++;
    if (ciclosTotales % 10 == 0) {
      memoria.putLong("ciclos", ciclosTotales);
    }

    if (cfg_chapaEn && estaTotalmenteCerrada) {
      digitalWrite(CHAPA_PUERTA, LOW);
      estadoMotor = CHAPA_ACTIVA;
      tiempoInicioFase = millis();
    } else {
      activarMotorDireccion(true);
      estadoMotor = ACELERANDO;
      tiempoInicioFase = millis();
      ultimoTiempoCalculo = millis();
    }
  } else {
    activarMotorDireccion(false);
    estadoMotor = ACELERANDO;
    tiempoInicioFase = millis();
    ultimoTiempoCalculo = millis();
  }

  tiempoInicioGlobalMovimiento = millis();
  
  sirenaDebeSonar = cfg_sirenaEn;
  tiempoUltimoSirena = millis();
  estadoFisicoSirena = LOW; 
  if (sirenaDebeSonar) digitalWrite(PIN_ALARMA, estadoFisicoSirena);
  
  forzarRedraw = true;
}

void iniciarFrenado(bool esSobrecorriente) {
  causaEsSobrecorriente = esSobrecorriente;
  pwmAlChoque = pwmActualInt; 
  paradaForzada = !esSobrecorriente; 
  
  estadoMotor = FRENADO_EMERGENCIA;
  tiempoInicioFase = millis();
}

void detenerSistema(String mensaje) {
  fijarPWM(PWM_STOP);
  digitalWrite(PIN_MOTOR_ARRIBA, LOW);
  digitalWrite(PIN_MOTOR_ABAJO, LOW);
  digitalWrite(CHAPA_PUERTA, HIGH);
  
  sirenaDebeSonar = false;
  estadoFisicoSirena = HIGH;
  digitalWrite(PIN_ALARMA, HIGH);
  
  estadoMotor = DETENIDO;
  forzarRedraw = true;
}

void maquinaMotor() {
  if (estadoMotor == DETENIDO || estadoMotor == FALLO_SENSORES) return;

  unsigned long actualMillis = millis();
  unsigned long tTranscurrido = actualMillis - tiempoInicioFase;
  int pwmMax = mapPWM(cfg_vMax);
  int pwmMin = mapPWM(cfg_vMin);

  long tAcelMs = (long)cfg_tRampa * 1000;
  long tDecelMs = (long)cfg_tRampa * 1000;
  long tTotalViajeMs = tAcelMs + ((long)cfg_tVMax * 1000) + tDecelMs;

  if (estadoMotor >= ACELERANDO && estadoMotor <= VEL_LENTA) {
    long dt = actualMillis - ultimoTiempoCalculo;
    if (motorAbriendo) posicionEstimadaMs += dt;
    else posicionEstimadaMs -= dt;
  }
  ultimoTiempoCalculo = actualMillis; 

  if (sirenaDebeSonar) {
    if (!cfg_sirenaModoCompleto && (actualMillis - tiempoInicioGlobalMovimiento) >= (unsigned long)cfg_sirenaTOn * 1000) {
      sirenaDebeSonar = false;
      digitalWrite(PIN_ALARMA, HIGH);
    } else {
      unsigned long tiempoEnEstadoAct = actualMillis - tiempoUltimoSirena;
      
      if (estadoFisicoSirena == LOW && tiempoEnEstadoAct >= 600) {
        estadoFisicoSirena = HIGH;
        digitalWrite(PIN_ALARMA, HIGH);
        tiempoUltimoSirena = actualMillis;
      } 
      else if (estadoFisicoSirena == HIGH && tiempoEnEstadoAct >= 200) {
        estadoFisicoSirena = LOW;
        digitalWrite(PIN_ALARMA, LOW);
        tiempoUltimoSirena = actualMillis;
      }
    }
  }

  switch (estadoMotor) {
    case CHAPA_ACTIVA:
      if (tTranscurrido >= cfg_chapaOn) {
        digitalWrite(CHAPA_PUERTA, HIGH); 
        estadoMotor = PRE_RAMPA;
        tiempoInicioFase = actualMillis;
      }
      break;

    case PRE_RAMPA:
      if (tTranscurrido >= cfg_chapaOff) {
        activarMotorDireccion(motorAbriendo); 
        estadoMotor = ACELERANDO;
        tiempoInicioFase = actualMillis;
        ultimoTiempoCalculo = actualMillis; 
      }
      break;

    case ACELERANDO:
      if (tTranscurrido <= tAcelMs) {
        int pwmCalculado = map(tTranscurrido, 0, tAcelMs, PWM_STOP, pwmMax);
        fijarPWM(pwmCalculado);
      } else {
        fijarPWM(pwmMax);
        estadoMotor = VEL_MAXIMA;
        tiempoInicioFase = actualMillis;
      }
      break;

    case VEL_MAXIMA:
      fijarPWM(pwmMax);
      
      if (motorAbriendo && posicionEstimadaMs >= (tTotalViajeMs - tDecelMs)) {
        estadoMotor = DESACELERANDO;
        tiempoInicioFase = actualMillis;
      } 
      else if (!motorAbriendo && posicionEstimadaMs <= tDecelMs) {
        estadoMotor = DESACELERANDO;
        tiempoInicioFase = actualMillis;
      }
      break;

    case DESACELERANDO:
      if (tTranscurrido <= tDecelMs) {
        int pwmCalculado = map(tTranscurrido, 0, tDecelMs, pwmMax, pwmMin);
        fijarPWM(pwmCalculado);
      } else {
        fijarPWM(pwmMin);
        estadoMotor = VEL_LENTA;
      }
      break;

    case VEL_LENTA:
      fijarPWM(pwmMin); 
      break;

    case FRENADO_EMERGENCIA:
      if (tTranscurrido <= 600) { 
        int pwmCalculado = map(tTranscurrido, 0, 600, pwmAlChoque, PWM_STOP);
        fijarPWM(pwmCalculado);
      } else {
        fijarPWM(PWM_STOP);
        if (causaEsSobrecorriente) {
          estadoMotor = PAUSA_INVERSION;
          tiempoInicioFase = actualMillis;
        } else {
          detenerSistema("PARADA POR USUARIO");
        }
      }
      break;

    case PAUSA_INVERSION:
      if (tTranscurrido >= 800) {
        iniciarMovimiento(!motorAbriendo); 
      }
      break;
  }
}

// ============================== LÓGICA DE MENÚS Y PANTALLA =====================
void gestionarBotones() {
  bool btnAdd = (digitalRead(PIN_BTN_ADD) == LOW);
  bool btnMinus = (digitalRead(PIN_BTN_MINUS) == LOW);
  int lecturaOK = digitalRead(PIN_BTN_OK); 

  unsigned long actual = millis();
  bool flagOk = false;
  
  if (actual > 2000) {
    if (estadoAnteriorOK == HIGH && lecturaOK == LOW) { 
      flagOk = true; 
      delay(50); 
    }
  }
  estadoAnteriorOK = lecturaOK; 
  
  if (flagOk) {
    if (menuContext == 0 && estadoMotor == DETENIDO) { 
      menuContext = 10; menuCursor = 0; forzarRedraw = true; 
    }
    else if (!isEditing && menuContext >= 10 && menuContext <= 50) {
      if (menuContext == 10) { 
        if (menuCursor == 0) { menuContext = 20; menuCursor = 0; } 
        else if (menuCursor == 1) { menuContext = 30; menuCursor = 0; } 
        else if (menuCursor == 2) { menuContext = 40; menuCursor = 0; } 
        else if (menuCursor == 3) { 
          menuContext = 103; 
          isEditing = true; 
          editTempVal = (cfg_limCorriente / 5) * 5; 
        } 
        else if (menuCursor == 4) { menuContext = 50; menuCursor = 0; } 
        else if (menuCursor == 5) { menuContext = 0; menuCursor = 0; } 
      }
      else if (menuContext == 20) { 
        if (menuCursor == 4) { menuContext = 10; menuCursor = 0; } 
        else { menuContext = 200 + menuCursor; isEditing = true; 
               if(menuCursor==0) editTempVal = cfg_tRampa;
               if(menuCursor==1) editTempVal = cfg_vMax;
               if(menuCursor==2) editTempVal = cfg_tVMax;
               if(menuCursor==3) editTempVal = cfg_vMin;
        }
      }
      else if (menuContext == 30) { 
        if (menuCursor == 3) { menuContext = 10; menuCursor = 1; } 
        else { menuContext = 300 + menuCursor; isEditing = true; 
               if(menuCursor==0) editTempVal = cfg_chapaEn ? 1 : 0;
               if(menuCursor==1) editTempVal = cfg_chapaOn;
               if(menuCursor==2) editTempVal = cfg_chapaOff;
        }
      }
      else if (menuContext == 40) { 
        if (menuCursor == 3) { menuContext = 10; menuCursor = 2; } 
        else { menuContext = 400 + menuCursor; isEditing = true; 
               if(menuCursor==0) editTempVal = cfg_sirenaEn ? 1 : 0;
               if(menuCursor==1) editTempVal = cfg_sirenaModoCompleto ? 1 : 0;
               if(menuCursor==2) editTempVal = cfg_sirenaTOn;
        }
      }
      else if (menuContext == 50) { 
        if (menuCursor == 0) { menuContext = 60; } 
        else if (menuCursor == 1) { menuContext = 61; } 
        else if (menuCursor == 2) { menuContext = 10; menuCursor = 4; } 
      }
      forzarRedraw = true;
    }
    else if (menuContext == 60 || menuContext == 61) {
      menuContext = 50; 
      forzarRedraw = true;
    }
    else if (isEditing) {
      if(menuContext == 103) { cfg_limCorriente = editTempVal; menuContext = 10; }
      else if(menuContext == 200) { cfg_tRampa = editTempVal; menuContext = 20; }
      else if(menuContext == 201) { cfg_vMax = editTempVal; menuContext = 20; }
      else if(menuContext == 202) { cfg_tVMax = editTempVal; menuContext = 20; }
      else if(menuContext == 203) { cfg_vMin = editTempVal; menuContext = 20; }
      else if(menuContext == 300) { cfg_chapaEn = (editTempVal == 1); menuContext = 30; }
      else if(menuContext == 301) { cfg_chapaOn = editTempVal; menuContext = 30; }
      else if(menuContext == 302) { cfg_chapaOff = editTempVal; menuContext = 30; }
      else if(menuContext == 400) { cfg_sirenaEn = (editTempVal == 1); menuContext = 40; }
      else if(menuContext == 401) { cfg_sirenaModoCompleto = (editTempVal == 1); menuContext = 40; }
      else if(menuContext == 402) { cfg_sirenaTOn = editTempVal; menuContext = 40; }

      guardarConfiguracion();
      isEditing = false;
      lcd.clear(); lcd.setCursor(0,0); lcd.print("   GUARDADO!   ");
      delay(600);
      forzarRedraw = true;
    }
  }

  if ((btnAdd || btnMinus) && (actual - tiempoUltimoBoton >= intervaloBoton)) {
    tiempoUltimoBoton = actual;

    if (!isEditing && menuContext >= 10 && menuContext <= 50) {
      if (btnAdd) menuCursor++;    
      if (btnMinus) menuCursor--;  

      int maxCursor = 0;
      if (menuContext == 10) maxCursor = 5;
      if (menuContext == 20) maxCursor = 4;
      if (menuContext == 30) maxCursor = 3;
      if (menuContext == 40) maxCursor = 3;
      if (menuContext == 50) maxCursor = 2;
      
      if (menuCursor < 0) menuCursor = 0;
      if (menuCursor > maxCursor) menuCursor = maxCursor;
      forzarRedraw = true;
    } 
    else if (isEditing) {
      int salto = 1;
      if (menuContext == 103) salto = 5; 
      if (menuContext == 301 || menuContext == 302) salto = 50; 

      if (btnAdd) editTempVal += salto;    
      if (btnMinus) editTempVal -= salto;  
      
      if(editTempVal < 0) editTempVal = 0;
      
      if(menuContext == 103 && editTempVal > 4095) editTempVal = 4095; 
      if((menuContext == 201 || menuContext == 203) && editTempVal > 25) editTempVal = 25; 
      if((menuContext == 300 || menuContext == 400 || menuContext == 401) && editTempVal > 1) editTempVal = 1; 

      forzarRedraw = true;
    }
  }
}

void dibujarMenu() {
  if (!forzarRedraw && menuContext != 60 && menuContext != 0) return; 

  if (menuContext == 0) {
    static bool lastSensorA = false;
    static bool lastSensorC = false;
    static EstadoPuerta lastEstadoMotor = DETENIDO;

    bool actualSensorA = (digitalRead(SENSOR_ABIERTO) == LOW);
    bool actualSensorC = (digitalRead(SENSOR_CERRADO) == LOW);

    if (forzarRedraw || actualSensorA != lastSensorA || actualSensorC != lastSensorC || estadoMotor != lastEstadoMotor) {
      lcd.clear();

      if (estadoMotor == FALLO_SENSORES) {
        lcd.setCursor(0, 0); lcd.print(" ERROR CRITICO  ");
        lcd.setCursor(0, 1); lcd.print(" AMBOS SENSORES ");
      }
      else if (estadoMotor == FRENADO_EMERGENCIA || estadoMotor == PAUSA_INVERSION) {
        lcd.setCursor(0, 0); lcd.print("  FRENANDO...   ");
        lcd.setCursor(0, 1); lcd.print(causaEsSobrecorriente ? "ANTIAPLASTAMIENT" : " POR BOTON STOP ");
      }
      else if (estadoMotor != DETENIDO) {
        lcd.setCursor(0, 0); lcd.print(motorAbriendo ? "  ABRIENDO...   " : "  CERRANDO...   ");
        lcd.setCursor(0, 1); lcd.print(" EN MOVIMIENTO  ");
      } 
      else {
        if (paradaForzada) {
          lcd.setCursor(0, 0); lcd.print("     PARADA     "); 
          lcd.setCursor(0, 1); lcd.print(" DE EMERGENCIA  ");
        } 
        else if (!actualSensorA && !actualSensorC) {
          lcd.setCursor(0, 0); lcd.print("     ERROR      ");
          lcd.setCursor(0, 1); lcd.print("   NO SENSOR    ");
        } 
        else {
          lcd.setCursor(0, 0);
          if (actualSensorA) lcd.print(" PUERTA ABIERTA ");
          else if (actualSensorC) lcd.print(" PUERTA CERRADA ");

          lcd.setCursor(0, 1);
          lcd.print("   EN ESPERA    ");
        }
      }

      lastSensorA = actualSensorA;
      lastSensorC = actualSensorC;
      lastEstadoMotor = estadoMotor;
      forzarRedraw = false;
    }
  }
  
  else if (!isEditing && menuContext >= 10 && menuContext <= 50) {
    if (menuContext == 10) mostrarSubMenu(menuCursor, menuPrin, 6);
    if (menuContext == 20) mostrarSubMenu(menuCursor, menuTRamp, 5);
    if (menuContext == 30) mostrarSubMenu(menuCursor, menuChapa, 4);
    if (menuContext == 40) mostrarSubMenu(menuCursor, menuSirena, 4);
    if (menuContext == 50) mostrarSubMenu(menuCursor, menuTestInfo, 3);
    forzarRedraw = false;
  }

  else if (isEditing) {
    lcd.clear();
    lcd.setCursor(0, 0);
    if(menuContext == 103) lcd.print("LIM CORRIENTE:");
    else if(menuContext == 200) lcd.print("T. Rampa (s):");
    else if(menuContext == 201) lcd.print("Vel. Max (0-25):");
    else if(menuContext == 202) lcd.print("T. Vel Max (s):");
    else if(menuContext == 203) lcd.print("Vel. Min (0-25):");
    else if(menuContext == 300 || menuContext == 400) lcd.print("Estado:");
    else if(menuContext == 301) lcd.print("Tiempo ON (ms):");
    else if(menuContext == 302) lcd.print("Pre-Rampa (ms):");
    else if(menuContext == 401) lcd.print("Modo Sirena:");
    else if(menuContext == 402) lcd.print("T.ON Inicio (s):");

    lcd.setCursor(0, 1);
    if(menuContext == 300 || menuContext == 400) {
      lcd.print(editTempVal == 1 ? "ON " : "OFF");
    } else if (menuContext == 401) {
      lcd.print(editTempVal == 1 ? "COMPLETO" : "INICIO  ");
    } else {
      lcd.print(editTempVal);
      lcd.print("       "); 
    }
    forzarRedraw = false;
  }

  else if (menuContext == 60) {
    if (millis() - ultimoRefrescoTest >= 200) {
      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("S.P_C S.P_A P_EM");
      lcd.setCursor(2, 1);
      lcd.print(digitalRead(SENSOR_CERRADO)); 
      lcd.setCursor(8, 1);
      lcd.print(digitalRead(SENSOR_ABIERTO)); 
      lcd.setCursor(14, 1);
      lcd.print(digitalRead(PIN_PARADA_EMERGENCIA)); 
      ultimoRefrescoTest = millis();
    }
  }
  
  else if (menuContext == 61) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("CICLOS COMPLET:");
    lcd.setCursor(0, 1);
    lcd.print(ciclosTotales);
    forzarRedraw = false;
  }
}

void mostrarSubMenu(int cursor, String menus[], int size) {
  lcd.clear();
  String l1 = "", l2 = "";
  if (cursor % 2 == 0) {
    lcd.setCursor(0, 0); lcd.write(byte(0)); 
    l1 = menus[cursor];
    if (cursor + 1 < size) l2 = menus[cursor + 1];
  } else {
    l1 = menus[cursor - 1];
    lcd.setCursor(0, 1); lcd.write(byte(0)); 
    l2 = menus[cursor];
  }
  lcd.setCursor(1, 0); lcd.print(l1);
  lcd.setCursor(1, 1); lcd.print(l2);
}

// ============================== GESTIÓN EEPROM =================================
void cargarConfiguracion() {
  memoria.begin("config", false);
  cfg_tRampa = memoria.getInt("tRampa", 3);
  cfg_vMax = memoria.getInt("vMax", 20);
  cfg_tVMax = memoria.getInt("tVMax", 20);
  cfg_vMin = memoria.getInt("vMin", 10);
  
  cfg_chapaEn = memoria.getBool("chapaEn", true);
  cfg_chapaOn = memoria.getInt("chapaOn", 500);
  cfg_chapaOff = memoria.getInt("chapaOff", 800);
  
  cfg_sirenaEn = memoria.getBool("sirEn", true);
  cfg_sirenaModoCompleto = memoria.getBool("sirMod", false);
  cfg_sirenaTOn = memoria.getInt("sirTOn", 3);
  
  cfg_limCorriente = memoria.getInt("limCorr", 4000);
  if (cfg_limCorriente > 4095) cfg_limCorriente = 4095;
  cfg_limCorriente = (cfg_limCorriente / 5) * 5; 

  ciclosTotales = memoria.getLong("ciclos", 0);
}

void guardarConfiguracion() {
  memoria.putInt("tRampa", cfg_tRampa);
  memoria.putInt("vMax", cfg_vMax);
  memoria.putInt("tVMax", cfg_tVMax);
  memoria.putInt("vMin", cfg_vMin);
  
  memoria.putBool("chapaEn", cfg_chapaEn);
  memoria.putInt("chapaOn", cfg_chapaOn);
  memoria.putInt("chapaOff", cfg_chapaOff);
  
  memoria.putBool("sirEn", cfg_sirenaEn);
  memoria.putBool("sirMod", cfg_sirenaModoCompleto);
  memoria.putInt("sirTOn", cfg_sirenaTOn);
  
  memoria.putInt("limCorr", cfg_limCorriente);
  memoria.putLong("ciclos", ciclosTotales);
}
Video


Comments