Energy Monitor Proof of Concept

igoogle-pachube

The Arduino is now connected to a photoreceiver that I got at Ax-Man for $0.95, picking up IR pulses every watt-hour of usage! 

arduino-power

Since Google Powermeter still has no open API, I am uploading data to pachube.com – but they have an iGoogle plugin so Google doesn’t have to feel too bad (Hi Mike!).  I’m sure they’ll carry on without me for now.  It’s working pretty well but it’s all duct tape, chewing gum and baling twine, and you may notice a flat spot on the screenshot; the photoreceiver doesn’t work when the sun shines on the meter during the day.  Not sure how to fix that even with various shading I put over it.  Still, kind of interesting.  I’m updating pachube every 20s.  They have a pretty cool API where you can generate all sorts of type of graphs, besides the ugly pink ones:

power_history

Russell ordered some current transformers to play with, which at least won’t be exposed to the elements; I may get a 2nd Arduino for that experimentation because I’d like this logging to keep going if I can.  So far there is strong-ish incentive to run around turning off lights to make the graph go down, but I suppose that will wear off…

Arduino code follows; it ain’t pretty but it works.  It listens on the serial port for a “p” (power) and sends the avg power over all the pulses since the last query.  Right now my mac is running crummy perl to poke the arduino board and upload the data to pachube with curl.

As I said, chewing gum and duct tape…

//
// Use interrupt to detect change on IR phototransistor
//
// So we know what's up, light up the built-in LED
//

// PIN DEFINITIONS
int led_pin = 12;              // Display LED pin
int int0_pin = 2;              // Interrupt 0 on pin 2, input

// Delays
int bounce_delay_ms = 100;     // pulse is 10ms wide
int blink_delay_ms = 100;      // LED on time; delays loop()

unsigned long counter = 0;      // Nr of pulses seen
unsigned long first_pulse_time = 0;
unsigned long last_pulse_time = 0;

// These are modified in the interrupt
volatile int pulse_seen = 0;   // Pulse was seen in interrupt

static unsigned long ms_per_hour = 3600000UL;  // ms per hour for power calcs

void setup()
{
 pinMode(led_pin, OUTPUT);

 // We want internal pullup enabled:
 pinMode(int0_pin, INPUT);
 // digitalWrite(int0_pin, HIGH);  // Turns on pullup resistors

 // And set up the interrupt.
 // RISING: -pin- goes from low to high
 // When IR detected, Axman detector emits 5V
 attachInterrupt(0, blink, RISING);
 // initialize serial communication:
 Serial.begin(9600);
}

void loop()
{
 static unsigned long avg_watts;        // average watt draw since startup
 unsigned long delta;            // Time since last interrupt
 int incoming_byte;

 // Query exists on serial port; service it.
 if (Serial.available() > 0) {
   incoming_byte = Serial.read();
   Serial.flush();
   switch (incoming_byte) {
   case 'c':
     // Just print the count since last query
     Serial.println(counter);
     counter = 0;
     break;
   case 'p':
     // print the avg watt draw since last query
     delta = last_pulse_time - first_pulse_time;
     if (delta && counter) {
       avg_watts = (counter - 1) * ms_per_hour / delta;
       counter = 0;
     }
     Serial.println(avg_watts);
     break;
   default:
     break;
   }
 }

 // IR pulse interrupt happened
 if (pulse_seen == 1) {
   counter++;

   last_pulse_time = millis();

   if (counter == 1 || first_pulse_time == 0) // we were reset
     first_pulse_time = last_pulse_time;

   // Blinky-blink
   digitalWrite(led_pin, HIGH);
   delay(blink_delay_ms);
   digitalWrite(led_pin, LOW);
   // Reset until next one.
   pulse_seen = 0;
 }
}

// Interrupt handler
// Whenever we get an IR state change our LED should change too.
//
// We get here for RISING; so when IR turns on.
//
void blink()
{
 static unsigned long last_interrupt_time = 0;
 unsigned long interrupt_time = millis();

 // If interrupts come faster than bounce_delay_ms, assume it's a bounce and ignore
 //
 // Note, millis() wraps every 50 days, be sure we cope...
 //
 if (interrupt_time - last_interrupt_time > bounce_delay_ms)
   pulse_seen = 1;  // loop() sees pulse == 1 and takes action

 last_interrupt_time = interrupt_time;
}

Leave a Reply