Thursday, January 21, 2016

RaspberryPi

I received a RaspberrypPi B+ as a Christmas gift last year. My plan was to use this low powered server to host Emoncms.

Setup

I started with a 4GB SD card and installed a new image of Rasbian Wheezy which was later upgraded to kernel 4.1. This was followed by installation of Emoncms and nodejs. After all this I had a blank Emoncms site running on my Raspberry. 

Communication

Although the Raspberry has plenty of pins, they are all digital. The power measurement needs analog pins for ADC operation. Usually, this feat is accomplished by a secondary board which in my case would be my old Arduino Mini.

Raspberry can talk to Arduino over Serial or I2C ports. Most people use Serial but I thought that it might not be reliable, plus it limits communication to single device. I2C allows multiple slave devices and so I proceeded with I2C communication. It uses same number of ports (3) for connection.

Node provides I2C communication through a package conveniently named i2c which was installed globally:
npm -g i2c

The data filing into Emoncms was done a node script which queried Arduino periodically over I2C channel and logged the data into Emoncms. The address 04 was identified by running i2cdetect. My script expected 2 data pieces to be send up as 4 bytes each with a max of 12 bytes (4 set aside for future use).

root@raspberrypi:~/dev# i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- 04 -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

Node Script

const fs = require('fs');
const i2c = require("i2c");
const wire = new i2c(0x04, { device: '/dev/i2c-1', debug: false });
const request = require('request');

setInterval(getI2CData, INTERVAL);

function getI2CData() {
    //Just issue a read request for 12 bytes
    wire.read(12, function (err, data) { 
        if (err) logErr(err);
        else parseData(data);
    });
}

//power1[4 bytes], power2[4 bytes]
function parseData(data) {
    for (var i = 0; i < data.length; i++) { //Remove bad data
        if (isNaN(parseInt(data[i]))) {
            data[i] = 0;
        }
    }

    //data needs to be parsed in this order
    var obj = {
        power1: bytesToLong(data, 0),
        power2: bytesToLong(data, 4)
    };

    var json = JSON.stringify(obj);
    var url = URL + json;     //File data
    request(url, function (err, response, body) {
        if (err) {
            logErr(err);
        }
    });
}

function bytesToLong(arr, index) {
    var num = parseInt(arr[index++]);
    num |= parseInt(arr[index++]) << 8;
    num |= parseInt(arr[index++]) << 16;
    num |= parseInt(arr[index++]) << 24;
    return num;
}
function bytesToInt(arr, index) {
    var num = parseInt(arr[index++]);
    num |= parseInt(arr[index++]) << 8;
    return num;
}

Arduino Sketch


Arduino basically just listens to I2C read request and sends out data. My first attempt was to gather data in the read request but the I2C response needed to be sent out promptly in the next frame.  The power calculation delay caused the data sequence to be disrupted and nothing was received at Raspberry end. It seems the slave can obtain some grace time but I took a different approach to solve this issue. I made Arduino continuously gather data every 2.5 seconds and just write back the last data reading.

//Outputs power1[4 bytes], power2[4 bytes] over I2C
//I2C address is 0x04
//http://blog.retep.org/2014/02/15/connecting-an-arduino-to-a-raspberry-pi-using-i2c/
//GPIO2 (SDA) <-> Pin4 (SDA)
//GPIO3 (SCL) <-> Pin5 (SCL)

#include <wire.h>
#include <emonlib.h>

//http://openenergymonitor.org/emon/buildingblocks/calibration
//New calibration = (correct reading / emonTx reading) * current calibration
#define VOLTAGE 121.5
#define CURRENT_CALIBRATION (100/0.05/21.7)
#define NUMBER_OF_SAMPLES 1000    //200ms at 60Hz, 1172 samples was giving 205ms
#define CURRENT_PIN1 A0  //Analog A0
#define CURRENT_PIN2 A1  //Analog A1
#define OUTPUT_LED 2

#define OUTPUT_LED_BLINK_DELAY 250
#define SLAVE_ADDRESS 0x04
#define CAPTURE_INTERVAL 2500

EnergyMonitor emon1, emon2;
long power1 = 0;
long power2 = 0;
byte bytes[14]; //Bytes array for sending out through I2C
int bytePos;  //Index position in bytes
uint32_t nextTime = 0;

void setup()
{
  delay(2000);  //Delay for allowing serial monitor to be launched
  Serial.begin(19200);

  pinMode(OUTPUT_LED, OUTPUT);
  digitalWrite(OUTPUT_LED, LOW);
  emon1.current(CURRENT_PIN1, CURRENT_CALIBRATION);
  emon2.current(CURRENT_PIN2, CURRENT_CALIBRATION);

  Wire.begin(SLAVE_ADDRESS);
  Wire.onRequest(sendData);
  Wire.onReceive(receiveData);
}

void loop() {
  uint32_t currentTime = millis();

  if (currentTime > nextTime) {
    double Irms1 = emon1.calcIrms(NUMBER_OF_SAMPLES);
    double Irms2 = emon2.calcIrms(NUMBER_OF_SAMPLES);
    power1 = (long) (Irms1 * VOLTAGE);
    power2 = (long) (Irms2 * VOLTAGE);
    Serial.print(power1);
    Serial.print(',');
    Serial.println(power2);
    blink();
    nextTime = currentTime + CAPTURE_INTERVAL;
  }
}

void receiveData(int byteCount) {
  while (Wire.available()) { //This does nothing .. future use
    int number = Wire.read();
  }
}

void putLongData(long num)
{
  bytes[bytePos++] = (num & 0xFF);
  bytes[bytePos++] = ((num >> 8) & 0xFF);
  bytes[bytePos++] = ((num >> 16) & 0xFF);
  bytes[bytePos++] = ((num >> 24) & 0xFF);
}
void putIntData(long num)
{
  bytes[bytePos++] = (num & 0xFF);
  bytes[bytePos++] = ((num >> 8) & 0xFF);
}

void sendData() {
  //gatherData();  //Unable to gather data and return data in time within I2C callback

  bytePos = 0;
  putLongData(power1); //4 bytes
  putLongData(power2); //4 bytes
  putIntData(distance); //2 bytes
  Wire.write(bytes, 10);
}



Later I started logging local temperature as well; this was done at Nodejs end. The temperature itself was obtained from openweathermap.org. So now I could see power consumption against temperature.

const OPENWEATHERURL = 'http://api.openweathermap.org/data/2.5/' +
    'weather?zip=12345,us&&appid=____&mode=json&units=imperial';

setInterval(getOutsideTemp, INTERVAL_WEATHER);
function getOutsideTemp() {
    request(OPENWEATHERURL, function (err, response, body) {
        if (err) {
            logErr(err);
        }
        else {
            try {
                var data = JSON.parse(body);
                if (data && data.main) {
                    outsideTemp = data.main.temp;
                }
            }
            catch (ex) { }
        }
    });
}





Power

Power against outside temperature


I was a little concerned about the reliability of this setup since there were many moving parts. I added logging by rewiring output and error streams to log files. I am happy that the system has been functioning for last 2 months, the only errors logged were due to failed resolution of openweather.org

const Console = console.Console;
const output = fs.createWriteStream('./stdout.log');
const errorOutput = fs.createWriteStream('./stderr.log');
const logger = new Console(output, errorOutput); //process.output

function log(text) {
    logger.log((new Date()).toLocaleString() + " " + text);
}
function logErr(text) { 
    logger.error((new Date()).toLocaleString() + " " + text);
}


The final piece to this equation was to configure the logging to start automatically. I created an init script using the sample provided at init-script-template and adjusted it to start my node script.
dir="/root/dev"
cmd="node EnergyMeter.js"
user="root"

The script was made executable and registered as an init script.
sudo chmod +x /etc/init.d/energymeter
sudo update-rc.d energymeter defaults 

That was it; the script would start data logging on system boot.

Saturday, January 16, 2016

Whole House Power Monitoring


I have been tinkering with electronics projects for a long time. I got some Arduino Nano boards in 2012 and started with some silly projects using the stock samples. I wanted to do something more productive and interesting. What could be better than measuring home power consumption?

More research led to an open source web application named Emoncms which seemed to fit my bill.

Data Capture

Now presentation was only half the battle, data capture itself was the other half. I decided to use Arduino Mini for this project which are based on ATmega168, it has plenty of digital and analog pins but only has 16KB of flash memory.

The basic power equation is Power = Voltage x Current. The voltage usually stays around 120V, so I decided to concentrate on measuring the current. So how does one measure current without dealing with live wire, the answer is Current Transformer. They go around the live wire like clamps and generate a safe small voltage corresponding to the current flowing.

US homes usually have two 120 V supply lines, these are out of phase and add up to give 240V used for dryer and cooking ranges. The two lines are spread across the house to all the receptacles.

I got two 100A rated Current Transformers SCT-013-000, they snap around the wire carrying current. They has to be installed in the main breaker panel when the supply for the house came in. This was the difficult part and I installed them very carefully and secured them with zip-ties. The transformers terminate as male mono connectors and I measured a small voltage on my multi-meter. Good, the breaker panel was closed and I was happy that the most critical part was over.

If you are unfamiliar with electrical connections then I strongly suggest getting an electrician to do this part.


The two CTs up close
Breaker panel

Filing Data

I decided to host data on Emoncms itself. Programming the Arduino is a piece of cake. Download the IDE, grab some sample code, flash the Arduino and you are ready to go. But Arduino by itself won't get data anywhere, so I needed something to get it connected.

This brought me to the Ethernet module ENC28J60 which can communicate with Arduino. I used EtherCard library to make the bridge; the ENC28J60 modules takes 4 pins to talk to Arduino. Thankfully, Arduino has plenty and I was not using it for anything else. Armed with some sample code, I was soon pinging google.com. 

My original power equation was insufficient to measure the actual AC power and instead I used a library name EmonLib for doing all the calculations. The power from both power lines was filed as 2 separate input feeds and then added up into a third feed at Emoncms.

The power calculations seemed a bit off but the delta seemed to be correct which could be due to the transformers or something else but I was so excited to see the graph jump when I turned on a lamp. Even a 5-10W consumption was getting registered, I was pretty happy.

Code

Working with EmonLib wasn't a very good experience for me, it felt very cryptic. Atmega168 has just 2KB of RAM and using the library with Serial debugging was sometimes resulted in memory issues. PSTR came to my rescue, it allowed strings to be saved into flash instead of SRAM.

#include <ethercard.h>
#include <emonlib.h>
...
#define    ERROR_NONE    9
#define    ERROR_ETHERNET    2
#define    ERROR_DHCP    3
#define    ERROR_DNS    4

byte mymac[] = { 0x00,0x1C,0xBF,0xB1,0xA0,0x00 }; // ethernet interface mac address, must be unique on the LAN

EnergyMonitor emon1, emon2;
byte Ethernet::buffer[700];
uint32_t nextTime = 0;
Stash stash;
byte errorStatus = 0;

void setup() {  
  delay(2000);  //Delay for allowing serial monitor to be launched
  Serial.begin(9600);

  pinMode(OUTPUT_LED, OUTPUT);     
  digitalWrite(OUTPUT_LED, HIGH);    

  Serial.print("Ethernet: ");
  if (ether.begin(sizeof Ethernet::buffer, mymac) == 0) {
    Serial.println("failed");
    errorStatus = ERROR_ETHERNET;    
    return;
  }
  else{
    Serial.println("ok");
  }

  Serial.print("DHCP: ");
  if (!ether.dhcpSetup()){
    Serial.println("failed");
    errorStatus = ERROR_DHCP;       
    return;
  }
  else{
    ether.printIp("", ether.myip);   
  }

  if (!tryDns()){
    return;
  }

  digitalWrite(OUTPUT_LED, LOW);
  errorStatus = ERROR_NONE;
  emon1.current(CURRENT_PIN1, CURRENT_CALIBRATION);
  emon2.current(CURRENT_PIN2, CURRENT_CALIBRATION);
}

void loop()
{   
  uint32_t currentTime = millis();
  ether.packetLoop(ether.packetReceive());

  if (currentTime > nextTime) {
    switch(errorStatus)
    {
    case ERROR_NONE: 
      break;
    case ERROR_DNS:
      if (!tryDns()){
        blink(ERROR_DNS);
        delay(500);
        return;
      }

      errorStatus = ERROR_NONE;
      break;
    case ERROR_ETHERNET:
      blink(ERROR_ETHERNET);
      delay(500);
      return;
    case ERROR_DHCP:
      blink(ERROR_DHCP);
      delay(500);
      return;
    }

    double Irms1 = emon1.calcIrms(NUMBER_OF_SAMPLES);               
    double Irms2 = emon2.calcIrms(NUMBER_OF_SAMPLES);

    //First reading is incorrect, so skip it
    if (nextTime == 0){
      nextTime = currentTime;
      delay(500);
      return;
    }

    double watt1=Irms1*VOLTAGE;
    double watt2=Irms2*VOLTAGE;    
    update(watt1, watt2);

    nextTime = currentTime + LOGGING_INTERVAL;
    blink(1);
  }
}

const char website[] = "emoncms.org";    //"80.243.190.58"

void update(double watt1, double watt2)
{
  byte sd = stash.create();
  Stash::prepare(PSTR("GET /emoncms/input/post.json?node=9&apikey=$F&json={power1:$D,power2:$D} HTTP/1.1" "\r\n"
    "Host: 80.243.190.58" "\r\n"      
    "User-Agent: EtherCard" "\r\n" 
    "Connection: close" "\r\n"
    "\r\n"
    "$H"),
  PSTR(EMON_APIKEY), (word)watt1, (word)watt2, sd);
  ether.tcpSend();
}

bool tryDns()
{
  Serial.print("Dns: ");
  if (!ether.dnsLookup(website))  {
    Serial.println("failed");
    errorStatus = ERROR_DNS;   
    return false;     
  }
  else{
    Serial.println("ok");
  }
  return true;
}


End Result

And here was the end result, a small box with both the module. The box just had a reset and a LED which blinked on every data write and blinked crazily if things went not good. This setup ran for more than a year non-stop.

I future I might add a small transformer to measure the actual voltage or visualize the data elsewhere instead of Emoncms.

Nothing much inside, power came from a wall adapter.

The project box with an opening for serial programming