Sunday, 15 July 2018

Hybrid ESP8266+UNO Energy Measurement

To complement my home automation system I needed to add a multi-channel power measurement system to my DB board. I figured four channels is a good place to start: total incoming (or one day outgoing) power, house power, geyser (hot water heater) power and cottage power.

I tried the Espurna analog energy meter and it's great, the only issue is it's one channel. While it's not too hard to add some analog current transformer multiplexing, I decided it would be easier to use an Arduino Nano for measurement and an ESP8266 (ESP-01) for transmitting the measurements via MQTT. This was version 1.0. Unfortunately, with the installation in a nice IP65 box mounted on the wall next to the DB board, it wasn't so easy to update the firmware with gradual improvements...

I discovered this hybrid board at Banggood recently and it's great! It has an Arduino Uno form factor with the usual Atmega328p microcontroller which is good for stability and robustness. It then couples a ESP8266 via the serial ports and also breaks out the ESP GPIOs just in case. Perfect! I have so many plans for these boards :) I am concerned about the stability and robustness of ESP8266s for long-term installations such as home automation systems and so I think that devices using this are the way forward given that the Atmega328 is well proven.


The on-board USB / ESP / Atmega serials can be configured with DIP switches:



I used clip-on current transformers (30 A ones for the various circuits and a 100 A one for the incomer) and a normal linear transformer for the voltage measurement. Much to my surprise, the mains voltage at my house fluctuates between around 208 V and 230 V! It's important to measure this as this is a very large error on the "ideal" 220 V.

I built four of the standard Arduino Emonlib (Open Energy Monitor) circuits for each of the channels as well as a voltage channel. My 50% DC bias voltage was common - no need to build four of these too! Unfortunalty, I don't have a circuit diagram of the final design. I used 1% resistors everywhere and I also used 3.3 V zeners for spike protection as used in the standard designs. A photo of the (almost) final product is below. I simply soldered all the bits to the plugs and wired the plugs to arduino pins.


The wiring isn't neat yet but it's a work in progress!

I found that while it is indeed possible to power the board from the 6 V 500 mA linear transformer that is used for voltage measurement, the start up time is very slow and the power on reset didn't always trigger. Instead, I wired up a 5 V switch mode power supply.

The firmware is separated into two parts: the ESP and the Atmega. The Atmega simply runs a fleshed out Emonlib example (for four channels) and formats the measurements into JSON and then dumps that long string to the serial port every five seconds. For the ESP I had initially written custom firmware which simply packaged the received JSON and sent it out via MQTT but for stability and flexibility I now use Sonoff-Tasmota which supports transmitting arbitrary text from the serial port. I suppose at a later stage I could also parse the text in Tasmota and automate some things without a central server...

On my server I run Node-Red which parses the received MQTT JSON message and splits it into separate MQTT messages with more meaningful topics. The server also initially calculated the total energy per channel in kWh but I am not sure about the accuracy due to missed messages, etc. and so I now do that calculation on the Atmega which is about as good as it gets! I'll feed back the comparison soon...

The code for the Atmega is below (it's a bit rushed...). 

#define V_CALIBRATION 991.6   //1017.35//1065.79
#define I_CALIBRATION 100
#define I_OFFSET -0.06
#define half_wavelengths 100
#define mtimeout 2000

#include "XEmonLib.h"             // Include Emon Library

EnergyMonitor ct1, ct2, ct3, ct4;
float kWh1, kWh2, kWh3, kWh4;           //energy since last restart

int led = LOW;
String json;

long deltaT = 0;
long timeNow = 0;
long lastTX = 0;
long txPeriod = 5000;

void setup()
{
  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);

  Serial.begin(115200);

  pinMode(A1, INPUT);
  pinMode(A2, INPUT);
  pinMode(A3, INPUT);
  pinMode(A4, INPUT);
  pinMode(A5, INPUT);

  ct1.current(2, I_CALIBRATION, I_OFFSET);      // Current: input pin, calibration.
  ct2.current(3, I_CALIBRATION, I_OFFSET);
  ct3.current(4, I_CALIBRATION, I_OFFSET);
  ct4.current(5, I_CALIBRATION, I_OFFSET);

  ct1.voltage(1, V_CALIBRATION, 0.79);           // Voltage: input pin, calibration, phase_shift
  ct2.voltage(1, V_CALIBRATION, 0.79);           // Tweak phase_shift until power factor ~ 1 on
  ct3.voltage(1, V_CALIBRATION, 0.79);           // a resistive load such as a hot water geyser.
  ct4.voltage(1, V_CALIBRATION, 0.79);

  //Do some fake readings to force everything to settle otherwise first reading is very wrong
  analogReference(INTERNAL); //internal 1.1V ref
  float temp;
  for (int i = 0; i < 100; ++i) {
    temp += analogRead(A1);
  }
  json = String(temp);

  delay(1000); //wait for power to settle

  digitalWrite(LED_BUILTIN, LOW);

  timeNow = millis();
}

void loop()
{
  led = !led;
  digitalWrite(LED_BUILTIN, led);

  ct1.calcVI(half_wavelengths, mtimeout);
  ct2.calcVI(half_wavelengths, mtimeout);
  ct3.calcVI(half_wavelengths, mtimeout);
  ct4.calcVI(half_wavelengths, mtimeout);

  deltaT = millis() - timeNow;
  timeNow = millis();

  // Calculate energy from last measurements:
  float dT = (float)deltaT / (60.0 * 60.0 * 1000.0 * 1000.0);
  kWh1 = kWh1 + ct1.realPower * dT;
  kWh2 = kWh2 + ct2.realPower * dT;
  kWh3 = kWh3 + ct3.realPower * dT;
  kWh4 = kWh4 + ct4.realPower * dT;

  // Transmit to ESP:

  if (millis() >= (lastTX + txPeriod)) {
    //ESP buffer is 128 bytes so must keep it less than that
    json = "{\"V\":" + String(ct1.Vrms, 0)
           + ",\"I\":[" + String(ct1.Irms) + "," + String(ct2.Irms) + "," + String(ct3.Irms) + "," + String(ct4.Irms)
           + "],\"R\":[" + String(ct1.realPower, 0) + "," + String(ct2.realPower, 0) + "," + String(ct3.realPower, 0) + "," + String(ct4.realPower, 0)
           + "],\"A\":[" + String(ct1.apparentPower) + "," + String(ct2.apparentPower) + "," + String(ct3.apparentPower) + "," + String(ct4.apparentPower)
           //  + "],\"F\":[" + String(ct1.powerFactor, 1) + "," + String(ct2.powerFactor, 1) + "," + String(ct3.powerFactor, 1) + "," + String(ct4.powerFactor, 1)
           + "],\"E\":[" + String(kWh1, 2) + "," + String(kWh2, 2) + "," + String(kWh3, 2) + "," + String(kWh4, 2) + "]}" ;

    Serial.print(json);

    lastTX = millis();
  }
}

Once it's all fired up this is how it looks from the ESP's perspective:


I hope you find it useful!

No comments:

Post a Comment

Hybrid ESP8266+UNO Energy Measurement

To complement my home automation system I needed to add a multi-channel power measurement system to my DB board. I figured four channels is ...