Home Energy Project

 

 

Here is a brief description of my home energy project. It is based on the Arduino microcontroller and it monitors all the power that my house uses and uploads the data to the Emoncms web site where I can then monitor it from anyplace there is a net connection.
The design and code were inspired by these three great sites:

Open Energy Monitor Site

Desert Home

Emoncms

 

For my friends not in the U.S., my house located in New York has a 3 wire, 240 volt, 200 amp service entrance.

See the sites to the left regarding the details. Basically the circuit uses 2 current transformers install in my circuit breaker box; one on each line. They feed an Arduino which measures the current the house is using through induction. It also knows the voltage. With current and voltage know, it figures out power usage. It then uploads the measurements to a super site called Emoncms. Emoncms houses the data and presents the data in graphical form. The screen shot below shows my dashboard on Emoncms. I plot current, power, KW usage and outside temp. I am working on adding cost display too.

 

The dashboard is just one of many ways to view your data. It is very customizable.

Two of these current transformers are mounted inside the circuit breaker box. They come from China. They put out a current in direct proportion to the wire they are wrapped around. This one can handle 200 amps in which case it puts out 33ma. You use a burden resistor across it to produce a voltage with which the Arduino can measure. Since I know the turns ratio, I can calculate the current based on the output current (6060 turns, therefore ratio is 6060:1.

My breadboard layout of my power monitor. The voltage reference is a 12.6 volt transformer. The Arduino Uno is used along with the Arduino Ethernet Shield. On top of that is a Radio Shack Arduino breadboard shield which houses the voltage divider resistors and burden resistors..
The project was then mounted in a plastic box and attached to the wall in my garage next to the circuit break box.

I ran a network cable to the garage to provide internet access.

Cover of the box removed showing one of the CT installed.

Both CTs are now installed in the box. It was a tight fit but I made it.

Basic circuit used to feed the Arduino. The CT circuit is duplicated for each CT. Values shown are NOT the final values used. Thanks to David from Desert Home for the diagram.

 

Below is the Arduino sketch that I am using currently. It is a combination of the energy monitor and temp sensor program.
It also sends the measurement data to Emoncms so I can view it on line from anywhere in the world. Most of the code came from the Emoncms site.

 

// 7/22/13 changed Ical to .86
// 7/18/13 added temp code
// 7/17/13 correct sd read loop, added SRAM code
// 7/14/13 Added second CT code

// 7/12/13 added SD card to store kilowatts
// 7/4/13 added cost
// 7/2/13 final ver put on line
// 6/30/13 added emoncms code to monitor code
// 6/28/13 add all 3 boards together
// 6/25/13 power monitor program


#include <SD.h>
#include <SPI.h>
#include <Ethernet.h>
#include <JeeLib.h> // https://github.com/jcw/jeelib

#include <OneWire.h> //for one wire temp sensor
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 7 // Data wire is digital pin 7 on the Arduino
#define TEMPERATURE_PRECISION 12


// Setup a oneWire instance w/any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);
// arrays to hold device addresses – replace with your sensors addresses
//DeviceAddress insideThermometer = { 0x28, 0x37, 0x38, 0xB7, 0x03, 0x00, 0x00, 0x3F};
DeviceAddress outsideThermometer = { 0x28, 0x45, 0x1F, 0x61, 0x03, 0x00, 0x00, 0x68};

// code used to show free sram
/*
int freeRam () {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
} */

File myFile; // used to store total kilowatt reading

int bc = 0; // used to bypass first time thru loop
int j; //loop counter
char kwh[10]; //temp array used to convert string to float

byte mac[] = {   }; // my second ethernet mac address
byte ip[] = {
192,168,1,99 }; //I am using ip=99, port 1234

typedef struct { int power1, power2, power3, voltage; }
PayloadTX;
PayloadTX emontx;

// Enter your apiurl here including apikey:
char apiurl[] = "http://emoncms.org/api/post.json?apikey=YOURAPIKEY&json=";

//char timeurl[] = "http://emoncms.org/time/local.json?apikey=YOURAPIKEY";
// For posting to emoncms server with host name, (DNS lookup) comment out if using static IP address below
// emoncms.org is the public emoncms server. Emoncms can also be downloaded and run on any server.
//char server[] = "emoncms.org";
//byte server[] = { 213.138.101.177 }; // emnocms.org ip

IPAddress server(213,138,101,177); // emoncms server IP for posting to server without a host name, can be used for posting to local emoncms server

//------------------------------------------------------------------------------------------------------
// The PacketBuffer class is used to generate the json string that is send via ethernet - JeeLabs
//------------------------------------------------------------------------------------------------------
class PacketBuffer :
public Print {
public:
PacketBuffer () :
fill (0) {
}
const char* buffer() {
return buf;
}
byte length() {
return fill;
}
void reset()
{
memset(buf,NULL,sizeof(buf));
fill = 0;
}
virtual size_t write (uint8_t ch)
{
if (fill < sizeof buf) buf[fill++] = ch;
}
byte fill;
char buf[150];
private:
};
PacketBuffer str;
//--------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------

//datastreams
char kilowatt[] = "Kilowatt";
char RealPower[] = "Power";
char RMSCurrent[] = "RMSCurrent";

const int voltageSensor = A0;
const int currentSensor = A1; //CT #1
const int currentSensor2 = A2; //CT #2
const int numberOfSamples = 3000;

// Calibration constants
const float AC_WALL_VOLTAGE = 122.0;
const float AC_ADAPTER_VOLTAGE = 7.40;
const float AC_VOLTAGE_DIV_VOUT = 1.23;
const float CT_BURDEN_RESISTOR = 55;
const float CT_TURNS = 6060.6;
const float CT_BURDEN_RESISTOR2 = 55;

// Calibration coefficients
const float VCAL = 1.00;
const float ICAL = 0.86;
const float PHASECAL = 0.9;
const float ICAL2 = 0.86;

// Calculated ratio constants, modified by VCAL/ICAL
const float AC_ADAPTER_RATIO = AC_WALL_VOLTAGE / AC_ADAPTER_VOLTAGE;
const float AC_VOLTAGE_DIV_RATIO = AC_ADAPTER_VOLTAGE / AC_VOLTAGE_DIV_VOUT;
const float V_RATIO = AC_ADAPTER_RATIO * AC_VOLTAGE_DIV_RATIO * 5 / 1024 * VCAL;
const float I_RATIO = CT_TURNS / CT_BURDEN_RESISTOR * 5 / 1024 * ICAL;
const float I_RATIO2 = CT_TURNS / CT_BURDEN_RESISTOR2 * 5 / 1024 * ICAL2;

// Sample variables
int lastSampleV, lastSampleI, sampleV, sampleI;
int lastSampleI2, sampleI2; //using same V for both CTs

// Filter variables
float lastFilteredV, lastFilteredI, filteredV, filteredI;
float lastFilteredI2, filteredI2; //using same V for both CTs

// Power sample totals
float sumI, sumV, sumP, sumI2, sumV2, sumP2;

float instantcost, kilowattcost, tempF, tempC;
const float crate = 0.0544; // current electric rate per kilowatt/hour

// Phase calibrated instantaneous voltage
float calibratedV;
float calibratedV2;

// Calculated power variables
float realPower, apparentPower, powerFactor, voltageRMS, currentRMS;
float realPower2, realPowert, apparentPower2, powerFactor2, currentRMS2, currentRMSt;
unsigned long last_kWhTime, kWhTime;
float kilowattHour = 0.0;
float kilowattHour2 = 0.0;
float kilowattHourt = 0.0;

EthernetClient client;

void setup() {
Serial.begin(9600);
//Serial.println("Begin");

sensors.begin(); // Start up the library for temp sensor
delay(1000);
// set the resolution
sensors.setResolution(outsideThermometer, TEMPERATURE_PRECISION);
delay(1000);

Ethernet.begin(mac, ip);

// On the Ethernet Shield, CS is pin 4. It's set as an output by default.
// Note that even if it's not used as the CS pin, the hardware SS pin
// (10 on most Arduino boards, 53 on the Mega) must be left as an output
// or the SD library functions will not work.
pinMode(10, OUTPUT);

if (!SD.begin(4)) { // SD card uses pin 4 as CS because ethernet uses pin 10
Serial.println("SD fail");
return; }

}

void loop() {

//Serial.print(F("free SRAM "));
//Serial.println(freeRam());

//Serial.println(F("Loop"));
sensors.requestTemperatures();
tempC = sensors.getTempC(outsideThermometer); //get outside temp
tempF = (DallasTemperature::toFahrenheit(tempC));

calculatePower1();
calculatePower2();

realPowert = realPower + realPower2;
kilowattHourt = kilowattHour + kilowattHour2;
currentRMSt = currentRMS + currentRMS2;

//Serial.print(F("KWHT is "));
//Serial.println(kilowattHourt);

instantcost = realPowert * (crate / 1000); //instant power cost

myFile = SD.open("killog.txt");
if (myFile) { // check to see if killlog.txt exists and open
//Serial.println(F("Open killog"));

if (kilowattHourt < 2.0) { // either just starting or Arduino has reset
//myFile = SD.open("killog.txt"); //killog.txt holds kilowatt hour running total
j = 0;
do {
// test for kwh full
if (j == sizeof(kwh)) {
Serial.println(F("line too long"));
break; }

kwh[j] = myFile.read(); }
while (kwh[j++] != '\r');

kilowattHourt = atof(&kwh[0]); //convert read string to float
//Serial.print(F("KWH from SD "));
//Serial.println(kilowattHourt);
myFile.close();}

else {
myFile = SD.open("killog.txt", FILE_WRITE); //open file for writing
//Serial.print(F("Write KWH "));
//Serial.println(kilowattHourt);
myFile.seek(0); //set to write to first byte of file
myFile.println(kilowattHourt);
myFile.close(); } //save kilowattHour to file

} // end of if myfile code
else {
// if the file didn't open, print an error:
Serial.println(F("error killog")); }


kilowattcost = kilowattHourt * crate;

//Serial.print(F("V "));
//Serial.println(voltageRMS);
//Serial.print(F("I "));
//Serial.println(currentRMSt);
//Serial.print(tempF);
//Serial.println(F(" F"));
/*Serial.print("Instant $ ");
Serial.println(instantcost);
Serial.print("Total KW $ ");
Serial.println(kilowattcost); */

if (bc > 0) {

str.reset(); // Reset json string
str.print("{kilowattHour:");
str.print(kilowattHourt); // Add power reading
str.print("},{realPower:");
str.print(realPowert); // Add power reading
str.print("},{currentRMS:");
str.print(currentRMSt); // Add power reading
//str.print("}"); //comment out this line when adding cost lines
str.print("},{InstantCost:");
str.print(instantcost); // Add power reading
str.print("},{TotalCost:");
str.print(kilowattcost);
str.print("},{OusideTemp:");
str.print(tempF);
str.print("}"); //end string


if (client.connect(server, 80)) {
str.print("}\0");
//Serial.println();
//Serial.print(F("Send "));
//Serial.println(str.buf);
client.print(F("GET "));
client.print(apiurl);
client.print(str.buf);
client.println();
delay(1000);
}

else {
//Serial.println(F("Done"));
delay(500);
client.stop();
}
}
//wdt_reset(); //watchdog timer
delay(60000);
bc = 1;

} // LOOP end

//=========================================================================


void calculatePower1() { // calculate line 2 of 240v
for (int i = 0; i < numberOfSamples; i++) {
// Used for voltage offset removal
lastSampleV = sampleV;
lastSampleI = sampleI;

// Read voltage and current values
sampleV = analogRead(voltageSensor);
sampleI = analogRead(currentSensor);

// Used for voltage offset removal
lastFilteredV = filteredV;
lastFilteredI = filteredI;

// Digital high pass filters to remove 2.5V DC offset
filteredV = 0.996 * (lastFilteredV + sampleV - lastSampleV);
filteredI = 0.996 * (lastFilteredI + sampleI - lastSampleI);

// Phase calibration
calibratedV = lastFilteredV + PHASECAL * (filteredV - lastFilteredV);

// Root-mean-square voltage
sumV += calibratedV * calibratedV;

// Root-mean-square current
sumI += filteredI * filteredI;

// Instantaneous Power
sumP += abs(calibratedV * filteredI);
}

// Calculation of the root of the mean of the voltage and current squared (rms)
// Calibration coeficients applied
voltageRMS = V_RATIO * sqrt(sumV / numberOfSamples);
currentRMS = I_RATIO * sqrt(sumI / numberOfSamples);

//Serial.println(voltageRMS);
//Serial.println(currentRMS);

// Calculate power values
realPower = V_RATIO * I_RATIO * sumP / numberOfSamples;
apparentPower = voltageRMS * currentRMS;
powerFactor = realPower / apparentPower;

// Calculate running total kilowatt hours
// This value will reset in 50 days
last_kWhTime = kWhTime;
kWhTime = millis();

// Convert watts into kilowatts and multiply by the time since the last reading in ms
kilowattHour += (realPower / 1000) * ((kWhTime - last_kWhTime) / 3600000.0);

// Reset sample totals
sumV = 0;
sumI = 0;
sumP = 0;
} // end of CT#1 calculations

//============================================================

void calculatePower2() { // calculate line 2 of 240v
for (int i = 0; i < numberOfSamples; i++) {
// Used for voltage offset removal
lastSampleV = sampleV;
lastSampleI2 = sampleI2;

// Read voltage and current values
sampleV = analogRead(voltageSensor);
sampleI2 = analogRead(currentSensor2);

// Used for voltage offset removal
lastFilteredV = filteredV;
lastFilteredI2 = filteredI2;

// Digital high pass filters to remove 2.5V DC offset
filteredV = 0.996 * (lastFilteredV + sampleV - lastSampleV);
filteredI2 = 0.996 * (lastFilteredI2 + sampleI2 - lastSampleI2);

// Phase calibration
calibratedV = lastFilteredV + PHASECAL * (filteredV - lastFilteredV);

// Root-mean-square voltage
sumV += calibratedV * calibratedV;

// Root-mean-square current
sumI2 += filteredI2 * filteredI2;

// Instantaneous Power
sumP2 += abs(calibratedV * filteredI2);
}

// Calculation of the root of the mean of the voltage and current squared (rms)
// Calibration coeficients applied
voltageRMS = V_RATIO * sqrt(sumV / numberOfSamples);
currentRMS2 = I_RATIO2 * sqrt(sumI2 / numberOfSamples);

//Serial.println(voltageRMS);
//Serial.println(currentRMS);

// Calculate power values
realPower2 = V_RATIO * I_RATIO2 * sumP2 / numberOfSamples;
apparentPower2 = voltageRMS * currentRMS2;
powerFactor2 = realPower2 / apparentPower2;

// Calculate running total kilowatt hours
// This value will reset in 50 days
last_kWhTime = kWhTime;
kWhTime = millis();

// Convert watts into kilowatts and multiply by the time since the last reading in ms
kilowattHour2 += (realPower2 / 1000) * ((kWhTime - last_kWhTime) / 3600000.0);

// Reset sample totals
sumV = 0;
sumI2 = 0;
sumP2 = 0;
}

That's it for now...

Return to my home page