Problemas al controlar relé biestable de 2 bobinas mediante MQTT

Hola a todos.

Estoy intentando controlar la iluminación mediante un relé biestable de 2 bobinas y a la vez que se puedan seguir usando los interruptores de pared. Lo quiero hacer por medio de MQTT (corriendo en una Raspberry Pi en la cual también está OpenHab) a través de un Arduino Mega Pro Mini conectado por ethernet. El caso es que la detección del estado de la luz (que va con un opto en paralelo que detecta los 220VAC) funciona perfectamente pero el mando del relé falla a veces. Según el Monitor Serie el comando llega al Mega pero el relé a veces responde y a veces no. Sinceramente no tengo mucha idea de programación y no sé si será tema de software o que el Mega se queda “corto” para estas cosas.

Os pongo el sketch para ver si me podéis echar una mano:

#include <UIPEthernet.h>
#include <PubSubClient.h>

static byte mymac[] = { 0x74,0x69,0x69,0x2D,0x30,0x31 };

int RELE1_1 = 2; //bobina 1 del relé
int RELE1_2 = 4; //bobina 2 del relé
int luz1Pin = 6; //estado

int RELE1 = 2;

int luz1State=HIGH;

// Update these with values suitable for your network.
const char* mqtt_server = "192.168.1.5";
const char* mqtt_user = "xxxxxxxxx";
const char* mqtt_password = "xxxxxxxxx";

// Ethernet and MQTT related objects
EthernetClient ethClient;
PubSubClient client(ethClient);
unsigned long lastMsg = 0;

#define MSG_BUFFER_SIZE  (50)
char msg[MSG_BUFFER_SIZE];
int value = 0;

unsigned long timeout=0;  // used to limit how many 'nothing happening' messages are send

void print_luz1State(int luz1State){
if(luz1State) Serial.print("Apagada");
else Serial.print("Encendida");
}

void setup() {

Serial.begin(9600);     // communication with the host computer
while (!Serial)   { ; }
Serial.println("Conectando...");
delay(1000);

// setup ethernet communication using DHCP
  if(Ethernet.begin(mymac) == 0) {
    Serial.println(F("Conexión Ethernet mediante DHCP ha fallado"));
    for(;;);
  }
     
// you're connected now, so print out the data
Serial.println("Conectado a la red");
delay(2000);
Serial.print("Dirección IP: ");
delay(2000);
Serial.println(Ethernet.localIP());
delay(2000);

    pinMode(RELE1_1, OUTPUT);
    pinMode(RELE1_2, OUTPUT);
    digitalWrite(RELE1_1, LOW);
    digitalWrite(RELE1_2, LOW);
    pinMode(luz1Pin, INPUT_PULLUP);

// Set the MQTT server to the server stated above ^
client.setServer(mqtt_server, 1883);
client.setCallback(callback);

}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Mensaje recibido [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();

 // Luz 1
  if ((char)payload[0] == '1'){
   digitalWrite(RELE1, HIGH);
   delay(200);
   digitalWrite(RELE1, LOW);

  } else if ((char)payload[0] == '2') {
   digitalWrite(RELE1, HIGH);
   delay(200);
   digitalWrite(RELE1, LOW);
   }
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    delay(2000);
    Serial.print("Intentando conexión MQTT...");
    // Attempt to connect
    if (client.connect("ArduinoClient", mqtt_user, mqtt_password)) {
      delay(2000);
      Serial.println("Conectado");
      client.subscribe("/piso1/entrada/luz/#");
    } else {
      delay(2000);
      Serial.print("fallida, rc=");
      Serial.println(client.state());
      delay(2000);
      Serial.println("Reintentando en 5 segundos");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}
 
void loop() {
    // listen for communication from the ESP8266 and then write it to the serial monitor
    if ( Serial2.available() )   {  Serial.write( Serial2.read() );  }
 
    // listen for user input and send it to the ESP8266
    if ( Serial.available() )       {  Serial2.write( Serial.read() );  }

  if (RELE1==2) RELE1=4;
  else if (RELE1==4) RELE1=2;

 //**************
 //    luz 1
 //**************

 if (luz1State != digitalRead(luz1Pin)){ 
 luz1State = digitalRead(luz1Pin); 
 if (luz1State == HIGH) client.publish("piso1/entrada/luz/state", "OFF") ;
 else client.publish("piso1/entrada/luz/state", "ON") ;
 timeout = millis() + 1000;
 }
  
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

Este es el Monitor Serie, en el que se ve que el estado ON/OFF cambia perfectamente tanto si se pulsa el interruptor en la pared como si se manda el comando por MQTT desde openHab (cuando el relé responde a este, por supuesto…):

Conectando...
Conectado a la red
Dirección IP: 192.168.1.95
Intentando conexión MQTT...Conectado
Mensaje recibido [piso1/entrada/luz/state] ON  //encendida la luz desde el interruptor... OK
Mensaje recibido [piso1/entrada/luz/state] OFF //apagada desde interruptor... OK
Mensaje recibido [piso1/entrada/luz/state] ON  //encendida desde interruptor... OK
Mensaje recibido [piso1/entrada/luz/state] OFF //apagada desde interruptor... OK
Mensaje recibido [piso1/entrada/luz/command] 2 //encender desde openHab
Mensaje recibido [piso1/entrada/luz/state] ON  //se enciente... OK
Mensaje recibido [piso1/entrada/luz/command] 2 //apagar desde openHab
Mensaje recibido [piso1/entrada/luz/state] OFF //se apaga... OK
Mensaje recibido [piso1/entrada/luz/command] 1 //comando desde openHab... no responde el relé
Mensaje recibido [piso1/entrada/luz/command] 2 //comando desde openHab... no responde
Mensaje recibido [piso1/entrada/luz/command] 1 //comando desde openHab... no responde
Mensaje recibido [piso1/entrada/luz/command] 2 //comando desde openhab
Mensaje recibido [piso1/entrada/luz/state] ON  //se enciende... OK
Mensaje recibido [piso1/entrada/luz/command] 2 //comando desde openHab... no responde
Mensaje recibido [piso1/entrada/luz/command] 1 //no responde
Mensaje recibido [piso1/entrada/luz/command] 2 //no responde...

Gracias y perdón si hay algún disparate en el código pero como ya he dicho no tengo mucha idea de esto.

Salu2.

Hola José, la verdad que el código es un poco raro.
Yo empezaría cambiando esas variables que has puesto para los pines de entrada, deben ser constantes.

int RELE1_1 = 2; //bobina 1 del relé
int RELE1_2 = 4; //bobina 2 del relé
int luz1Pin = 6; //estado
// de hecho no son variables sino constantes yo lo pondría así
#define RELE_1 2
#define RELE_2 4
#define luzPin 6

Si tengo tiempo le echaré un vistazo más a fondo.

@Chblasco
Es preferible que sean constantes pero un código no cambia en nada si usas variables o constantes en este caso. Si el consumo de su memoria RAM.

@joseabch

La pregunta que me hago es que recibe desde OPENHAB cuando no responde

	// Luz 1
	if ((char)payload[0] == '1'){
		digitalWrite(RELE1, HIGH);
		delay(200);
		digitalWrite(RELE1, LOW);
	} else 
	if ((char)payload[0] == '2') {
		digitalWrite(RELE1, HIGH);
		delay(200);
		digitalWrite(RELE1, LOW);
	}

para que esto no funcione playload[0] no es ni 1 ni 2 en ASCII.
Has visto que recibe?

surbyte:
La pregunta que me hago es que recibe desde OPENHAB cuando no responde

	// Luz 1
if ((char)payload[0] == '1'){
	digitalWrite(RELE1, HIGH);
	delay(200);
	digitalWrite(RELE1, LOW);
} else 
if ((char)payload[0] == '2') {
	digitalWrite(RELE1, HIGH);
	delay(200);
	digitalWrite(RELE1, LOW);
}



para que esto no funcione playload[0] no es ni 1 ni 2 en ASCII.
Has visto que recibe?

Muchas gracias a los dos por contestar y perdón por tardar tanto en responder, pero es que quería hacer unas pruebas antes.

@surbyte cuando envío comandos desde openHab el servidor mqtt los recibe bien, los veo en MQTT.fx, y en Arduino lo único que tengo para ver lo que se recibe es el monitor serial, en el que al menos ahí se ven 1 y 2 cuando no envía el digitalwrite al pin de salida que en ese momento corresponda (o al menos eso supongo yo). En el primer mensaje que puse está una captura del monitor serie en el que se reciben 1 y 2 cuando no responde el relé.

Pensando que pudiera ser algo de hardware (más bien de "potencia") he probado tanto con un ESP8266 como con un Arduino Due y sigue pasando lo mismo, tal vez con el Due pase algo menos pero igual se pierden comandos. Sin embargo un cambio de estado en el interruptor de la pared siempre lo detecta correctamente el Mega en el pin de entrada digital, ahí no hay fallos.

No sé, yo sigo pensando que puede ser más un problema de software que del hardware, de que el scketch no esté bien, o a lo mejor es que estas placas no valen para lo que yo quiero... no sé qué pensar...
Otra prueba que quiero hacer es poner un equipo de medida registrando las salidas digitales pra ver si cuando se "pierde" el comando el Mega saca o no los 5V por el pin. Ya no sé qué pensar...

Muchas gracias por vuestra ayuda.

Salu2!

Moderador:
No repitas lo que otra persona te dice. Ya se lee en el post correspondiente.
Menciona a la persona por su nick y nada mas.
Usa quote (que es lo que eso cita para resaltar un párrafo, no todos y cada uno de los textos respondidos que corresponden a ese post.
Item 14 de las normas del foro, último párrafo, si quieres leer al respecto.
Edita por favor todo lo que esta de más.
Gracias.

Al leer esto:

Pensando que pudiera ser algo de hardware (más bien de "potencia") he probado tanto con un ESP8266 como con un Arduino Due y sigue pasando lo mismo, tal vez con el Due pase algo menos pero igual se pierden comandos. Sin embargo un cambio de estado en el interruptor de la pared siempre lo detecta correctamente el Mega en el pin de entrada digital, ahí no hay fallos.

me obligas a preguntar como estan conecados los releés al ESP8266 o al DUE y estoy seguro que ese es el problema.
Cuando usas dispositivos 3.3V no puedes conectar todo como se sugiere para los dispositivos de 5V.
Debes primero separar el jumper que une las dos alimentaciones, porque la etapa del transistor, rele y fototransistor requiere SI O SI 5V, asi que las debes alimentar con 5V externos.
El led o sea la entrada INX debe ir conectado a 3.3V y a través de una Resistencia que limite a 10mA porque el GPIO del ESP no da mas que 12mA, unes dicha resistencia al GPIOx correspondiente.
Cuando pongas un LOW en el GPIO el led se iluminará y cuando pongas un HIGH no funcionará.

Te aseguro que esto no va a fallar.
Es muy problable que tu sistema funcione pero al accionar dos dispositivos RELEs no lo haga. Ese es otro tema. Cada rele requiere 65mA aprox. EL ESP no recuerdo cuanto puede entregar pero como te estoy indicando la corriente debe ser suministrada por una fuente de 5V externa.

@surbyte los relés son de 12V (RT242F12) y las bobinas se controlan por medio de un BC548. El pin de salida del Arduino (o del ESP cuando lo he probado) va conectado a la base del transistor mediante una resistencia de 1K. No encuentro el esquema "limpio" así que te mando una foto, espero que se entienda bien cómo está alimentado el relé y como se gobierna desde Arduino.

La resistencia de 10K se ver perfecta y la otra resistencia en serie con la base cual es?
Se ve perfectamente el esquema, gracias!

Esa Rb (resistencia de base) debería ser

Rb = hfe*(3.3-0.7)/Ibobina =
supongamos 100 de hfe

Si Ibobina es 50mA => 5200 ohms aprox 4k7
Si Ibobina es 100mA => 2600 ohms aprox 2k7

De 1K.

1k garantiza la corriente.
Bueno mi sospecha quedó por el piso.
A buscar otra razón.

Ahora me centraré en esta parte del código

if (luz1State != digitalRead(luz1Pin)){
		luz1State = digitalRead(luz1Pin);
	if (luz1State == HIGH) 
		client.publish("piso1/entrada/luz/state", "OFF") ;
	else 
		client.publish("piso1/entrada/luz/state", "ON") ;

cambialo por

	luz1State = digitalRead(luz1Pin);
	if (luz1State != luz1StateAnt){
            client.publish("piso1/entrada/luz/state", luz1State?"ON":"OFF") ;
	luz1StateAnt = luz1State;

Agrega cuando defines luz1State = HIGH la variable nueva

int luz1State = HIGH, luz1StateAnt = LOW;

veamos como trabaja ahora.

Muchas gracias @surbyte, la verdad es que así da gusto :sweat_smile:

Te comento, he cambiado el código que me comentas y en los primeros comandos parecía que iba bien pero duró poco, al rato empezó como antes a "perder" comandos. El código que he modificado siguiento tu consejo es la parte que se dedica a la detección del estado de la lámpara, que en principio no tiene que ver nada con el mando del relé, pero imagino que todo el código al final está relacionado entre si...

De lo que me di cuenta (es lo que tiene estar haciendo las pruebas casi de madrugada con la mujer y los niños durmiendo y todo en silencio... :grinning: ) es que en los comandos en los que el relé no se hacía se escuchaba un ligerísimo click en la bobina del relé, como si intentara hacer "algo" pero el relé no llegara a excitarse. Muy raro todo. Para descartar fallo del relé lo cambié por otro pero nada, pasaba lo mismo.

Según las especificaciones del relé la resistencia de la bobina es de 240 Ohm, lo que nos daría una corriente de 50 mA. No pensaba yo que ese pudiera ser el problema, pero desde luego fue lo primero en lo que tú pensaste y quién sabe si puede que llevaras razón.

La verdad es que estoy empezando a tirar la toalla con esto porque no me veo capaz de que funcione bien del todo.

Paciencia hombre, hay problemas en los que llevo meses sin resolverlos y sigo intentando, claro que los voy olvidando... pero cada tanto los recupero con nuevos aires.

A ver si este hilo link muy similar a tu caso ayuda en algo
Mira al final

Muchas gracias, voy a probar lo del hilo que me has pasado y ya comentaré el resultado.

Revisa si no hay caracteres extra.
En lugar de imprimir con Serial.print() usa algo que muestre los bytes.

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Mensaje recibido [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i = 0; i < length; i++) {
    Serial.print((payload[i], HEX);
  }
  Serial.println();

 // Luz 1
  if ((char)payload[0] == '1'){
   digitalWrite(RELE1, HIGH);
   delay(200);
   digitalWrite(RELE1, LOW);

  } else if ((char)payload[0] == '2') {
   digitalWrite(RELE1, HIGH);
   delay(200);
   digitalWrite(RELE1, LOW);
   }
}

después del 31 (‘1’) y 32 (‘2’) no debe haber 10 o 13 como bytes impresos. Verifica!!