Vuelvo desde el inframundo para continuar con mi «aire acondicionado inteligente» 😄
En la anterior entrada, conseguimos echar a andar un sketch de prueba que permitía controlar el aire acondicionado mediante un arduino mega. Ahora toca portar el sketch a un ESP-01 para hacer uso de su conectividad WiFi y tamaño compacto, ya que no parece haber demasiado hueco dentro de la unidad split del aire acondicionado.
Al igual que con el proyecto de mi lavadora inteligente (que sigue funcionando genial un año después 😊), la idea sería usar MQTT para la comunicación con mi servidor central openHAB, por lo que necesitaremos la librería PubSubClient.
#include <ESP8266WiFi.h> #include <WiFiClient.h> #include <PubSubClient.h> . . . void reconnect() { // Loop until we're reconnected while (!client.connected()) { // Attempt to connect if (client.connect(mqttClientId, mqttUser, mqttPassword)) { // Once connected, resubscribe client.subscribe(mqttPowerCommandTopic); client.subscribe(mqttModeCommandTopic); client.subscribe(mqttTempCommandTopic); client.subscribe(mqttSpeedCommandTopic); } else { // Wait 5 seconds before retrying delay(5000); } } }
Para la gestión de conexiones WiFi, he encontrado una librería que nos permite configurar de nuevo la conexión WiFi en caso de que cambiemos de red, sin tener que reprogramar la placa, lo que nos viene genial. Se llama WiFiManager. En este caso, le indicamos que, en caso de no poder conectarse por WiFi a nuestro router, abra una conexión para realizar la configuración, y reintente la conexión pasados 3 minutos (180 segundos).
#include <DNSServer.h> #include <ESP8266WebServer.h> #include <WiFiManager.h> . . . void setup_wifi() { WiFiManager wifiManager; wifiManager.setTimeout(180); //3 minutes if(!wifiManager.autoConnect(wifiSsid, wifiPassword)) { //Retry after 3 minutes with no WiFi connection ESP.reset(); delay(5000); } }
Para el envío de comandos por infrarrojos, al igual que con el sketch de prueba que montamos en la entrada anterior, necesitaremos la librería IRRemote. En este caso, la versión para ESP8266, IRremoteESP8266.
#include <IRremoteESP8266.h> #include <IRsend.h>
Una vez usadas las librerías equivalentes para el microcontrolador, el resto de la lógica implementada en el sketch de prueba es compatible, por lo que no es necesario mayor cambio, aparte de encapsularlo de forma que podamos llamarlo mediante subrutinas de una forma fácil. En este caso, he trasladado toda esa lógica a un fichero .h, para dejar el fichero .ino principal para las subrutinas de conexión, gestión de topics MQTT, etc.
#include "Configuration.h" #include "Commands.h"
Usaremos en este caso dos topic MQTT para cada función a controlar del aire acondicionado (un topic para recibir comandos desde el servidor MQTT y otro topic para comunicar al servidor el estado actual de cada función).
const char* mqttPowerStateTopic = "Home/FF_MasterBedroom/AirConditioner/PowerS"; const char* mqttPowerCommandTopic = "Home/FF_MasterBedroom/AirConditioner/PowerC"; const char* mqttModeStateTopic = "Home/FF_MasterBedroom/AirConditioner/ModeS"; const char* mqttModeCommandTopic = "Home/FF_MasterBedroom/AirConditioner/ModeC"; const char* mqttTempStateTopic = "Home/FF_MasterBedroom/AirConditioner/TemperatureS"; const char* mqttTempCommandTopic = "Home/FF_MasterBedroom/AirConditioner/TemperatureC"; const char* mqttSpeedStateTopic = "Home/FF_MasterBedroom/AirConditioner/SpeedS"; const char* mqttSpeedCommandTopic = "Home/FF_MasterBedroom/AirConditioner/SpeedC";
Las funciones que vamos a controlar son:
- Estado de funcionamiento: ON/OFF
- Modo: AUTO, COOL (frío), FAN (aire) y HEAT (calor)
- Velocidad: AUTO, LOW (baja), MED (media) y HIGH (alta)
- Temperatura: AUTO o un valor entre 17 y 30 grados
El resto del funcionamiento es sencillo: Al recibir comandos por los topic de comando MQTT, actuamos sobre el aire mandando los comandos correspondientes (cambiar de modo, cambiar la temperatura…). En el caso del comando de encendido/apagado, se esperan los comandos ON o OFF:
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); // Conversion of payload to Byte payloadByte = (byte)payloadString.toInt(); // Power Topic: Payload will be "ON" or "OFF" if(topicString.equals(String(mqttPowerCommandTopic))) { if (payloadString.equals("ON")) { sendPowerCommand(true); } else if (payloadString.equals("OFF")) { sendPowerCommand(false); } . . .
En el caso de la velocidad, se esperan los valores AUTO (que corresponde a la velocidad 0 según nuestro sketch de pruebas), LOW (correspondiente a la velocidad 1), MED (velocidad 2) o HIGH (velocidad 3).
. . . // Speed Topic: Payload will be "AUTO", "LOW", "MED", or "HIGH" } else if(topicString.equals(String(mqttSpeedCommandTopic))) { if (payloadString.equals("AUTO")) { sendSpeedCommand(0); } else if (payloadString.equals("LOW")) { sendSpeedCommand(1); } else if (payloadString.equals("MED")) { sendSpeedCommand(2); } else if (payloadString.equals("HIGH")) { sendSpeedCommand(3); } . . .
Para el modo, el funcionamiento es prácticamente igual que el caso de la velocidad, solo que con los valores AUTO (modo 0), COOL (modo 1), FAN (modo 2) y HEAT (modo 3).
. . . // Mode Topic: Payload will be "AUTO", "COOL", "FAN" or "HEAT" } else if(topicString.equals(String(mqttModeCommandTopic))) { if (payloadString.equals("AUTO")) { sendModeCommand(0); } else if (payloadString.equals("COOL")) { sendModeCommand(1); } else if (payloadString.equals("FAN")) { sendModeCommand(2); } else if (payloadString.equals("HEAT")) { sendModeCommand(3); } . . .
La temperatura puede tener el valor AUTO (valor de temperatura 0 segun nuestro sketch de pruebas) o una cantidad de grados entre 17 y 30.
. . . // Temperature Topic: Payload will be "AUTO" or a number corresponding to the desired temperature, between 17 and 30 } else if(topicString.equals(String(mqttTempCommandTopic))) { if (payloadString.equals("AUTO")) { sendTemperatureCommand(0); } else { if (payloadByte >= 17 && payloadByte <= 30) { sendTemperatureCommand(payloadByte); } } } publishStates(); }
Aparte de enviar el status de cada función del aire usando los topics de status tras recibir comandos, para comunicar al servidor MQTT que el comando se ha ejecutado, también se envía una actualización de los mismos cada 5 segundos, para casos en los que el status en el servidor se corrompa o restablezca, por ejemplo, debido a un reinicio del servidor.
void loop() { String payload; if (!client.connected()) { reconnect(); } client.loop(); long now = millis(); if (now - lastCheck > 5000) { lastCheck = now; publishStates(); } } void publishStates() { // Publish power state if (powerState) { client.publish(mqttPowerStateTopic, "ON"); } else { client.publish(mqttPowerStateTopic, "OFF"); } // Publish mode if (mode == 0) { client.publish(mqttModeStateTopic, "AUTO"); } else if (mode == 1) { client.publish(mqttModeStateTopic, "COOL"); } else if (mode == 2) { client.publish(mqttModeStateTopic, "FAN"); } else if (mode == 3) { client.publish(mqttModeStateTopic, "HEAT"); } // Publish temperature if (temperature == 0) { client.publish(mqttTempStateTopic, "AUTO"); } else { client.publish(mqttTempStateTopic, String(temperature).c_str()); } // Publish speed if (fanSpeed == 0) { client.publish(mqttSpeedStateTopic, "AUTO"); } else if (fanSpeed == 1) { client.publish(mqttSpeedStateTopic, "LOW"); } else if (fanSpeed == 2) { client.publish(mqttSpeedStateTopic, "MED"); } else if (fanSpeed == 3) { client.publish(mqttSpeedStateTopic, "HIGH"); } else if (fanSpeed == 4) { client.publish(mqttSpeedStateTopic, "SPECIAL"); } else if (fanSpeed == 5) { client.publish(mqttSpeedStateTopic, "OFF"); } }
Una vez hecho esto, tenemos la parte de software lista. ¡Hora de coger el destornillador! Veamos qué hay debajo de la carcasa de mi aire, a ver de cuánto sitio disponemos.
Primero, quitemos esa tapa superior y veámos cuantos kilos de polvo hay acumulados 😂
Parece que todo se mantiene en su sitio con cuatro tornillos: uno claramente visible al quitar la tapa y otros tres dentro de la ranura de salida de aire, tras unas piezas de plástico que los ocultan. Tras quitarlos, la carcasa completa sale sin problemas.
Así a primeras, la modificación parece fácil: la pequeña placa abajo a la derecha contiene los LEDs de estado y el receptor de infrarrojos, y está conectada con un conector a la placa principal. La idea es pegar un led infrarrojo al lado del receptor para que los comandos que se envíen desde la nueva placa sean visibles al aire, dejando vía libre para si en un futuro (o en caso de todo el tinglado se nos estropee) queremos manejar el aire con el mando a distancia de serie.
El siguiente paso será crear una pequeña plaquita para conectar el ESP-01 al LED infrarrojos y la instalación de la misma dentro del aire acondicionado. Nos vemos en la siguiente entrada 😊