YAEULS - Yet Another Electricity Usage Logging System

After a few relatively high electricity bills I wanted to get a bit of a grip on my electron usage.

My electricity meter has a LED that blinks every Watt.Hour.  At a use of 3600W it will give one blink/second.
The highest possible usage is about 8000W  based on a main-fuse of 35A@230Vac.

I'm using arduino as a solution, maybe in the future I will switch to a Raspberry-PI.

I used the following parts :
-  Arduino Ethernet
-  Photodiode sensor PCB. (dealextreme)

With the above setup I can Log measurements to a memory card, or send the data to a PHP/MySQL system using ethernet.


 Ethernet Shield with Wiznet W5100 Ethernet Chip / TF Slot  
1-Channel Photo Diode Sensor Module for Arduino - Blue
  
led setup on energy meter


Relevant Arduino Libraries :   Ethernet                          http://arduino.cc/en/Reference/Ethernet
For timing we can start with using :    millis()                 http://arduino.cc/en/Reference/Millis
Relevant Arduino Functions:
We will use interupts for reading the pulses from the photodiode :
attachInterrupt()      http://arduino.cc/en/Reference/AttachInterrupt
interrupts()             http://arduino.cc/en/Reference/Interrupts


Internet of things logging provider Cosm:
To store the measurement results it seems to be quickest to use one of the many 'internet of things' logging servers.
I started with Cosm (pachube) for which you can see the results below.



After a few days you can clearly see your fridges, coffeemachine, cooking, TV, computers, etc
But I must say that the functionality quickly becomes limited at Cosm if you want to do something more with your data.

Internet of things logging provider Cosm:
I'm checking out other providers like sen.se.
They seem to have way more features but limitations become clear after a while.
SenseBoard at Sen.se



Arduino Sketch for use with COSM : (discontinued)
Rev 1000
I've been the code below now for a few months,  https://cosm.com/feeds/83680
It has some jitter on the measurements.
Rev 1001  - 19Jan'13
Fixed jitter in the measurement results, was due to sometimes getting an interupt on the low->high flank of the signal.
Added a simple check in the interupt routine which seems to solve the problem.
The code has been tested with a pulsegenerator.
Rev1002  20Feb13  : Added second datastream , keeping track of cumulative kwh
                               Stream is called : kwh_stream,      renamed YAEULS to: watt_stream
Rev1003  21Feb13  : Content-Length +=2 chars for newln in wattdata stream

//-------------------------------------------------------------------------------------
//- Project           : YAEULS - Yet Another Electricity Usage Logging System
//- Revision          : 1003
//- Author(s)         : Stormywebber , Frostbyte
//- Requirements      : ArduinoUNO + EthernetShield (W5100), Photodiode
//-                   : Arduino Ethernet
//-                   : Arduino IDE 1.0.1 or higher, 
//- Description       : Counts the pulses given by an domestic electricity usage kWH meter
//-                     One pulse equals one Watt.hour
//-                     assosiated website : https://sites.google.com/site/yaeuls/
//-                     Data will be logged to Cosm, use ver2.0 of cosm.com API
//-                     Circuit:
//-                     * Pulse sensor to pin 2 , interupt 0
//-                     * Ethernet shield attached to pins 10, 11, 12, 13
//- Todo              : can be found at the bottom of this file 
//-                     
//- Rev1000    Dec13  : Initial setup, ran for a few months on Cosm
//-                   : has jitter on measurement results
//- Rev1001  19Jan13  : Removed jitter problem, timer are captures in interrupt routine
//-                   : Also sometimes have interupt on rising flank !, 
//-                   : switches from cosm datastream "sensor1," to  "YAEULS,"
//- Rev1002  20Feb13  : Added second datastream , keeping track of cumulative kwh
//-                     Stream is called : kwh_stream
//-                     renamed YAEULS to: watt_stream
//- Rev1003  21Feb13  : Content-Length +=2 chars for newln in wattdata stream
//-------------------------------------------------------------------------------------

#include <SPI.h>
#include <Ethernet.h>

#define APIKEY         "pa8u2___Dummy_Key__xy2GNS1Y9SKtHhHakOT" // replace your Cosm api key here
#define FEEDID         83680                     // replace your feed ID
#define USERAGENT      "YAEULS"                  // user agent is the project name

//-------------------------------------= Ethernet related
byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x69, 0x82};
IPAddress ip(192,168,1,177);                      // available IP address on network, for manual configuration:
EthernetClient client;                            // initialize the library instance:
IPAddress server(216,52,233,121);                 // numeric IP for api.cosm.com
//char server[] = "api.cosm.com";                 // name address for cosm API

//--------------------------------------= Constants
const long         BAUDRATE = 115200;
const byte         LEDPIN = 2;
const unsigned int INTERVALSEC = 60;

//--------------------------------------= Variables Global
//--------------------------------------= Data Structures
struct interval_type {
  long Time;
  unsigned int Cnt;
  boolean Event;
};
interval_type interval;
//--------------------------------------= 
struct LED_type {
  unsigned int Cnt;
  unsigned long EventMillis;
  unsigned long Watthour;  
  boolean      Event;
  boolean      Overflow;
  unsigned int Watt;
};
LED_type LedPulse;
//--------------------------------------= 
struct RESULTS_type {
 unsigned int WattAvg;
 unsigned int kWattHour;
 unsigned int intervalcnt;
 unsigned int LedPulseCnt;
};
RESULTS_type Results;
//--------------------------------------= 
struct FLAGS_type {
  boolean SendResults;
  boolean error;
};
FLAGS_type Flag;

//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
//- Setup, configure all hardware and software
//- put your setup code here, to run once                                            -
//-------------------------------------------------------------------------------------
void setup() 
{  Serial.begin(BAUDRATE);                                   // open serial port,
   Serial.println("YAEULS rev1003 started, ");
   interval.Event = false;
   interval.Time = (1000 * INTERVALSEC);                     //( msec x sec)
   interval.Cnt = 0;

   pinMode(LEDPIN, INPUT);
   attachInterrupt(0 ,Int_LedPulse, FALLING ); // call Int_LedPulse() on falling edge on pin 
   LedPulse.Cnt = 0;
   
   LedPulse.Watthour = 0;
   Results.kWattHour = 0;
   
   Serial.print("Interval time is "); Serial.print(INTERVALSEC); Serial.println("seconds.");
   Flag.SendResults = false;

  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // DHCP failed, so use a fixed IP address:
    Ethernet.begin(mac, ip);
  }
  delay(500);
}//setup()

//-------------------------------------------------------------------------------------
//- Interupt routine - count number of pulses, initialized in setup()
//-                                                                                   -
//-------------------------------------------------------------------------------------
void Int_LedPulse()
{ if ( digitalRead(LEDPIN) == LOW )            // to flush false triggers
  { LedPulse.EventMillis = millis();           // capture timer
    LedPulse.Event=true;                       // set flag to signal PulseSensor_manager()
    LedPulse.Watthour++;
    if (LedPulse.Cnt++ == 0)
      LedPulse.Overflow = true;
  }
}//Int_LedPulse

//-------------------------------------------------------------------------------------
//- Main Loop, calls the different tasks
//- interupt routine Int_LedPulse will always be active
//- tasks should not hogg time but split there work up in short time segments
//-------------------------------------------------------------------------------------
void loop() {

  PulseSensor_Manager();
  Ethernet_StateMachine();

}//loop

//-------------------------------------------------------------------------------------
//- PulseSensor_Manager 
//- Counts number of pulses in interval (hours/minutes)
//- Calculate powerUsage (Watt) between two pulses
//-------------------------------------------------------------------------------------
void PulseSensor_Manager()
{  
  static unsigned long previousMillis = 0;
  static unsigned long previousLogging = 0;
  static unsigned long nextMillis = interval.Time;
 
  if( millis() > nextMillis )
  {  nextMillis += interval.Time;                     // calc new logging time
     interval.Event = true;                           // cleared in PulseSensor_Manager
  }

  if ( LedPulse.Event )                                     // Pulse detected,set by Interrupt routine
  {  unsigned long currentMillis = LedPulse.EventMillis;
     unsigned long deltamillis = (currentMillis - previousMillis);        // calc time between pulses
     previousMillis = currentMillis;
//     LedPulse.Watt = ( (3600000UL) / deltamillis);                        // calc current Power (Watt)
//     Serial.print("T="); Serial.println(deltamillis);
     if ( interval.Event )                                                 // New logging event ?
     {  Results.LedPulseCnt = LedPulse.Cnt;                                //
        LedPulse.Cnt=0;
        Results.kWattHour = (LedPulse.Watthour / 1000) ;                   // calc kWh
//      Serial.print("Kwh=");Serial.print(Results.kWattHour);
        deltamillis = (currentMillis - previousLogging);                   // calc interval time
//      Serial.print("d="); Serial.print(deltamillis);
        previousLogging = currentMillis;                                   //
        nextMillis = currentMillis + interval.Time;                        // set net event time
//      Serial.print("ms LedPulseCnt="); Serial.print(Results.LedPulseCnt);
        Results.WattAvg = (3600000UL * (unsigned long)(Results.LedPulseCnt) ) / deltamillis;
//      Serial.print(" Avg="); Serial.println(Results.WattAvg);
        interval.Event = false;

        if (Flag.SendResults == false)                                     // if not busy with Cosm frame
          Flag.SendResults = true;                                         // then ask for Cosm frame to be send
      }

     LedPulse.Event = false;
  }
}//PulseSensor_StateMachine()

//-------------------------------------------------------------------------------------
//- Ethernet_StateMachine, 
//- 
//-------------------------------------------------------------------------------------
void Ethernet_StateMachine()
{
  static boolean lastConnected = false;         // state of the connection last time through the main loop

  if (client.available())                       // if incoming data from the net connection.
  { char c = client.read();                     // send it to serial port.
    Serial.print(c);                            // For debugging purposes
  }

  if (!client.connected() && lastConnected)     // if there's no net connection, 
  { Serial.println();                           // but there was one last time through the loop,
    Serial.println("disconnecting.");           //  then stop the client:
    client.stop();                              
  }

  if(!client.connected() && Flag.SendResults )  // if not connected, and INTERVAL has passed since
  { sendData(Results.WattAvg, Results.kWattHour);// your last connection, then connect and send data:
    Flag.SendResults=false;                     // Signal that Cosm frame has been send
  }
  
  // store the state of the connection for next time through the loop:
  lastConnected = client.connected();

}//Ethernet_StateMachine

//-------------------------------------------------------------------------------------
// Makes a HTTP connection to the Cosm server
//
//-------------------------------------------------------------------------------------
void sendData(int watt_data, int kwh_data) 
{
  int thisLength = 12 + getLength(watt_data) + 11 + getLength(kwh_data) + 2;

  if (client.connect(server, 80))   // if there's a successful connection:
  { // send the HTTP PUT request:
    client.print("PUT /v2/feeds/");
    client.print(FEEDID);
    client.println(".csv HTTP/1.1");
    client.println("Host: api.cosm.com");
    client.print("X-ApiKey: ");    client.println(APIKEY);
    client.print("User-Agent: ");  client.println(USERAGENT);
    client.print("Content-Length: ");

    // calculate the length of the sensor reading in bytes:
    // 8 bytes for "sensor1," + number of digits of the data:
    client.println(thisLength);

    // last pieces of the HTTP PUT request:
    client.println("Content-Type: text/csv");
    client.println("Connection: close");
    client.println();

    // The actual Sensor Data content of the PUT request:
    client.print("watt_stream,");   client.println(watt_data);
    client.print("kwh_stream,");    client.println(kwh_data);
    
  } 
  else     // if you couldn't make a connection:
  { Serial.println("connection failed, disconnecting.");
    client.stop();
  }
}//sendData

//-------------------------------------------------------------------------------------
// This method calculates the number of digits in the sensor reading.  
// Since each digit of the ASCII decimal representation is a byte, 
// the number of digits equals the number of bytes:
int getLength(int someValue) 
{ int digits = 1;                 // there's at least one byte:
  int dividend = someValue /10;   // continually divide the value by ten,adding one to the
  while (dividend > 0)            // digit count for each time you divide, until you're at 0:
  { dividend = dividend /10;
    digits++;
  }
  return digits;
}//getLength

//-------------------------------------------------------------------------------------
//end



Arduino Sketch for use with Sen.se :
Rev 1001  Todo List
  - It would be better not to send the kwh stream every minute, since graphs on sen.se are limited, change code only to upload when new kwh point available.
Rev 1000  First version
http://open.sen.se/sensemeters/tab/3868/


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#include <SensuinoEth.h>
#include <SensuinoSerial.h>
#include <Ethernet.h>
#include <SPI.h>
//-------------------------------------------------------------------------------------
//- Project           : YAEULS - Yet Another Electricity Usage Logging System
//- Revision          : 1000
//- Author(s)         : Stormywebber , Frostbyte
//- Requirements      : ArduinoUNO + EthernetShield (W5100), Photodiode
//-                   : Arduino Ethernet
//-                   : Arduino IDE 1.0.1 or higher, 
//- Description       : Counts the pulses given by an domestic electricity usage kWH meter
//-                     One pulse equals one Watt.hour
//-                     assosiated website : https://sites.google.com/site/yaeuls/
//-                     Data will be logged to Sen.se, using Sensuino library
//-                     Circuit:
//-                     * Pulse sensor to pin 2 , interupt 0
//-                     * Ethernet shield attached to pins 10, 11, 12, 13
//- Todo              : can be found at the bottom of this file 
//-                     
//- Rev1000  09Apr13  : Initial setup, 
//-                   : 
//-------------------------------------------------------------------------------------

SensuinoEth pf;

//--------------------------------------= Constants
const long         BAUDRATE = 115200;
const byte         LEDPIN = 2;
const unsigned int INTERVALSEC = 60;

const long int          device_id  = 11076; // your device's ID
const unsigned long int feed_29788 = 29788; // the feed "watt_stream"
const unsigned long int feed_29790 = 29790; // the feed "kwh_stream"

//--------------------------------------= Variables Global
//--------------------------------------= Data Structures
struct interval_type {
  long Time;
  unsigned int Cnt;
  boolean Event;
};
interval_type interval;
//--------------------------------------= 
struct LED_type {
  unsigned int  Cnt;
  unsigned long EventMillis;
  unsigned long Watthour;  
  boolean       Event;
  boolean       Overflow;
  unsigned int  Watt;
};
LED_type LedPulse;
//--------------------------------------= 
struct RESULTS_type {
 unsigned int WattAvg;
 unsigned int kWattHour;
 unsigned int intervalcnt;
 unsigned int LedPulseCnt;
};
RESULTS_type Results;
//--------------------------------------= 
struct FLAGS_type {
  boolean SendResults;
  boolean error;
};
FLAGS_type Flag;

char buffer[30];
//----------------------------------------------------------------------------
void setup() {
  Serial.begin(BAUDRATE);
  Serial.println("YAEULS Sen.se rev1001 started, ");

  pinMode(LEDPIN, INPUT);                     // We set the PINs 
  attachInterrupt(0 ,Int_LedPulse, FALLING ); // call Int_LedPulse() on falling edge on pin 
  LedPulse.Cnt = 0;

  LedPulse.Watthour = 0;
  Results.kWattHour = 0;

  interval.Time = (1000 * INTERVALSEC);                     //( msec x sec)
  interval.Event = false;
  interval.Cnt = 0;

  Flag.SendResults = false;
  Serial.print("Interval time is "); Serial.print(INTERVALSEC); Serial.println("seconds.");

  byte err;        // Use the following if you don't want to use DHCP
  // pf.init(&err, 0, "aa:bb:cc:dd:ee:ff", "192.168.1.150", "192.168.1.1");// First IP is for the Arduino, second IP is for the gateway
  pf.init(&err, 1, "aa:bb:cc:dd:ee:ff");
  delay(1000);
  pf.setSenseKey("hsikdhwu__DummyKey____shshsg");   // This is your sense key
  pf.longPollSense(device_id);  //We listen for incoming events on the device
  
}//setup

//-------------------------------------------------------------------------------------
//- Interupt routine - count number of pulses, initialized in setup()
//-                                                                                   -
//-------------------------------------------------------------------------------------
void Int_LedPulse()
{ if ( digitalRead(LEDPIN) == LOW )            // to flush false triggers
  { LedPulse.EventMillis = millis();           // capture timer
    LedPulse.Event = true;                     // set flag to signal PulseSensor_manager()
    LedPulse.Watthour++;
    if (LedPulse.Cnt++ == 0)
      LedPulse.Overflow = true;
  }
}//Int_LedPulse

//-------------------------------------------------------------------------------------
//- PulseSensor_Manager 
//- Counts number of pulses in interval (hours/minutes)
//- Calculate powerUsage (Watt) between two pulses
//-------------------------------------------------------------------------------------
void PulseSensor_Manager()
{  
  static unsigned long previousMillis = 0;
  static unsigned long previousLogging = 0;
  static unsigned long nextMillis = interval.Time;
 
  if( millis() > nextMillis )
  {  nextMillis += interval.Time;                     // calc new logging time
     interval.Event = true;                           // cleared in PulseSensor_Manager
  }

  if ( LedPulse.Event )                                     // Pulse detected,set by Interrupt routine
  {  unsigned long currentMillis = LedPulse.EventMillis;
     unsigned long deltamillis = (currentMillis - previousMillis);        // calc time between pulses
     previousMillis = currentMillis;
//     LedPulse.Watt = ( (3600000UL) / deltamillis);                        // calc current Power (Watt)
//     Serial.print("T="); Serial.println(deltamillis);
     if ( interval.Event )                                                 // New logging event ?
     {  Results.LedPulseCnt = LedPulse.Cnt;                                //
        LedPulse.Cnt=0;
        Results.kWattHour = (LedPulse.Watthour / 1000) ;                   // calc kWh
//      Serial.print("Kwh=");Serial.print(Results.kWattHour);
        deltamillis = (currentMillis - previousLogging);                   // calc interval time
//      Serial.print("d="); Serial.print(deltamillis);
        previousLogging = currentMillis;                                   //
        nextMillis = currentMillis + interval.Time;                        // set net event time
//      Serial.print("ms LedPulseCnt="); Serial.print(Results.LedPulseCnt);
        Results.WattAvg = (3600000UL * (unsigned long)(Results.LedPulseCnt) ) / deltamillis;
//      Serial.print(" Avg="); Serial.println(Results.WattAvg);
        interval.Event = false;

        if (Flag.SendResults == false)                                     // if not busy with Sen.se frame
          Flag.SendResults = true;                                         // then ask for Sen.se frame to be send
     }
     LedPulse.Event = false;
  }
}//PulseSensor_StateMachine()

//-------------------------------------------------------------------------------------
//- Ethernet_StateMachine, 
//- Upload data to open.sen.se
//-------------------------------------------------------------------------------------
void Ethernet_StateMachine()
{ byte _check = pf.longPollCheck();
  if(_check == 0) 
  { pf.longPollSense(device_id);
    unsigned long int feed_id = pf.getLastFeedId();
    char *str = pf.getLastValue();
  } 
  else if(_check == 2) 
  { pf.longPollSense(device_id);
  }
  //----------------------------------- Send values for feeds
  if (Flag.SendResults)
  { Serial.println("POST value for feeds");

    Serial.println("Posting in feed watt_stream");
    sprintf(buffer,"%d",Results.WattAvg);
    if(pf.postSense(feed_29788, buffer)) 
    { Serial.println("POST in feed watt_stream succeeded");
    } else 
    { Serial.println("POST in feed watt_stream failed");
    }
    
    Serial.println("Posting in feed kwh_stream");
    sprintf(buffer,"%d",Results.kWattHour);
    if(pf.postSense(feed_29790, buffer)) 
    { Serial.println("POST in feed kwh_stream succeeded");
    } else 
    { Serial.println("POST in feed kwh_stream failed");
    }
    Flag.SendResults=false;                    // Signal that sen.se frame has been send
  }
  delay(200);
}//Ethernet_StateMachine()
//-------------------------------------------------------------------------------------
void loop() 
{ PulseSensor_Manager(); 
  Ethernet_StateMachine();
}//loop
//----------------------------------------------------------------------------
// ToDo :
// 




... Updated on 12Apr13,   added sen.se   .... 

Keywords: arduino power electricity usage photodiode logging Cosm
https://sites.google.com/site/arduinodueatmelstudio/home
https://sites.google.com/site/arduinofocus/home
https://sites.google.com/site/taiglathe/
https://sites.google.com/site/openlogicsniffer/home/arduino_spi_frame