Hora de tocar la parte de software de este proyecto. Si recordamos, el requerimiento es simple: Hacer que cuando la lavadora termine el lavado (desbloquee la puerta), se lance una notificación a mi teléfono móvil.
Para enviar notificaciones, podría elegir una de muchas rutas posibles: por e-mail, por alguno de los servicios que existen de comunicaciones, a través de una app que me diseñe yo mismo… Para ahorrar tiempo, decidí usar uno de los servicios que existen para notificaciones simples: pushbullet.
Pushbullet te permite, entre muchas otras opciones, enviar pequeñas notificaciones de texto a todos los dispositivos que tengas enlazados. Podría entonces, desde mi microcontrolador ESP8266, enviar una notificación y recibirla en el móvil. Pero decidí ir un poco más lejos: Tengo un mini-ordenador como centro domótico en mi casa: un orange PI PC con OpenHAB, que me permite controlar centralizadamente mis enchufes inteligentes. ¿Y si conecto mi lavadora al sistema y es mi centro domótico el encargado de enviar dichas notificaciones? Así, también podré controlar el estado de la lavadora y enviar órdenes de puesta en marcha de forma remota.
Empecemos con la forma de comunicación que va a tener el ESP-01 con mi centro domótico. En este caso, decidí elegir MQTT, por lo simple que es: Defines canales (topics) de comunicación, el dispositivo que vaya a escuchar esos canales se suscribe a ellos, y el dispositivo que vaya a enviar publica los mensajes a dicho topic. Para el uso de MQTT con los microcontroladores ESP8266, existe la librería PubSubClient, que nos facilitará el envío de mensajes entre un dispositivo y otro.
Así que comencemos nuestro proyecto en Arduino IDE. Lo primero, la conexión wifi a mi punto de acceso y la conexión al servidor MQTT (que tengo en mi centro domótico). En mi caso, he separado los datos de conexión en un fichero .h, para tenerlos «desconectados» del código principal:
//Connection.h: const char* ssid = "<YourSSID>"; const char* password = "<YourSSIDPassword>"; const char* mqttServer = "<YourMQTTServerIP>"; const int mqttPort = 1883; const char* mqttUser = "<YourMQTTServerUser>"; const char* mqttPassword = "<YourMQTTServerPassword>";
Creamos un par de subrutinas, para preparar la conexión y conectar al servidor, así como el armazón de setup() y loop(). Aquí, también hemos definido los dos topics mqttDoorTopic, que usaremos para transmitir el estado de bloqueo de la puerta de la lavadora, y mqttStartTopic, que usaremos para recibir la orden de comenzar el lavado:
#include <WiFiClient.h> #include <ESP8266WiFi.h> #include <PubSubClient.h> #include "Connection.h" const char* mqttClientId = "GF_WashingMachine"; const char* mqttDoorTopic = "Home/GF_Patio/WashingMachine/Door"; const char* mqttStartTopic = "Home/GF_Patio/WashingMachine/Start"; WiFiClient espClient; PubSubClient client(espClient); void setup() { setup_wifi(); client.setServer(mqttServer, mqttPort); client.setCallback(callback); } void loop() { if (!client.connected()) { reconnect(); } client.loop(); } void setup_wifi() { delay(10); // We start by connecting to a WiFi network WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); } } void callback(char* topic, byte* payload, unsigned int length) { } void reconnect() { // Loop until we're reconnected while (!client.connected()) { // Attempt to connect if (client.connect(mqttClientId, mqttUser, mqttPassword)) { // Once connected, resubscribe client.subscribe(mqttStartTopic); } else { // Wait 5 seconds before retrying delay(5000); } } }
Los pines GPIO0 y GPIO2 del ESP-01 se usan para elegir el modo de inicio al momento de alimentar la placa, lo que hacía incompatible el circuito que explicaba en la entrada anterior. Para solucionarlo, podemos usar los pines RX y TX como pines GPIO, usando el modo FUNCTION_3 en el setup(). También indicamos si van a ser de salida o entrada:
//Disable Serial pins in order to use them as GPIO pinMode(1,FUNCTION_3); //TX pinMode(3,FUNCTION_3); //RX pinMode(startCyclePin, OUTPUT); // For washing cycle start pinMode(doorLockPin, INPUT); // For door lock detection
Creamos una función que nos devuelva el estado de bloqueo de puerta, leyendo el estado del pin 1:
bool isDoorLocked() { //Returns true if pin is high (door is locked); false otherwise return digitalRead(doorLockPin) == 1; }
Creamos también una subrutina para el inicio de lavado, que active durante medio segundo el relé que puenteará el botón de inicio (pin 3).
void startWashingCycle() { if(!isDoorLocked()) { //Presses the start button for 0.5 seconds digitalWrite(startCyclePin, HIGH); delay(500); //Releases the start button and waits for the washing cycle to start //(the door will lock after some time) digitalWrite(startCyclePin, LOW); while(!isDoorLocked()) { delay(1000); } } }
En el loop(), se comprueba cada 5 segundos el estado de bloqueo de la puerta, y se publica al topic de bloqueo de puerta. Si la puerta estaba bloqueada y se desbloquea, quedándose desbloqueada por 10 segundos, se entiende que el lavado ha finalizado, y se transmite el estado «OPEN», actualizando el estado de funcionamiento también a «OFF». Si la puerta estaba desbloqueada y se bloquea, se transmite el estado de bloqueo «CLOSED» y el estado de funcionamiento «ON»:
long now = millis(); //Check status every 5 seconds if (now - lastCheck > 5000) { lastCheck = now; if (isDoorLocked()) { if (!wasDoorLocked) { wasDoorLocked = true; client.publish(mqttStartTopic, "ON"); } client.publish(mqttDoorTopic, "CLOSED"); if (doorUnlockedFor > 0) { doorUnlockedFor = 0; } } else { if (wasDoorLocked) { doorUnlockedFor++; } //Door needs to be unlocked for 10 seconds (2 checks). This is //done to avoid misunderstanding power fluctuations as //unlocking signals. if (doorUnlockedFor >= 2) { client.publish(mqttDoorTopic, "OPEN"); client.publish(mqttStartTopic, "OFF"); wasDoorLocked = false; } } }
Por último, se controla cuándo se recibe la orden externa de inicio de lavado en la subrutina callback(). Se convierte el contenido del mensaje a string, agregando un carácter «\0» delante, y se comprueba si es la orden de inicio «ON». Si es así, se llama a la subrutina de inicio de lavado:
//Add \0 to payload, to convert it to string later byte* payloadNull = (byte*)malloc(length+1); memcpy(payloadNull, payload, length); payloadNull[length] = '\0'; if(String(topic).equals(String(mqttStartTopic))) { if (String((char*)payloadNull).equals("ON")) { startWashingCycle(); } }
Con esto es suficiente para la parte del microcontrolador. Os dejo el código en github por si lo queréis :). Lo que queda es entrar en mi servidor OpenHAB y configurar la lavadora para que esté disponible.
En el servidor, será necesario el broker MQTT (o sea, el servidor que maneje los mensajes y a quién van dirigidos) y el plugin (binding) para agregar dispositivos MQTT. También, en este caso, será necesario agregar el plugin para envío de notificaciones a través de PushBullet.
Agregamos el MQTT broker:
Agregamos el binding MQTT:
Y por último el conector con PushBullet:
Necesitaremos agregar el dispositivo (thing) de la lavadora, sus canales de comunicación y sus items, para poder controlarlos. Empezamos con el dispositivo:
Se agregan sus canales. Para el bloqueo de puerta, uno de tipo contacto, que tiene los valores posibles «OPEN» y «CLOSED». Para el inicio de lavado, uno de tipo switch, que tiene los valores posibles «ON» y «OFF». Es importante asignar los mismos topics que nos declaramos en el ESP-01, si no, no funcionará por supuesto:
Se mapean los canales a dos nuevos items, para dar control sobre ellos:
Con esto ya tenemos acceso al estado de la lavadora:
Ahora para lanzar las notificaciones cuando el lavado se complete, es necesario tener instalado el plugin de reglas:
La interacción con el plugin de PushBullet aún no se puede hacer de forma gráfica, por lo que para esto habrá que hacerlo de la forma antigua: accediendo por SSH al servidor OpenHAB. Será necesario agregar un nuevo fichero a /etc/openhab2/rules. En mi caso creé el archivo «lavadora.rules», con el siguiente contenido:
rule "lavadora" when Item Lavadora_BloqueoPuerta changed from CLOSED to OPEN then sendPushbulletNote("Lavadora", "Lavado completado") end
Con eso, cuando el bloqueo de puerta de la lavadora se cambia de CLOSED a OPEN, se lanza la notificación.
Lo último que hacer, conectar las tripas a la lavadora 🙂
¡Nos vemos en la siguiente entrada!