Monitor de energía de precisión con pantalla y salida de datos


En este interesante proyecto de radiopench , utilizaremos un Arduino NANO, un medidor PZEM004T v3 y una pantalla OLED de 0.96 pulgadas , y dos pulsadores de operación, que son lo más pequeños posibles.

El esquema de conexiones podemos resumirlo del siguiente modo:

  • Pin 10 : select.
  • Pin 9 :enter.
  • Pin 11 Rx (conectado a Tx pin en el PZEM).
  • Pin 12 Tx (conectado a Rx pin en the PZEM).
  • Pin 4 a SDA de la pantalla amoled.
  • Pin5a SCL de la pantalla amoled.

En este blog se ha tratado en un articulo anterior el modulo medidor PZEM004T v3 (ver PZEM-004Tv3 del fabricante Peacefair
). Esta vez leeremos el resultado de la medición del Arduino pero además lo mostraremos en una pequeña pantalla amoled de 6 líneas. Además, como no es interesante solo mostrarlo, dejaremos que el resultado fluya hacia el puerto serial como un registro pues de esta manera podemos recibir datos en una PC, etc. y crear gráficos fácilmente.

Asimismo al incluir dos pulsadores podemos variar el periodo de muestreo o resetear los resultados.

En este esquema podemos ver más en detalle todas las conexiones (la fuente se puede suprimir en caso de que lo conectemos permanentemente a un ordenador vía USB)

Hay dos pulsadores de seleccionar e ingresar en la parte superior izquierda. El Arduino NANO (compatible) se puede reemplazar por cualquier variante de la familia Arduino (incluido el ESP32) y la pantalla OLED de 0.96 pulgadas no deberían costar demasiado

Descripción de la funcionalidad de la operación

La operación es simple y el resultado de la medición de potencia se muestra en el OLED a intervalos de 1 segundo. Además, el resultado de la medición se envía al puerto serie (salida de registro) en el intervalo especificado.

Vamos a ver el significado de cada línea mostrada en el panel amoled (el orden es el correspondiente a empezar desde la parte superior de la pantalla hacia abajo):

  • Línea 1: El valor de la potencia activa se muestra como un carácter con un cuadrado doble (unidad W). El borde derecho de la primera línea es el tiempo restante hasta la salida del registro. Cuando llega a 0, el registro se genera y, al mismo tiempo, el registro se muestra en la parte superior derecha de la pantalla.
  • Línea 2: Muestra los valores de tensión y corriente (unidades V, A). El valor es un valor efectivo
  • Línea 3: Mostrar el valor de la potencia aparente (unidad VA)
  • Línea 4: Mostrar la cantidad de electricidad (energy) (unidad: kWh)
  • Línea 5: Frecuencia (unidad Hz)
  • Línea 6: Muestra el valor del factor de potencia (unidad %), y además como un gráfico de barras.

Puede ser difícil de leer porque está lleno de información en una pantalla pequeña. Sin embargo, dado que la potencia activa más importante se muestra en letras grandes en la fila superior y el factor de potencia también se muestra en el gráfico de barras en la parte inferior, debería ser relativamente fácil de ver incluso desde la distancia.

Al restablecer mientras presiona el botón de configuración del ciclo de grabación, ingresará a la pantalla de configuración del ciclo de grabación. El período de medición se puede cambiar presionando el botón SELECT y se puede especificar como 1, 2, 5, 10, 30, 60, 120, 300 segundos. El valor se confirma presionando el botón Enter y se inicia el modo de medición normal. Además, el valor establecido se registra en la EEPROM, por lo que se recuerda incluso si la alimentación está apagada.

Al presionar el botón Enter durante la operación de restablecimiento de energía, el valor de potencia integrado (valor de kWh) se puede borrar a cero. Para indicar que se ha aceptado la operación del botón, la pantalla se apaga una vez .

Dado que se emite en formato CSV separado por comas, puede leerlo tal como está en Excel. Los datos son desde el principio hasta el segundo, en el siguiente orden: segundo, voltaje (V), corriente (A), potencia aparente (VA), potencia activa (W), factor de potencia, consumo de energía (kWh) y frecuencia (Hz).
Además, se inserta la coma inicial para aclarar el delimitador porque si se inserta una cadena de marca de tiempo en el software del terminal, se puede perder el límite con el segundo valor.

A continuación podemos ver el código completo para gestionar la pantalla , los dos pulsadores y el monitor PZEM.

/* AC power meter with text log
  Measuring instrument:PZEM-004T v3.0
  Display 0.96 inch OLED (SSD1306, 128x64)

   Pin 10 energy clear button
   Pin 11 Rx (Connects to the Tx pin on the PZEM)
   Pin 12 Tx (Connects to the Rx pin on the PZEM)
  
*/

#include <EEPROM.h>
#include <PZEM004Tv30.h>                     // PZEM004Tv3 library
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>                // 0.96inch OLED
#include <MsTimer2.h>

#define vCal 1.006                           // voltage calibration coefficient(dafault=1.0)
#define iCal 0.9205                          // current calibration coefficient(dafault=1.0)

#define entPin   9                           // enter button
#define selPin  10                           // select button
#define pzemRX  11                           // recieve data (connect to PZEM TX)
#define pzemTX  12                           // send data    (connect to PZEM RX)

Adafruit_SSD1306 oled(128, 64, &Wire, 4);    // OLED setup
PZEM004Tv30 pzem(pzemRX, pzemTX);            // Pin11 to TX, Pin12 to RX

unsigned int rangeTable[8] = {1, 2, 5, 10, 30, 60, 120, 300}; // time Inerval Table
int rangeNumber;
int timeToLog;                               // remaining time to output log

float Vx, Ax, VAx, Wx, kWhx, PFx, Hzx, VARx; // measured value
char buff[8];                                // character format buffer
unsigned int logInterval;                    // log interval
volatile boolean t2Flag = false;             // timing sync variable

void setup() {
  oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);    // set OLED I2C Address 0x3C or 0x3D
  Serial.begin(115200);
  pinMode(entPin, INPUT_PULLUP);             // enter button
  pinMode(selPin, INPUT_PULLUP);             // select button

  rangeNumber = EEPROM.read(0);
  if (rangeNumber > 7) {                     // abnormal range number will be
    rangeNumber = 0;                         // set to 0
  }
  logInterval = rangeTable[rangeNumber];

  oled.clearDisplay();
  oled.display();
  oled.setTextColor(WHITE);                  // write with white character

  if (digitalRead(entPin) == LOW) {
    recIntervalSet();
  }

  startScreen();                             // show start screen
  Serial.println();                          // start message
  Serial.println(F(" , PZEM-004V3 Power meter start V0.5"));
  Serial.println(F(" , sec, V(rms), I(rms), VA, W, PF, kWh, Hz"));
  Serial.flush();

  timeToLog = logInterval;                   // set interval
  measure();                                 // get value from PZEM-004Tv3
  logPrint();                                // data out serial port
  oledDisplay();                             // display data on OLED

  MsTimer2::set(1000, timer2IRQ);            // IRQ interval 1sec
  MsTimer2::start();
}

void loop() {
  if (digitalRead(selPin) == LOW) {          // if select button pushed during running
    resetKWh();                              // clear the energy value
  }
  while (t2Flag == false) {                  // wait MsTimer2 IRQ
  }
  t2Flag = false;

  measure();                                 // get value from PZEM-004Tv3
  timeToLog --;                              // decrement log timer
  oledDisplay();                             // display present data to OLED
  if (timeToLog == 0 ) {                     // if log interval time is up,
    logPrint();                              // output log data to serial port
    timeToLog = logInterval;                 // set value for next cycle
  }
}            // end of loop

void measure() {                             // read values from PZEM-004T v3
  Vx = vCal * pzem.voltage();                // voltage(apply calibration correction)
  Ax = iCal * pzem.current();                // current(apply calibration correction)
  VAx = Vx * Ax;                             // calculate apparent power
  Wx = vCal * iCal * pzem.power();           // effective power(Use the value after calibration correction)
  PFx = pzem.pf();                           // power factor
  kWhx = vCal * iCal * pzem.energy();        // sum of energy(Use the value after calibration correction)
  Hzx = pzem.frequency();                    // line frequency
}

void logPrint() {                               // serial out for log
  static long t = 0;
  Serial.print(F(" , "));                       // this for the separator for terminal software timestamp text
  Serial.print(t); Serial.print(F(", "));       // time in second
  Serial.print(Vx, 1); Serial.print(F(", "));   // voltage
  Serial.print(Ax, 3); Serial.print(F(", "));   // current amps
  Serial.print(VAx, 2); Serial.print(F(", "));  // VA value
  Serial.print(Wx, 1); Serial.print(F(", "));   // wattage
  Serial.print(PFx, 2); Serial.print(F(", "));  // powewr factor
  Serial.print(kWhx, 3); Serial.print(F(", ")); // totall energy
  Serial.print(Hzx, 1);                         // frequency
  Serial.println();
  t += logInterval;                             // increment accumurate time
}

void oledDisplay() {                                // display information on OLED
  oled.clearDisplay();                              // clear display buffer
  oled.setTextSize(2);                              // double size character

  dtostrf(Wx, 6, 1, buff);                          // format nnnn.n
  oled.setCursor(0, 0);
  oled.print(buff); oled.print(F("W"));             // display power value

  oled.setTextSize(1);                              // standerd size character
  oled.setCursor(110, 8);
  sprintf(buff, "%3d", timeToLog);                  // format decimal 3-digit
  oled.print(buff);                                 // remainig time display
  if (timeToLog == 0) {                             // if time up
    oled.setCursor(110, 0); oled.print(F("Log"));   // display "Log" at top right
  }

  dtostrf(Vx, 5, 1, buff); oled.setCursor(0, 16); oled.print(F("V:"));
  oled.print(buff); oled.print(F("V"));             // voltage nnn.n

  dtostrf(Ax, 6, 3, buff); oled.setCursor(56, 16); oled.print(F("I: "));
  oled.print(buff); oled.print(F("A"));             // amps nn.nnn

  dtostrf(VAx, 7, 2, buff); oled.setCursor(0, 26); oled.print(F("Pappa:"));
  oled.print(buff); oled.print(F(" VA"));           // volt amps nnnn.nn

  dtostrf(kWhx, 7, 3, buff); oled.setCursor(0, 36); oled.print("Energ");
  oled.print(buff); oled.print(F(" kWh"));          // watt hours nnn.nnn


  dtostrf(Hzx, 4, 1, buff); oled.setCursor(0, 46); oled.print(F("Freq :   "));
  oled.print(buff); oled.print(F(" Hz"));           // fequency nn.n

  dtostrf(PFx * 100.0, 3, 0, buff); oled.setCursor(0, 56); oled.print(F("PF:"));
  oled.print(buff); oled.print(F("% Y    Y    Y")); // power factor nnn
  oled.fillRect(50, 61, (60 * PFx) + 1, 3, WHITE);  // Power factor bar

  oled.display();                                   // actual display will be done at here
}

void recIntervalSet() {                             // Log interval time setting by button swith
  while (digitalRead(entPin) == LOW) {              // wait till the enter button is released
    oled.clearDisplay(); oled.setCursor(0, 0);
    oled.print(F("Log interval setting"));          // start message
    oled.display();
  }
  delay(30);

  while (digitalRead(entPin) == HIGH) {
    oled.clearDisplay();
    oled.setTextSize(2);                            // double size character
    oled.setCursor(0, 0);
    oled.println(F("Set Int."));                    // display set interaval value

    oled.setCursor(35, 30);
    oled.print(logInterval); oled.print(F("sec"));  // display the value
    oled.display();

    if (digitalRead(selPin) == LOW) {
      rangeNumber ++;                               // increment range number
      if (rangeNumber > 7) {                        // if beyond upper limit
        rangeNumber = 0;                            // circulate the number
      }
      logInterval = rangeTable[rangeNumber];        // set interval time from table
    }
    while (digitalRead(selPin) == LOW) {            // wait select botton relesed
    }
    delay(30);
  }

  EEPROM.write(0, rangeNumber);                     // save value to EEPROM
}

void startScreen() {                                // start message
  oled.clearDisplay(); oled.setTextSize(2);         // double size character
  oled.setCursor(0, 0);
  oled.println(F("PZEM-004T"));
  oled.println();
  oled.print(F("int.="));
  oled.print(logInterval); oled.print(F("s"));      // display log interval
  oled.display();
  delay(2000);
}

void resetKWh() {                   // clear accumulated energy value
  Serial.println(F("Reset kWh"));   // write message on the log
  pzem.resetEnergy();               // reset value
  oled.clearDisplay();              // erase disp. to show command accepted
  oled.display();
  delay(50);
}

void timer2IRQ() {                  // MsTimer2 IRQ
  t2Flag = true;                    // set flag for loop
}

Se ha omitido la descripción del programa, pues no hay nada complicado. Sin embargo, dado que el coeficiente de corrección se define a continuación y corrige el resultado de la medición de voltaje y corriente, es mejor ajustarlo para cada dispositivo.

Si no conoce el valor de corrección, está bien establecerlo en 1.0.

  • define vCal 1.006 // coeficiente de calibración de voltaje(dafault=1.0)
  • define iCal 0.9205 // coeficiente de calibración de corriente(dafault=1.0)

En el siguiente video podemos ver una demostración práctica del dispositivo:

Video del proyecto en inglés

Fuente :El diagrama de circuito y el código del programa están disponibles en el siguiente siguiente sitio escrito en japonés http://radiopench.blog96.fc2.com/blog-entry-995.html

Un nuevo horizonte para NILMTK con OpenZmeter


En plena crisis energética, el consumo eléctrico supone una importante fuente de gasto, tanto para las empresas, como para los ciudadanos podríamos haber reducido  las emisiones  de  32,23 Megatoneladas de Co2.

Para intentar optimizar el consumo eléctrico de cualquier entidad, es necesario previamente medir éste de la forma más fiel posible, pues no se puede optimizar nada que no se pueda medir, siendo lo ideal monitorizarlo en tiempo, con objeto de generar alarmas, registrar eventos y crear informes de análisis de calidad de energía.

Algunas de las utilidades de monitorizar el consumo eléctrico:

  • Estudiar los hábitos de consumo en función de las franjas horarias con objeto de promover políticas destinadas a su reducción.
  • Detectar picos indebidos de consumo en horas de baja actividad con objeto de detectar “consumos fantasmas” y consumos indebidos.
  • Racionalizar el gasto energético consumiendo sólo lo necesario evitando derroches superfluos, contribuyendo de esta manera a luchar contra el cambio climático.
  • Predecir el consumo energético estudiando el histórico.
  • Trasladar los picos de consumo, cuando sea posible, a otras franjas horarias con objeto de no sobrecargar la red.
  • Detectar cortes de suministro con objeto de generar alarmas o tomar medidas correctivas.
  • Diagnosticar posibles fallos tanto en las instalaciones como en los aparatos eléctricos.
  • Servicios relacionados con AAL (Ambient Assisted Living), es decir servicios para la independencia de las personas mayores .
  • Detectar consumos indebidos causados por sabotajes de las instalaciones.
  • Optimización de la demanda contratada.

Usando técnicas de monitorización según muchos expertos se puede ahorrar desde un 15% hasta  20%. Y no solo esto va de ahorro económico pues por ejemplo en España en 2020 podríamos haber reducido  las emisiones  de  32,23 Megatoneladas de Co2.

En este contexto, contamos con el OpenZMeter, un medidor monofásico de energía eléctrica, que es además también analizador de calidad de la energía. Este dispositivo, es  de código abierto,  cuenta  con capacidades de IoT y permite medir una amplia gama de variables eléctricas a una elevada frecuencia de muestreo (15625Hz).

Este nuevo código intenta completar  las capacidades del OZM adaptando para ello la herramienta de desagregación de la demanda NILMTK, mediante la ampliación del  uso de TimeStamp de 13 dígitos,  la creación  de dos nuevos convertidores y conversores con el soporte de transitorios hasta el orden 50 de tensión , corriente y potencia, la generación de dos nuevos DS y el desarrollo del nuevo  código abierto para la desagregación. 

Para ello, el entrenamiento contempla el uso de dos de los algoritmos de desagregación más conocidos, como son el de Optimización Combinatoria (CO) y el Modelo de Markovv Factorial Oculto (FHMM).

OZM requiere para la desagregación un periodo bastante más pequeño de unos 30” en contraposición con los requeridos con otros DS públicos que suele estar en torno a los 15’.

Tras el entrenamiento, los datos agregados pueden introducirse en el modelo para su desagregación y posterior análisis de los resultados obtenidos obteniéndose un error que no supera el 1%.

Gracias a que NILMTK proporciona funciones para calcular las métricas de los resultados de cada algoritmo de desagregación, en este estudio podemos comparar resultados obtenidos con diferentes métodos, periodos de muestreo, algoritmos y DS.

Resultados DEPS                                

RESULTADOS  OZM

Destaca el error mínimo en la desagregación así como  las métricas           MNEAP y RMSE cuyo comportamiento son bastante superiores en OZM frente a otros DS públicos

Conclusiones

  •  El uso de componentes armónicos se   demuestra que mejora el resultado de  casi  todas las métricas dependiendo mucho del tipo del dispositivo eléctrico.
  •  El comportamiento del nuevo DS  supera en tiempos de muestreo y métricas a otros DS públicos.
  • Se entrega código abierto  funcional que permite desagregar por completo una instalación convencional.
  •  En un horizonte futuro, como continuación de este trabajo, se propone obtener un mecanismo automatizado para desplegar en una instalación convencional para hacer posible mostrar en tiempo real  la desagregación del consumo energético de los diferentes aparatos eléctricos que lo constituyen.

Video resumen

En el siguiente video podemos ver un resumen en video de los conceptos y resultados mas importantes que hemos visto en este post:

NOTA : El código fuente del proyecto lo podemos encontrar en https://github.com/crn565