Problema con interrupt

Muy buenas a todos.

Tengo un problemilla que me está resultando complicado de solucionar. He buscado en los hilos del foro pero no he encontrado ninguno que me sirva.

El caso es que tengo un circuito con una placa Wemos D1 mini lite para controlar 2 relés (luces) a través de MQTT y también desde los interruptores de la pared.

Lo que pretendo es que si por alguna razón, el Wemos pierde la conexión con la red wifi o con el servidor MQTT, los interruptores sigan accionando los relés, saltándome así los delays que introduzco en el loop a la hora de reconectar. Ya que si no, los interruptores no serían muy responsivos en el caso de no tener red.

Este es el código del fichero que controla los interruptores

#define SWITCH1 D2
#define SWITCH2 D3

int estadoSwitch1;
int estadoSwitch2;


void toggleInterruptor1() {
    toggleRele(1);
}

void toggleInterruptor2() {
    toggleRele(2);
}

void setupInterruptores() {
  pinMode(SWITCH1, INPUT_PULLUP);
  pinMode(SWITCH2, INPUT_PULLUP);

  estadoSwitch1 = digitalRead(SWITCH1);
  estadoSwitch2 = digitalRead(SWITCH2);

  attachInterrupt(digitalPinToInterrupt(SWITCH1), toggleInterruptor1, CHANGE);
  attachInterrupt(digitalPinToInterrupt(SWITCH2), toggleInterruptor2, CHANGE);
  
  Serial.println("Interruptores configurados");
}


void loopInterruptores() {
  // Interruptor 1
  int nuevoEstado1 = digitalRead(SWITCH1);
  if (nuevoEstado1 != estadoSwitch1) {
    estadoSwitch1 = nuevoEstado1;
    toggleRele(1);
  }

  // Interruptor 2
  int nuevoEstado2 = digitalRead(SWITCH2);
  if (nuevoEstado2 != estadoSwitch2) {
    estadoSwitch2 = nuevoEstado2;
    toggleRele(2);
  }
}

El problema viene cuando un interruptor pasa de HIGH a LOW, ya que parece que la interrupción se ejecuta 4 veces. No es un gran problema porque al final el relé conmuta una sola vez (no le da tiempo a conmutar 4 veces), pero no creo que sea la forma correcta de hacerlo. No se si es un problema de software o si debería poner alguna resistencia en el interruptor, o algún condensador, ya que actualmente están conectados directamente desde el PIN del arduino a tierra.

Muchas gracias por vuestra ayuda. Saludos.

El problema que tienes son rebotes en los interruptores, podrías resolverlo de dos formas, mediante software utilizando la librería "BOUNCE 2" o mediante hardware en este enlace puedes leer al respecto DEBOUNCE YOUR NOISY BUTTONS otra cosa es porque utilizas interrupciones para leer el estado de los botones?, eso no es necesario no entiendo como se puede bloquear si no este conectado a menos que tengas algún while que bloquee el programa o algun delay muy grande.

Muchas gracias @swift.
En caso de que se pierda la conexión con el servidor MQTT, hay un delay de 1 segundo en cada loop para reintentar la conexión (ya que el ESP8266 reconecta al Wifi en segundo plano sin afectar al loop). Por lo tanto como mucho los interruptores podrían tardar en actuar 1 segundo desde que se pulsan. Se que no es mucho tiempo, pero si me lo pudiera ahorrar, mejor que mejor.

La solución por hardware que comentas me gusta, la probaré en la protoboard, pero no la podré integrar en mi diseño actual ya que las placas ya están impresas y todos los componentes montados, así que será para la versión 2.0 xD.

En cuanto a la solución por software, me da la impresión de que la librería que me propones trabaja con delays de 25 ms para saltarse los rebotes, lo cual no es posible ya que en un interrupt no se pueden usar delays.

Si te fijas en el código que he puesto ya tengo una función llamada loopInterruptores(){...} que es la que usaba antes en el loop principal para verificar los cambios en los interruptores. Si no consigo solucionar lo de los rebotes tendré que volver a ella.

De todas formas muchas gracias por tu respuesta, me ha ayudado mucho.

Si tu problema esa en que pierde la señal WIFI entonces porque no revisas como es la distribución WIFI en tu casa? Usa tu celular con las aplicaciones que indican el nivel de señal WIFI y verás cual es el problema.
Tu problema debe ser que donde estan los WEMOS tu WIFI esta siendo interferida por otra mas potente y no logra identificarla.

Puedes cambiar de canal o bien usa un mejorador de señal en ese recinto o considera tu casa como un todo y distribuye de mejor manera la señal de tu router.
Mi consejo es que siempre verifiques los canales WIFI porque aunque ahora lo resuelvas eso no quita que en un tiempo tu vecino cambie el suyo y te complique. Ni hablar si vives en departamento.

No, si realmente no tengo problemas ni de cobertura, ni de velocidad. Tengo una red mallada con 2 puntos de acceso y cobertura al 100% en toda la casa con 200 mbps simétricos de ancho de banda (con Wifi). Pero lo que no quería era que una vez instalado todo y funcionando, por lo que sea, se estropearan mis puntos de acceso, o el router, o cualquier elemento de red y que dejaran de funcionar las luces xD.

Quiero que los arduinos funcionen de manera autónoma sin que se vean afectados por una caída eventual de la red o del servidor MQTT.

De todas maneras he vuelto a la solución inicial. Prescindiré de los interrupts por el momento y verificaré los cambios en los interruptores dentro del loop. En caso de que se caiga la red tendré un delay de 1000ms en cada loop hasta que consiga conectar. De esta manera solo tendré un tiempo de respuesta como mucho de 1 segundo desde que pulso el interruptor hasta que responde la luz. Tampoco es mucho tiempo y de momento soluciono la papeleta.

#include "controlReles.h"
#include "controlInterruptor.h"
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <PubSubClient.h>
#include <ArduinoOTA.h>

// Definimos el SSID de la Wifi
char ssid[] = "XXXXXX";
char password[] = "XXXXXX";

// Vinculamos el cliente wifi con el cliente MQTT
WiFiClient clienteWifi;
PubSubClient client(clienteWifi);

// Control del led de la placa
unsigned int led = 0;

// Cada 30 segundos publicaremos un mensaje en el topic de estado confirmando que seguimos online
unsigned int estoyVivo = 0; // Tiempo de la última notificación
bool iniciado = false;      // En el primer bucle también lo publicamos

// Zona de la casa que cubre este dispositivo (la usaremos para identificarlo y para construír los topics)
char* zona = "pasillo";

// Definimos las variables de los topics del RELE 1 para el servidor mqtt
char topicEstadoRele1[50];
char topicSetRele1[50];

// Definimos las variables de los topics del RELE 2 para el servidor mqtt
char topicEstadoRele2[50];
char topicSetRele2[50];

// Definimos las variables del topic de disponibilidad (alive)
char topicDisponible[50];


// Definimos la dirección IP del servidor MQTT, así como el usuario y contraseña
const char* mqtt_uri = "XXX.XXX.XXX.XXX";
const char* mqtt_user = "****";
const char* mqtt_pass = "****";


// Cuando se actua sobre un relé, se almacena su estado para posteriormente notificarlo al servidor MQTT
char* pendienteRele1 = NULL;
char* pendienteRele2 = NULL;


/**
 * Función que conectará con la red Wifi
 */
void setup_wifi () {
  // Definimos el modo Wifi a Station
  WiFi.mode(WIFI_STA);
  
  // Conectamos con la red Wifi
  WiFi.begin(ssid, password);

  // Esperaremos a conectar al Wifi un total de 5 iteraciones (1000ms cada una)
  int iteraciones = 0;
  
  Serial.print("Conectando a la red Wifi ");
  while (WiFi.status() != WL_CONNECTED && iteraciones < 5) {
    Serial.print(".");
    delay(1000);
    iteraciones = iteraciones + 1;
  }

  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("No se ha podido conectar a la red Wifi, seguimos intentándolo en segundo plano");
  } else {
    Serial.println("Conexión a la red Wifi correcta");
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());
    ArduinoOTA.begin();
  }

  // Entropía... xD
  randomSeed(micros());
}


/**
 * Función de callback para cuando recibimos algún mensaje en los topics suscritos
 */
void mensajeRecibido(char* topic, byte* payload, unsigned int length) {
  String orden;
  for (int i = 0; i < length; i++) {
    orden += (char)payload[i];
  }
  
  int numeroRele = (strcmp(topic, topicSetRele1) == 0) ? 1 : 2;
  if (orden == "ON") {
    Serial.print("Recibida instrucción para encender el rele: ");
    Serial.println(numeroRele);
    encenderRele(numeroRele);
  } else {
    Serial.print("Recibida instrucción para apagar el rele: ");
    Serial.println(numeroRele);
    apagarRele(numeroRele);
  }
}

/**
 * Función que tratará de reconectar con el servidor mqtt
 */
void reconnect() {
  // Aunque el bucle solo se ejecutará una vez, no quitamos la estructura del while por si
  // en un futuro decidimos iterar más veces
  unsigned int intentos = 0;
  
  while (!client.connected() && intentos < 1) {
    Serial.print("Conectando al servidor mqtt: ");
    Serial.println(mqtt_uri);
    
    // Creamos un ID aleatorio
    String clientId = "ESP8266Client-";
    clientId += String(zona);
    clientId += "-";
    clientId += String(random(0xffff), HEX);
    intentos++;
    
    // Intentamos conectar
    if (client.connect(clientId.c_str(), mqtt_user, mqtt_pass, topicDisponible, 0, 0, "offline", true)) {
      Serial.println("Conectado al servidor MQTT");
      
      // Nos suscribimos de nuevo a los topics
      client.subscribe(topicSetRele1);
      client.subscribe(topicSetRele2);

      // Publicamos el estado "online"
      client.publish(topicDisponible, "online");
      estoyVivo = millis();
    } else {
      Serial.print("Ha fallado la conexión, rc=");
      Serial.print(client.state());
      // Esperando 1 segundo para seguir con el loop
      delay(1000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("Iniciando el dispositivo");
  pinMode(BUILTIN_LED, OUTPUT);

  // Definimos los topics
  sprintf(topicEstadoRele1, "home/%s/luz1/state", zona);
  sprintf(topicSetRele1, "home/%s/luz1/set", zona);
  sprintf(topicEstadoRele2, "home/%s/luz2/state", zona);
  sprintf(topicSetRele2, "home/%s/luz2/set", zona);
  sprintf(topicDisponible, "home/%s/status", zona);

  // Setup de relés
  iniciarReles();

  // Setup de interruptores
  setupInterruptores();

  // Setup de wifi
  setup_wifi();

  // Setup de MQTT
  client.setServer(mqtt_uri, 1883);
  client.setCallback(mensajeRecibido);

  // Definimos el callback al que hay que llamar cuando se modifica algún relé
  onCambioRele([](int rele, char* valor) {
    if (rele == 1) {
      pendienteRele1 = valor;
    } else if (rele == 2) {
      pendienteRele2 = valor;
    }
  });
}

void loop() {
  // Gestión de las actualizaciones OTA
  ArduinoOTA.handle();

  // Loop de interruptores
  loopInterruptores();

  // Si estamos conectados ejecutamos toda la lógica relacionada con la conectividad MQTT
  if (WiFi.status() == WL_CONNECTED) {
    if (!client.connected()) {
      reconnect();
    } else {
      if (pendienteRele1) {
        client.publish(topicEstadoRele1, pendienteRele1);
        pendienteRele1 = NULL;
      }
      if (pendienteRele2) {
        client.publish(topicEstadoRele2, pendienteRele2);
        pendienteRele2 = NULL;
      }
  
      // Notificamos que estamos vivos cada 30 segundos
      unsigned int ahora = millis();

      // Pasados 47 días, millis() se reinicia, por lo que es necesario verificar si ha vuelto a 0 (segunda condición del IF)
      if (ahora - estoyVivo > 30000 || ahora - estoyVivo < 0 || !iniciado) {
        client.publish(topicDisponible, "online");
        estoyVivo = millis();
        iniciado = true;
      }
      client.loop();
    }
  }

  // Parpadea el led de ESP8266 cada 500 ms
  int ahora = millis();
  if (ahora - led > 500 || ahora - led < 0) {
    int estado = digitalRead(LED_BUILTIN);
    digitalWrite(LED_BUILTIN, !estado);
    led = ahora;
  }
}

La forma de evitar los rebotes con las interrupciones es deshabilitarla (al final de la función que llama la interrupción) mediante detachinterrup y levantar una bandera. En el loop principal, mediante la bandera y milis() , volver a habilitar la interrupción.
Saludos