domingo, 6 de agosto de 2017

Sensor de consumo eléctrico para HomeAssistant (y 3)

En este artículo pretendo dar por zanjado el tema del sensor de consumo eléctrico doméstico. Venimos de esta entrada.
En la entrada anterior planteamos el circuito a utilizar para nuestro sensor e hicimos una serie de pruebas en nuestro protoboard.
Esta vez vamos a ver el montaje definitivo y cómo mandar los datos a HomeAssistant.

Montaje definitivo

En la imagen podemos ver el montaje definitivo que he hecho en una placa:
Montaje definitivo
No sé si se ven bien los detalles, pero básicamente he montado el D1 mini en un zócalo para poder extraerlo fácilmente para reprogramarlo y le he conectado a la entrada de 5V una batería recargable de litio 18650 de 3.7V, con lo cual el regulador interno nos da los 3.3V estabilizados para el divisor de tensión.
He de decir que este es el segundo D1 mini que uso: el primero me lo cargué midiendo el consumo del dispositivo. Se ve que no le sentaron nada bien los microcortes de tensión que le metí cuando estaba probando a poner y quitar el amperímetro. Ahora le he puesto al circuito unos pines que me hacen de interruptor al estar conectados mediante un puente. No quiero cargarme más componentes con un pico al conectar y desconectar la batería (que no es nada fácil).
Aparte de esto, nada más que decir en cuanto al montaje físico.

Software

En cuanto al programa en sí para el lector de consumo, aparte de utilizar la librería EmonLib para lo que son las lecturas de corriente, necesitamos algo más para enviar los datos una vez que los tenemos.
Lo más lógico dado que vamos a utilizar HomeAssistant como centralizador es utilizar un cliente de MQTT.

MQTT

MQTT son las siglas de Message Queue Telemetry Transport, un protocolo ligero utilizado en dispositivos del Internet de las cosas que permite a múltples intermediarios intercambiar mensajes de la misma manera que funcionaría un chat: cada intermediario accede a lo que se denomina un tópico, que es el tema de la conversación, y cada uno puede publicar mensajes sobre ese tópico. Otros clientes pueden suscribirse a ese tópico y recibir una notificación cuando haya un mensaje nuevo.
Nos conectaremos a un servidor (o broker) de MQTT y publicaremos periódicamente en un tópico concreto los valores leídos de corriente y potencia efectivas. Para ello utilizaremos la librería PubSubClient.

El programa

A continuación, el código fuente del programa utilizado para el sensor de consumo:
/**
 * Lector de consumo energético con conexión Wifi a servidor MQTT
 * Isidoro Aguilar Baeza - Julio/Agosto 2017
 * Este script se ejecuta en dispositivos NodeMCU, no en Arduino
 *
 * Librerías utilizadas:
 *   - PubSubClient(MQTT): https://github.com/knolleary/pubsubclient
 *   - EmonLib: https://github.com/openenergymonitor/EmonLib
 */

#include "Arduino.h"
#include <ESP8266WiFi.h>
#include "lib/PubSubClient.h"
#include "lib/EmonLib.h"

// Configuración de pines
#define PIN_LED            2

// Configuración de WIFI
#define WIFI_SSID    "XXXXXX"               // Nombre del AP Wifi
#define WIFI_PWD    "XXXXXX"            // Contraseña para conectar
#define WIFI_BPS    9600                // Velocidad del interfaz WIFI

// Configuración de MQTT
#define MQTT_SERVIDOR    "X.X.X.X"        // Dirección IP del broker
#define MQTT_PUERTO        1883
#define MQTT_USUARIO    "XXXXXXXXXXXXX"    // Usuario de acceso
#define MQTT_PWD        "XXXXXXXXXXXXX"    // Contraseña
#define MQTT_CLIENTE    "sensorConsumo"    // Identificador de este cliente
#define MQTT_TOPICO        "micasa/potencia"    // Tópico en el que publicaremos

// Configuración eléctrica
#define RatioTC            2000    // Ratio del transformador
#define ResCarga        91        // Resistencia de carga
#define correccion        1.145    // Factor de corrección adicional. Calculado empíricamente
#define VoltajeLinea    220        // Voltaje típico de la línea
#define T_PAUSA            10        // Intervalo entre medidas, en segundos

// Inicializamos el cliente Wifi y el MQTT
WiFiClient clienteESP;
PubSubClient clienteMQTT(clienteESP);

// Declaramos un lector de consumo de la librería EmonLib
EnergyMonitor emon1;

// Declaramos la cadena que se enviará al servidor MQTT
char payloadMQTT[30];

/**
 * Conecta con el servidor de MQTT (Mosquitto)
 */
void conectarMQTT() {
    if (!clienteMQTT.connected()) {
        Serial.print("Conectando con el servidor MQTT en ");
        Serial.print(MQTT_SERVIDOR);

        boolean conectado = false;
        while (!conectado) {
            Serial.print(".");
            if (clienteMQTT.connect(MQTT_SERVIDOR, MQTT_USUARIO, MQTT_PWD)) {
                conectado = true;
                Serial.println();
                Serial.println("Conectado");
                //clienteMQTT.subscribe(MQTT_TOPICO);
            } else {
                delay(1000);
            }
        }
    }
}

/**
 * Configuración.
 * Se ejecuta sólo una vez, al iniciar el Arduino
 */
void setup() {
    // 1.- Inicializamos el puerto serie USB
    Serial.begin(9600);
    delay(100);
    Serial.println("");
    Serial.println("");
    Serial.println("Lector de consumo eléctrico");
    Serial.println();
    Serial.print("Conectando con ");
    Serial.print(WIFI_SSID);

    // 2.- Inicializamos el módulo WIFI y nos conectamos al AP
    WiFi.persistent(false);
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PWD);

    // Establecemos un bucle de espera hasta que esté conectado...
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println();
    Serial.println("Conectado.");
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());

    // 3.- Nos conectamos al servidor MQTT
    clienteMQTT.setServer(MQTT_SERVIDOR, MQTT_PUERTO);

    // 4.- Inicializamos el LED
    pinMode(PIN_LED, OUTPUT);
    digitalWrite(PIN_LED, LOW);

    // 5.- Indicamos dónde está conectado el sensor de corriente (pin analógico A0) y el factor de calibración.
    emon1.current(A0, RatioTC / ResCarga * correccion);

    Serial.print("Voltaje de alimentación: ");
    Serial.print((double) (emon1.readVcc() / 1000));
    Serial.println("V");

    // Hacemos una primera lectura que desechamos. La primera nunca es fiable
    emon1.calcIrms(14800);
}

/**
 * Bucle de ejecución
 * Se ejecuta una y otra vez hasta que se apague el NodeMCU
 */
void loop() {
    // 1.- Encendemos el LED antes de leer (extrañamente parece que los valores HIGH y LOW están invertidos en el esp8266)...
    digitalWrite(PIN_LED, LOW);

    if (!clienteMQTT.connected())
        conectarMQTT();

    clienteMQTT.loop();

    // 2.- Leemos la corriente efectiva y calculamos la potencia.
    double corriente = emon1.calcIrms(14800);
    double potencia = corriente * VoltajeLinea;

    Serial.print("Corriente: ");
    Serial.print(corriente);
    Serial.println(" A");

    Serial.print("Potencia: ");
    Serial.print(potencia);
    Serial.println(" W");

    // 3.- Lo mandamos al servidor de MQTT
    sprintf(payloadMQTT, "{\"corriente\" : %d.%03d,\"potencia\" : %d.%03d}",
            (int) corriente, (int) (corriente * 1000) % 1000, (int) potencia,
            (int) (potencia * 1000) % 1000);
    Serial.print("Payload: ");
    Serial.println(payloadMQTT);
    clienteMQTT.publish(MQTT_TOPICO, payloadMQTT);
    clienteMQTT.disconnect();

    // 4.- Volvemos a apagar el led hasta la próxima
    digitalWrite(PIN_LED, HIGH);

    // 5.- Esperamos un ratito
    delay(T_PAUSA * 1000);
}
Varias cosas que destacar en este código:
  1. Los ejemplos de EmonLib para Arduino siempre utilizan un muestreo de 1480. Como yo estoy utilizando un ESP8266 que va a 80 Mhz (en lugar de los 8Mhz del Arduino Pro Mini), he multiplicado por 10 el tamaño de la muestra. Tarda más en obtener la corriente pero es bastante más estable que en el ejemplo que vimos en la entrada anterior.
  2. Además, en mis experimentos he observado que cuando se comienzan a hacer lecturas mediante llamadas al método calcIrms, la primera vez da un valor extrañamente alto, aún sin carga. Por tanto, he hecho una primera llamada en la función setup que simplemente descarto.
  3. Para el parámetro de calibración, además del ratio del transformador y el valor de la resistencia de carga, he utilizado un factor de corrección basándome en la comparación de las lecturas obtenidas al medir el consumo de una lámpara de 300W. Lo he corregido para que las lecturas se acerquen en lo posible a dicho valor, aunque quizá no sea buena idea. Lo ideal es medir con una pinza amperimétrica profesional y comparar con nuestras lecturas, aplicando entonces la corrección necesaria.
  4. Otro parámetro a tener en cuenta es el voltaje de la línea. Debemos utilizar un valor medio, aún cuando sabemos que el voltaje no es algo estable en una instalación. En mi casa oscila entre los 208V y los 234V, con lo cual he elegido 220V como el valor medio. Lo ideal sería leer el voltaje instantáneo, pero para eso necesitaríamos otro tipo de sensor.
  5. La conexión con la Wifi y con el servidor de MQTT entrarán en un bucle infinito en caso de no poder conectarse, con lo cual quizá hubiera que optimizar algo aquí.

Optimizaciones

Este es el código estándar que se utilizaría en un Arduino: una función setup que se encarga de las inicializaciones, y otra función loop que se repite de forma infinita hasta que el dispositivo se apaga. Las lecturas en este caso se hacen en dicha función y se espacian unos 10 segundos (más el tiempo de lectura). En esta configuración, el código funciona bastante bien, pero el consumo es algo alto si vamos a funcionar con una batería.
Siendo optimista, una batería 18650 de las que he comprado (en Amazon) podría tener unos 2700 mAh de capacidad, aunque estén marcados como de 3000.
He medido el consumo del circuito (aquí es donde me cargué el primer D1 mini) y, mientras está muestreando, el consumo es de unos 70-74 mA. Cuando el programa pasa a la espera, el consumo baja a unos 18-20 mA. Si hacemos la media, nos sale un consumo medio de unos 38 mA. Esto quiere decir que una batería nos duraría:
2700 mAh / 38 mA = 71 h = 2,9 días
Esto está muy bien para una prueba, pero no me veo cambiando la batería del lector cada 3 días. Así que habrá que hacer algo: algo que se llama deep sleep (sueño profundo).
El ESP8266 tiene este modo de funcionamiento que es prácticamente “catatónico“: toda la electrónica se apaga excepto un pequeño temporizador establecido por el programa. El truco consiste en que, al iniciar el temporizador, se le indica al ESP que active un puerto concreto, normalmente el D0. Si el D0 lo conectamos al pin de RESET, el resultado es que al terminar el temporizador, el ESP se reinicia y ejecuta de nuevo la función setup.
Por tanto, habría que modificar el programa para que toda la lógica transcurra en el setup y no en el loop. Una vez que tengamos una lectura válida y la hayamos enviado al servidor MQTT, nos echaremos a dormir un ratito en modo deep sleep.
En este modo el consumo baja drásticamente. En teoría, en un dispositivo puro sin electrónica añadida debería bajar al orden de microAmperios en vez de miliAmperios. Lógicamente, la unidad que estamos usando (D1 mini) tiene electrónica para el puerto USB, para el regulador de tensión, etc. Aún así, modifiquemos el código y veamos qué tal el consumo:
/**
 * Lector de consumo energético con conexión Wifi a servidor MQTT
 * Isidoro Aguilar Baeza - Julio/Agosto 2017
 * Este script se ejecuta en dispositivos NodeMCU, no en Arduino
 *
 * Librerías utilizadas:
 *   - PubSubClient(MQTT): https://github.com/knolleary/pubsubclient
 *   - EmonLib: https://github.com/openenergymonitor/EmonLib
 */

#include "Arduino.h"
#include <ESP8266WiFi.h>
#include "lib/PubSubClient.h"
#include "lib/EmonLib.h"

// Configuración de pines
#define PIN_LED            2

// Configuración de WIFI
#define WIFI_SSID    "XXXXXX"               // Nombre del AP Wifi
#define WIFI_PWD    "XXXXXX"            // Contraseña para conectar
#define WIFI_BPS    9600                // Velocidad del interfaz WIFI

// Configuración de MQTT
#define MQTT_SERVIDOR    "X.X.X.X"        // Dirección IP del broker
#define MQTT_PUERTO        1883
#define MQTT_USUARIO    "XXXXXXXXXXXXX"    // Usuario de acceso
#define MQTT_PWD        "XXXXXXXXXXXXX"    // Contraseña
#define MQTT_CLIENTE    "sensorConsumo"    // Identificador de este cliente
#define MQTT_TOPICO        "micasa/potencia"    // Tópico en el que publicaremos

// Configuración eléctrica
#define RatioTC            2000    // Ratio del transformador
#define ResCarga        91        // Resistencia de carga
#define correccion        1.145    // Factor de corrección adicional. Calculado empíricamente
#define VoltajeLinea    220        // Voltaje típico de la línea
#define T_PAUSA            55        // Intervalo entre medidas, en segundos

// Inicializamos el cliente Wifi y el MQTT
WiFiClient clienteESP;
PubSubClient clienteMQTT(clienteESP);

// Declaramos un lector de consumo de la librería EmonLib
EnergyMonitor emon1;

// Declaramos la cadena que se enviará al servidor MQTT
char payloadMQTT[30];

/**
 * Conecta con el servidor de MQTT (Mosquitto)
 */
void conectarMQTT() {
    if (!clienteMQTT.connected()) {
        Serial.print("Conectando con el servidor MQTT en ");
        Serial.print(MQTT_SERVIDOR);

        boolean conectado = false;
        while (!conectado) {
            Serial.print(".");
            if (clienteMQTT.connect(MQTT_SERVIDOR, MQTT_USUARIO, MQTT_PWD)) {
                conectado = true;
                Serial.println();
                Serial.println("Conectado");
                //clienteMQTT.subscribe(MQTT_TOPICO);
            } else {
                delay(1000);
            }
        }
    }
}

/**
 * Configuración.
 * Se ejecuta sólo una vez, al iniciar el Arduino
 */
void setup() {
    // 1.- Inicializamos el puerto serie USB
    Serial.begin(9600);
    delay(100);
    Serial.println("");
    Serial.println("");
    Serial.println("Lector de consumo eléctrico");
    Serial.println();
    Serial.print("Conectando con ");
    Serial.print(WIFI_SSID);

    // 2.- Inicializamos el módulo WIFI y nos conectamos al AP
    WiFi.persistent(false);
    WiFi.mode(WIFI_STA);
    WiFi.begin(WIFI_SSID, WIFI_PWD);

    // Establecemos un bucle de espera hasta que esté conectado...
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        Serial.print(".");
    }

    Serial.println();
    Serial.println("Conectado.");
    Serial.print("IP: ");
    Serial.println(WiFi.localIP());

    // 3.- Nos conectamos al servidor MQTT
    clienteMQTT.setServer(MQTT_SERVIDOR, MQTT_PUERTO);

    // 4.- Inicializamos el LED
    pinMode(PIN_LED, OUTPUT);
    digitalWrite(PIN_LED, LOW);

    // 5.- Indicamos dónde está conectado el sensor de corriente (pin analógico A0) y el factor de calibración.
    emon1.current(A0, RatioTC / ResCarga * correccion);

    Serial.print("Voltaje de alimentación: ");
    Serial.print((double) (emon1.readVcc() / 1000));
    Serial.println("V");

    // Hacemos una primera lectura que desechamos. La primera nunca es fiable
    emon1.calcIrms(14800);

    // 6.- Encendemos el LED antes de leer (extrañamente parece que los valores HIGH y LOW están invertidos en el esp8266)...
    digitalWrite(PIN_LED, LOW);

    if (!clienteMQTT.connected())
        conectarMQTT();

    clienteMQTT.loop();

    // 7.- Leemos la corriente efectiva y calculamos la potencia.
    double corriente = emon1.calcIrms(14800);
    double potencia = corriente * VoltajeLinea;

    Serial.print("Corriente: ");
    Serial.print(corriente);
    Serial.println(" A");

    Serial.print("Potencia: ");
    Serial.print(potencia);
    Serial.println(" W");

    // 8.- Lo mandamos al servidor de MQTT
    sprintf(payloadMQTT, "{\"corriente\" : %d.%03d,\"potencia\" : %d.%03d}",
            (int) corriente, (int) (corriente * 1000) % 1000, (int) potencia,
            (int) (potencia * 1000) % 1000);
    Serial.print("Payload: ");
    Serial.println(payloadMQTT);
    clienteMQTT.publish(MQTT_TOPICO, payloadMQTT);
    clienteMQTT.disconnect();

    // 9.- Volvemos a apagar el led hasta la próxima
    digitalWrite(PIN_LED, HIGH);

    // 10.- Finalmente, nos vamos a dormir
    pinMode(D0, WAKEUP_PULLUP);        // D0 conectada a RST
    ESP.deepSleep(T_PAUSA * 1000000);    // Establecemos el temporizador
    delay(100);        // Normalmente no se ejecutará, pero a veces es necesario.
}

/**
 * Bucle de ejecución
 * Se ejecuta una y otra vez hasta que se apague el NodeMCU
 */
void loop() {
}
Una vez modificado el código, las lecturas de consumo son las siguientes:
  • Unos 75 mA durante la lectura (5s).
  • 0.46 mA en deep Sleep (55s).
Haciendo la media:
((5 x 75 mA) + (55 x 0,46 mA)) / 60 = 6,67 mA
Luego, la duración de la batería ahora sería de:
2700 mAh / 6,67 mA = 404,80 h = 16.86 días
No está nada mal, la verdad. Pero a lo mejor no necesitamos tener lecturas cada minuto. Veamos qué pasa si las hacemos cada 3 minutos:
((5 x 75 mA) + (175 x 0,46 mA)) / 180 = 2,53 mA
2700 mAh / 2,53 mA = 1067,19 h = 44,46 días
Pues decidido: vamos a hacer las lecturas con un intervalo de unos 3 minutos, con lo cual la batería nos debería durar sobre mes y medio. No pasa nada porque es recargable, así nos entretenemos.
Por tanto, sólo habría que cambiar el parámetro T_PAUSA por el valor 175 y volver a subir el programa al dispositivo, conectarlo todo y probar.

Montaje físico

El montaje físico habría que realizarlo lo más cerca posible de la toma general de la casa, colocando el sensor CT013 alrededor de un sólo cable: o el vivo o el neutro (nunca la tierra). Un buen lugar para coloclarlo sería en la caja para el ICP, aunque ello nos obligaría a abrirla cada vez que queramos cambiar la batería.
En mi caso, lo he colocado de esta manera en una caja de conexiones que tenía cerca del cuadro general:
Montaje en pared

Conexión con HomeAssistant

Bueno, pues ya tenemos nuestro dispositivo leyendo la corriente y la potencia de nuestra casa y enviando los datos cada 3 minutos al servidor MQTT.
Ahora lo que nos queda es configurar un sensor en HomeAssistant para poder leer y mostrar los valores.
La configuración es muy fácil. En nuestro fichero configuration.yaml deberíamos activar el protocolo mqtt y deberíamos definir (en el mismo configuration.yaml o en sensors.yaml) un sensor para el tópico en el que estamos publicando los datos.
La configuración debería quedar parecida a esta:
mqtt:
  discovery: true
  broker: X.X.X.X
  port: 1883

sensor:
  - platform: mqtt
    name: Consumo Global
    state_topic: "micasa/potencia"
    unit_of_measurement: "kW"
    value_template: '{{ "%0.2f" | format((value_json.potencia | float) / 1000 ) }}'
Yo lo he configurado para que muestre la potencia en kilovatios, redondeándolo a 2 decimales. Si alguien quiere mostrar el valor en vatios, tal como viene del lector, tan sólo bastaría con cambiar la última línea por:
    value_template: '{{ value_json.potencia  }}'
Finalmente, el aspecto que tiene el sensor cuando lo vemos en HomeAssistant es el siguiente:
HA Consumo

Conclusión

A lo largo de estas tres entradas hemos construido un sensor de potencia efectiva que nos permite consultar en HomeAssistant la potencia aproximada que se está consumiendo actualmente en toda la casa. Además del circuito eléctrico, hemos creado un pequeño programa que se ejecuta en un dispositivo inalámbrico basado en ESP8266 cada tres minutos y el resto del tiempo permanece dormido. Hemos conectado los datos del dispositivo con un servidor de mensajería MQTT y hemos conseguido visualizar los datos en HomeAssistant.
Para el montaje, hemos utilizado los siguientes componentes:
  • 1 transformador de corriente SCT013-000. Comprado en Aliexpress por unos 5€.
  • 1 dispositivo inalámbrico D1 mini. Comprado en Aliexpress por unos 2,50€.
  • 1 condensador de 10 uF y 25V. Comprado en la tienda de electróncia de la esquina.
  • 2 resistencias de 1/4W y 4,7KΩ.
  • 2 resistencias de 1/4W y 22Ω.
  • 1 resistencia de 1/4W y 47Ω.
  • Placa de montaje y zócalos.
Entre el condensador y las resistencias no llegan al euro. Lo que sí es importante es comprar resistencias buenas, con una precisión alta. Para el primer montaje que hice utilicé unas resistencias que compré en Aliexpress baratísimas pero que resultaron ser desastrosas. Los voltajes del divisor de tensión no cuadraban y los valores del lector no tenían sentido.
Related Posts Plugin for WordPress, Blogger...

No hay comentarios:

Publicar un comentario