Home power Monitoring
About |
Current power usage |
Summary by hour |
Summary by weekday
Note: new section added to discuss the accuracy of this system.
This is a DIY project using current transducers, an arduino, and some
custom software
to monitor power consumption for an entire house.
The power measurements are graphed and made public on a webserver.
The end result is a slightly cheaper hack of the
The Energy Detective.
This data shared online is currently represented as both
current power consumption as well as
as summary charts.
- What this can do is visualize when appliances, lights, computers, etc are using home power.
- What this cannot do is detect smaller devices like cellphone chargers, dangling wallwarts or anything else that consumes less than 10Watts.
Please monitor
my rss feed if you want to know when this project is updated. If you have questions or comment feel free to
send me mail or leave
a message on the
hack-a-day comment thread.
Similar projects
There are a number of similar projects already out there for
home power monitoring.
If you have a project similar to this one not listed here
I would love to hear about it.
- Jason Winters' Pico-Projects - Similar project using CTs with
an IO bridge
- ladyada's tweet-a-watt
- Uses a Kill-a-Watt for monitoring power usage on one outlet
- Pluggy is a geek
- amazing "let's monitor everything possible" project.
This is very interesting because he compares power usage monitored by CTs vs
monitoring power at the meter.
Overview Diagram
Materials
Total cost ~ $110 (not counting what I already had lying around)
If money is tight there are a few things that can make this cheaper by about fifty bucks:
- Adding a serial port to a router running openwrt or using a dedicated computer connected to the arduino serial.
This will elimate the need for the ethernet shield which is a very expensive component
- Hosting the charts locally or on a server with a dedicated, static ip address will eliminate the need for the openwrt router as well as the ethernet shield (more details on that later)
- Web hosting is optional if you only want to view the data locally and you have a machine that's always on at home.
A cheap netbook would be sufficient for the task.
Here are some factors that will influence accuracy for all
current transducer monitoring systems like this one.
Power Factor
Power factor
is a number between 0 and 1 and is the ratio of real power to the
product of the rms current times the rms voltage.
Power factor differs from device to device and will need
to be approximated to caculate over all power usage.
A power factor of .60 will be used as an approximation for the
overall power calculation. In order to calculate power
factor the voltage would need to be monitored as well as the
current for each phase.
Voltage
Voltage can vary depending on what is connected to the home.
For calculating power the voltage will be approximated to
be 120V(rms).
Accuracy of the ADC
Measuring the voltage from the CT is done using an Arduino microcontroller.
With 3.3V as the analog reference the resolution of the ADC is .0032mV.
This voltage on the CT is 213mA of current which corresponds to an
apparent power resolution of ~ 25Watts. This means the apparent
power calculations on the arduino will only be as accurate as the
resolution.
RMS vs Peak Approximations
In the first version of this project
a very simple peak voltage calculation was being
used with a fudge factor to make a guess at
the scaling factor for overall power.
To improve this slightly the arduino is now making an RMS caluculation instead of using
the peak value.
Because the signal from the CT is not level shifted the arduino
only sees the signal when it is above zero volts.
Instead of applying a DC bias to the signal only half the samples
will be used for the RMS calculation.
Assuming the signal is symetrical on the X-axis the following code
calculates an approximate RMS value for a specified
number of evenly spaced samples.
float rms_val1 = 0;
float rms_val2 = 0;
for (int cnt=0; cnt<SAMPLES; cnt++) {
val1 = analogRead(S1_PIN);
val2 = analogRead(S2_PIN);
rms_val1 = rms_val1 + sq( (float)val1 );
rms_val2 = rms_val2 + sq( (float)val2 );
}
rms_val1 = sqrt(rms_val1 / (SAMPLES/2) );
rms_val2 = sqrt(rms_val2 / (SAMPLES/2) );
A 100W spotlight, a fan, a toaster oven and a computer were used
as devices to test the accuracy of the home power monitor.
For each device the CT was hooked up to an oscilliscope as well
as a DMM to measure the RMS voltage of the CT. During the testing
one of the power phases was shut completely off with the exception
of the device being tested. (the scope and arduino were on the other
phase, isolated from the device being tested)
The toaster oven also makes a good firestarter.
Kill-a-watt measuremnts
- Voltage = voltage measurement on the Kill-a-Watt
- Current = current measurement on the Kill-a-Watt
- Power = power measurement on the Kill-a-Watt
- Power factor = ratio of voltage x current to the measured power
Arduino measurements
- RMS measurement = The value recorded by the ADC from the CT
- Voltage = ADC value converted to voltage ( value * .0032 )
- CT Voltage Accuracy = The ratio of the measured voltage to the voltage measu
red on the CT with a DMM
- Calculated Current = (Voltage) * 200 / 3
- Current Accuracy = The ratio of the calculated current to the measurement ma
de by the Kill-a-Watt
| Device / Measurements |
Measurements on the Kill-a-Watt |
Measurements on the Arduino |
Spotlight
CT DMM measurement = .015Vrms
|
- Voltage = 124.8Vrms
- Current = .85Arms
- Power = 104W
- Power factor = .99
|
- RMS measurement = 3.38
- Voltage = .0108Vrms
- CT Voltage Accuracy = 72%
- Calculated Current = .7211Arms
- Current Accuracy = 85.8%
|
Fan
CT DMM measurement = .022Vrms
|
- Voltage = 124.5Vrms
- Current = 1.18Arms
- Power = 134W
- Power factor = .92
|
- RMS measurement = 5.12
- Voltage = .0164Vrms
- CT Voltage Accuracy = 74%
- Calculated Current = 1.092Arms
- Current Accuracy = 92.5%
|
Toaster Oven
CT DMM measurement = .166Vrms
|
- Voltage = 117.0Vrms
- Current = 10.77Arms
- Power = 1250W
- Power factor = .99
|
- RMS measurement = 55.88
- Voltage = .1788Vrms
- CT Voltage Accuracy = 107.8%
- Calculated Current = 11.92Arms
- Current Accuracy = 110.7%
|
Computer
CT DMM measurement = .032Vrms
|
- Voltage = 124.6Vrms
- Current = 2.02Arms
- Power = 160W
- Power factor = .64
|
- RMS measurement = 10.36
- Voltage = .0332Vrms
- CT Voltage Accuracy = 103.8%
- Calculated Current = 2.21Arms
- Current Accuracy = 109.4%
|
Hardware
The analog to digital converter on the Arduino has a 10bit resolution. This means it can measure 2^10 or 1024 distinct states.
A spec sheet wasn't available from the CT which makes it difficult to
calculate the power conversion. The output of the CT is 0-3V and since
the default range of the ADC is 0-5V the analog reference
was tied to 3.3V through a 5k
resistory (pictured below).
For a 0-3.3V input on the ADC / 1024 bits the arduino will measure 3.2mv for every bit.
Installation of the clamp-on current transducers
and mounting the arduino was the easiest part
of this project. Using plexiglass, screws and stand-offs
the arduino was mounted next to the
breaker box. The CTs were clamped on the mains and fed through the
side down to the arduino analog inputs.
The waveforms that come off the CTs are pictured on the right below.
(right click for higher resolution). Between the two phases there 180degree
difference as expected.
Software
Arduino Sketch
The arduino samples the the input from the ADC and posts it
to a cgi sitting on the linksys router using the ethernet shield.
The input is sinusoidal so the rms value is calculated for
10k samples. Once the sampling is complete the arduino takes
NUM_READINGS and averages them together which is posted to the cgi.
The rate at which the arduino sends data to the webserver tobe recorded in the
database is about 1 measurement every 20 seconds.
#include <Ethernet.h>
#define S1_PIN 0 // input pin for first inductive sensor
#define S2_PIN 5 // input pin for second inductive sensor
#define SAMPLES 10000 // number of samples to take
#define NUM_READINGS 10 // number of readings to average
int val1 = 0;
int val2 = 0;
int reading_cnt = 0; // counter for 1 second samples
float total_rms1 = 0;
float total_rms2 = 0;
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 0, 177 };
byte gateway[] = { 192, 168, 0, 1 };
byte subnet[] = { 255, 255, 255, 0 };
byte server[] = { 192, 168, 0, 1 }; // router.springfield
Client client(server, 80);
void setup() {
Ethernet.begin(mac, ip, gateway, subnet);
analogReference(EXTERNAL);
Serial.begin(9600);
delay(1000);
Serial.println("Connecting...");
}
void loop() {
int max_val1 = 0;
int max_val2 = 0;
float rms_val1 = 0;
float rms_val2 = 0;
for (int cnt=0; cnt < SAMPLES; cnt++) {
val1 = analogRead(S1_PIN);
val2 = analogRead(S2_PIN);
rms_val1 = rms_val1 + sq((float)val1);
rms_val2 = rms_val2 + sq((float)val2);
}
rms_val1 = sqrt(rms_val1 / (SAMPLES/2) );
rms_val2 = sqrt(rms_val2 / (SAMPLES/2) );
total_rms1 = total_rms1 + rms_val1;
total_rms2 = total_rms2 + rms_val2;
reading_cnt++;
if (reading_cnt >= NUM_READINGS) {
float average_rms1 = total_rms1 / NUM_READINGS;
float average_rms2 = total_rms2 / NUM_READINGS;
Serial.print("AVG VALUES *RMS* - ");
Serial.print(average_rms1);
Serial.print(" ");
Serial.println(average_rms2);
if (client.connect()) {
Serial.println("Sending data");
client.print("GET /cgi-bin/relay.pl?value1=");
client.print(average_rms1);
Serial.print(" ");
Serial.println(average_rms2);
if (client.connect()) {
Serial.println("Sending data");
client.print("GET /cgi-bin/relay.pl?value1=");
client.print(average_rms1);
client.print("&value2=");
client.print(average_rms2);
client.println(" HTTP/1.1");
client.println();
client.stop();
} else {
Serial.println("Failed to connect to client");
}
total_rms1 = 0;
total_rms2 = 0;
reading_cnt = 0;
}
}
Moving the data from the arduino to shared web hosting
Router on the local network
The arduino ethernet shield is not capable of DNS lookups which means it
needs to talk to something that has a static ip address. Most shared
web hosting consist of multiple servers that use the dns name to route
http requests. To work around this issue a linksys router
running the openwrt linux operating system was used to host a cgi
that relays the
data from the arduino to my web hosting.
use Socket;
use strict;
print "Content-Type: text/html; charset=ISO-8859-1\n\n";
# Read the reading from the GET request
my @qstring = split ('&', $ENV{'QUERY_STRING'});
die "wrong number of values" unless (scalar @qstring == 2);
# Relay the readings to power.jarv.org
socket(SH, PF_INET, SOCK_STREAM, getprotobyname('tcp')) or die $!;
my $dest = sockaddr_in (80, inet_aton('power.jarv.org'));
connect (SH, $dest) or die $!;
print SH "\nGET /post.pl?$qstring[0]&$qstring[1] HTTP/1.1\n" .
"Host: power.jarv.org\n" .
# BASE64 encoded username:password
"Authorization: Basic ************************\n\n";
close SH;
The base64 encoded username-password is for minimal security on
the shared web hosting server.
Storing the data in an sqlite db
For the purpose of posting the power readings on my shared web hosting
a subdomain was created called power.jarv.org. This is just a paranoid
step as it keeps it isolated from my main web directory.
This is the database schema. There is one table that stores
the epoch time and the two values recorded for each power phase.
CREATE TABLE data (
timestamp INTEGER UNIQUE,
value1 INTEGER,
value2 INTEGER
);
Very simple, one table and one row per entry.
If you can think of better ways to store this
data and report please
let me know,
I considered using rrdtool
but thought this offered the most flixibility.
!/usr/bin/perl -w
use strict;
use Data::Dumper;
use CGI qw/:standard/;
use DBI;
use constant DBNAME => [path to sqlite database]
print header;
my $ts = time();
my $v1 = param('value1') or die "Value1 not in parameter list";
my $v2 = param('value2') or die "Value2 not in parameter list";
my $dbargs = {AutoCommit =>1, PrintError =>1};
my $dbh = DBI->connect("dbi:SQLite:dbname=" . DBNAME, "", "",$dbargs)
or die "Unable to connect to the db";
$dbh->do(
"insert or ignore into data values ($ts, $v1, $v2)");
$dbh->disconnect;
This simple script will take the data which is encoded in the
http query string sent from the arduino ethernet shield and relay
it to another cgi sitting on power.jarv.org.
Generating the Flash Charts
Finally once the data is collected into the sqlite database the charts are
periodically generated. To do this a perl script is run in a cron job
that connects to the database and outputs json files for
open flash charts.
I have two types of charts that are created, one is a regular line chart and
the other is a summary chart that averages data over time and presents it
as a bar graph.
The perl script is set up as a cron job that runs on the shared web hosting
server every 10 minutes or so. Runtime statistics are presented at the
top of each page.
Perl script for generating flash charts