Home Energy Project

details

Home energy monitoring system

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.

Hardware and Sofrware is ready

 

Code

#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;
}

 

 

http://www.joecool.org/joe_home_energy_power_monitor_pr.htm

COMMENTS

Please Login to comment
  Subscribe  
Notify of