Arduino,  Hardware,  Software

Haciendo inteligente mi ventilador de pie

¿Que más se le puede pedir a un ventilador? Nos dan aire en los calurosos veranos, y no nos piden gran cosa a cambio… Frente al típico aire acondicionado, que gasta cientos de vatios, incluso más de mil, un ventilador de pie se contenta con menos de 100, por lo general. ¿Tiene sentido hacerlos inteligentes? ¿Al menos algo más de sentido que el hecho de que esté escribiendo esto en pleno Enero, cuando este mismo ventilador ya está guardado hasta que venga de nuevo el verano?

Como se dice, «ya está todo inventado»… Ya existen modelos de ventilador con wifi, conectables a tu sistema domótico. Sencillo, podría pagar de 70 a 100 euros y ea, ya tendría ventilador inteligente. Pero así no es como se hacen las cosas aquí en El Programador Ibérico 😏.

Empecemos por desmontar el ventilador y ver como funciona. La botonera delantera tiene un cable de entrada (que viene del cable de fase) y tres de salida, y al pulsar cada uno de los botones, se cierra circuito entre el cable de entrada y uno de los cables de salida, quedando el resto sin conectar. Este mismo funcionamiento será el que tendremos que replicar: recibir una orden del servidor de domótica y activar una de 3 salidas, según la velocidad que queramos, desactivando las otras.

Para conectar la fase con las diferentes salidas, que van cada una a una bobina del motor, usaremos relés básicos. En mi caso, de 5v, que pueden conectar circuitos por los que discurren hasta 10A a 250v en alterna:

Para alimentar estos relés y quitarle esa carga al microcontrolador, usaremos unos transistores, en este caso el clásico modelo 2N2222 (NPN). Para proteger dichos transistores del pico de voltaje que se produce cuando los relés dejan de alimentarse (podéis leer más aquí), usaremos unos diodos 1N4148. Y por último, para no dejar los botones que traía el ventilador inutilizados… ¿por qué no conectarlos también al microcontrolador, a través de una resistencia de 10Kohm, y poder seguir usándolos?. De esta forma el ventilador no perderá funcionalidad, sólo la ganará 😊.

Como microcontrolador podemos usar un… ¿Qué? ¿Otro ESP-01? ¿Que ya me estoy repitiendo con tanto proyecto usando el mismo bicho enano de 8 pines? Oye, no me podéis decir que no es útil, con lo peque que es 😊. No, en este caso, ya que vamos a hacer uso de los 3 botones del ventilador, además de las 3 salidas para el motor, los 4 pines GPIO del pequeño ESP-01 se nos quedan cortos. ¿Qué tal un NodeMCU, para variar? (O uno de sus sucedáneos chinos).

El montaje que vamos a hacer se podría resumir en el siguiente esquema: tres botones de entrada, que a través de una resistencia conectan a masa su correspondiente pin del microcontrolador cuando son pulsados, tres pines de salida, que cuando son activados mandan una señal HIGH a la base de su correspondiente transistor, que a su vez conecta la masa de su correspondiente relé, el cable de fase, conectado al pin central de los tres relés, y los tres cables del motor, cada uno conectado al pin normalmente abierto de su correspondiente relé.

Como ya he repetido mucho eso de «en el futuro haré placas PCB en condiciones, en vez de usar placas perforadas», pues llegó el momento: Diseñemos una placa para este montaje 😊. La verdad que para mí ha sido la primera vez, y me ha costado bastante colocar los diferentes componentes de forma que no me queden pistas muy enrevesadas, aunque el número de componentes sea pequeño 😫. Me terminó quedando así:

¡Menuda forma más rara! ¿Por qué no es cuadrada? ¿Por qué tiene dos agujeros en medio? Tranquilos, tranquilos. No tengo ni idea. Que no, que es broma 😜. La forma está adaptada al interior del cuerpo del ventilador: resulta que en el sitio donde podía montar la placa, la forma es abombada, y la parte que quedaría hacia arriba es más estrecha que la de abajo, así que para aprovechar todo el espacio posible hice que la placa tuviese esa forma. Los dos agujeros están ahí porque el fabricante dejó en la moldura interior del ventilador un par de postes para tornillos, supongo que para otro modelo, que usará algún componente y lo atornillan ahí. Me venía genial para dejar la placa bien cogida al ventilador, así que los aproveché 😊.

Ahora a trasladar a la realidad este diseño. Podría hacerlo a mano, con transparencias, placas de cobre fotosensibles, una insoladora, ácido férrico… Pero la verdad que me decidí a probar alguno de los servicios de PCB a medida que existen. En mi caso, JLCPCB. La verdad que los precios son bastante buenos. Si no necesitas que vengan por envío urgente, puedes tener 5 placas por 10€, envío incluído. ¿Y qué tal el resultado? La verdad que bastante bueno, a menos a mi parecer:

¡A montar se ha dicho! La verdad que esa misma tarde del día que me llegaron me puse a montarlas. Tenía muchas ganas de ver funcionando el proyecto 😊. Como todo en esta vida, no sale todo como queremos a la primera… Cometí un par de fallos a la hora de elegir qué pines usar para los botones de entrada: usé para dos de los botones los pines D3 y D4, que corresponden a los GPIO 0 y 2 del ESP8266, que están reservados para elegir el tipo de arranque del microcontrolador. Se podrían usar, aunque con limitaciones, que no tuve en cuenta, así que terminé por usar un par de pequeños cables para conectar esos botones a los pines D5 y D6, que pueden usarse sin limitaciones. Un rato de soldadura más tarde, ya tenía esto:

Para alimentar esto, usé otro de mis cargadores de móvil antiguos. En este caso, ya que el microcontrolador se puede alimentar sin problema a 5V, no tuve que hacer nada para adaptar el voltaje de entrada 😊. Unas últimas soldaduras y… ¡todo montado dentro del ventilador!.

El software para mi caso es similar que el de mis dos aires acondicionados (johnson y daitsu): Un cliente MQTT, que se conecte a mi servidor de domótica (que recientemente he cambiado a Home Assistant), un loop infinito, que vaya informando cada X segundos al servidor acerca del estado de funcionamiento, y en este caso, chequee si han habido cambios en el estado de los botones, para actuar y cambiar dicho estado. En la función setup(), haremos los preparativos, indicando los pines que se van a usar para entrada (los botones del ventilador), y de salida (los tres relés de las diferentes velocidades).

void setup() {
  Serial.begin(115200);
  
  pinMode(speed1ButtonPin, INPUT_PULLUP);
  pinMode(speed2ButtonPin, INPUT_PULLUP);
  pinMode(speed3ButtonPin, INPUT_PULLUP);
  pinMode(speed1RelayPin, OUTPUT);
  pinMode(speed2RelayPin, OUTPUT);
  pinMode(speed3RelayPin, OUTPUT);

  setup_wifi();
  client.setServer(mqttServer, mqttPort);
  client.setCallback(callback);
}

La función de callback (la que se llamará cuando recibamos comandos por MQTT) lleva funcionamiento para convertir el valor que se recibe por payload a un número byte (0 para apagado, 1, 2 o 3 para velocidades) y convertir eso en estados apagado-encendido, que se almacenan en un valor byte (usando los últimos 3 bits, o sea, los menos significativos, y siendo 1 = encendido y 0 = apagado). De esta forma un comando de velocidad 1 se convertirá en un valor 0b00000001 (último bit con valor 1), y un comando de velocidad 3 en un valor 0b00000100 (tercer bit menos significativo a 1, el resto a 0). Este valor se envía a otra función, que es la que cambia los estados de los relés:

void callback(char* topic, byte* payload, unsigned int length) {
  String topicString = String(topic);
  byte* payloadZeroTerm = (byte*)malloc(length+1);
  String payloadString;
  byte payloadByte;

// Conversion of payload to String
  memcpy(payloadZeroTerm, payload, length);
  payloadZeroTerm[length] = '\0';
  payloadString = String((char*)payloadZeroTerm);
  Serial.print("Received message from topic ");
  Serial.print(topic);
  Serial.print(": ");
  Serial.println(payloadString);

// Conversion of payload to Byte
  payloadByte = (byte)payloadString.toInt();

// Speed Topic: Payload will be "0", "1", "2", or "3"
  if(topicString.equals(String(mqttSpeedCommandTopic))) {
    if (payloadByte == 0) {
      updateRelayState(0b00000000);  // All OFF
    } else if (payloadByte == 1) {
      updateRelayState(0b00000001);  // Speed 1
    } else if (payloadByte == 2) {
      updateRelayState(0b00000010);  // Speed 2
    } else if (payloadByte == 3) {
      updateRelayState(0b00000100);  // Speed 3
    }
  }

  publishStates();
}

En la función updateRelayState() se recibe por parámetro qué relé es el que debe estar encendido, y se actúa para apagar el resto y encender el que debe ser encendido. Dejo un pequeño retardo entre la desactivación de los relés actuales y la activación del nuevo, para dar tiempo al relé que estaba activado a volver a su posición de desconexión, para minimizar problemas (para que no exista en ningún momento más de una bobina del motor alimentada. Ese supuesto caso podría causar algún problema, al haber más de una bobina «compitiendo» por mover las aspas del ventilador a velocidades diferentes. Mejor evitarlo 😊.

void updateRelayState(byte relayState) {
  if (relayState & 0b00000100) {  // Speed 3
    digitalWrite(speed1RelayPin, LOW);
    digitalWrite(speed2RelayPin, LOW);
    if (currentSpeed > 0) {
      delay(100);
    }
    digitalWrite(speed3RelayPin, HIGH);
    currentSpeed = 3;
  } else if (relayState & 0b00000010) {  // Speed 2
    digitalWrite(speed1RelayPin, LOW);
    digitalWrite(speed3RelayPin, LOW);
    if (currentSpeed > 0) {
      delay(100);
    }
    digitalWrite(speed2RelayPin, HIGH);
    currentSpeed = 2;
  } else if (relayState & 0b00000001) {  // Speed 1
    digitalWrite(speed2RelayPin, LOW);
    digitalWrite(speed3RelayPin, LOW);
    if (currentSpeed > 0) {
      delay(100);
    }
    digitalWrite(speed1RelayPin, HIGH);
    currentSpeed = 1;
  } else {  // Speed 0
    digitalWrite(speed1RelayPin, LOW);
    digitalWrite(speed2RelayPin, LOW);
    digitalWrite(speed3RelayPin, LOW);
    currentSpeed = 0;
  }
}

Cuatro veces por segundo se realiza una lectura de los pines de los botones del ventilador, para ver si el estado ha cambiado (se ha pulsado un botón nuevo). Ese funcionamiento se hace en la función getButtonState(), y se devuelven los valores de estado de los botones en el mismo formato que la función callback(), para en caso de haber cambiado, enviárselos a updateRelayState().

byte getButtonState() {
  byte stateReturn = 0b00000000;
  
  byte buttonState = digitalRead(speed1ButtonPin);
  if (buttonState == LOW) {
    stateReturn = stateReturn | 0b00000001;
  }

  buttonState = digitalRead(speed2ButtonPin);
  if (buttonState == LOW) {
    stateReturn = stateReturn | 0b00000010;
  }
  
  buttonState = digitalRead(speed3ButtonPin);
  if (buttonState == LOW) {
    stateReturn = stateReturn | 0b00000100;
  }

  lastButtonStateTime = millis();

  return stateReturn;
}

Poco más. Cada X segundos, se envía el estado al servidor MQTT, para que lo tenga actualizado, mediante la función publishStates(). Cuando se hace un cambio de velocidad, se envía una actualización al momento, para que no le llegue X segundos más tarde al servidor.

void publishStates() {
//  Publish speed
  if (currentSpeed == 0) {
    client.publish(mqttSpeedStateTopic, "0");
  } else if (currentSpeed == 1) {
    client.publish(mqttSpeedStateTopic, "1");
  } else if (currentSpeed == 2) {
    client.publish(mqttSpeedStateTopic, "2");
  } else if (currentSpeed == 3) {
    client.publish(mqttSpeedStateTopic, "3");
  }
}

La configuración del dispositivo en Home Assistant, al usar MQTT, hay que hacerlo mediante el fichero de configuración configuration.yaml. Podemos seguir la página de ayuda oficial para configurarlo. En mi caso usé un tipo de dispositivo select (de esta forma, las velocidades me aparecerán como un desplegable). Aquí indicaremos el mismo command_topic y state_topic que hemos definido en «configuration.h» del sketch del microcontrolador.

select:
  - platform: mqtt
    name: Ventilador dormitorio
    unique_id: select.floor1_bedroom_fan
    command_topic: Home/Floor1/Bedroom/Fan/Command
    state_topic: Home/Floor1/Bedroom/Fan/State
    options:
      - "0"
      - "1"
      - "2"
      - "3"

Os dejo abajo todo lo necesario para replicar el proyecto, en caso que a alguien le interese. También tenéis esta información en mi GitHub.

¡Nos vemos en la siguiente entrada!

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Ver
Privacidad