How I Monitor My Smart Meter

Or maybe I should begin with “Why Do I Monitor My Smart Meter”. I trust it, no reason not to. My local utility (SDG&E) even posts utilization graphs overnight so when I wake up, yesterdays usage, broken down by the hour, is all ready for my review. It’s a “free” feature we get for paying the criminal rates here in San Diego. So there’s no problem except I want more. It’s how I’m wired. Plus I get to add more code to my Arduino. This is a relatively easy project, consisting of just a few parts costing under $10 if you do it on the cheap. The goal is to capture your electricity usage, but how?

The Itron OpenWay Smart Meter

The Itron OpenWay meter has a little secret, actually a few, but we only cover one here.  Many smart meters have access methods.  On the top are two round holes in the meter housing (at 12 o’clock if looking at the face).  One is dark, the other is lighter.  The lighter hole is actually an infrared emitter (LED).  Some have a visible light pipe like this one.  You can’t see it with the naked eye, but it flashes for about 10ms every time one-watt-hour (Wh) is consumed.  So some math calculations and you can get your kilo-Watt-hours (kWh) used, which is the metric your utility uses to bill you.  That’s where the IR phototransistor comes in.  We’ll use the IR pulse from the smart meter to saturate the phototransistors gate, allowing the Arduino to receive voltage on a digital pin.

But How’s That Going To Work?

The Arduino I have is a 5V model, the Mega2560 R3 specifically.  Some are 3.3V and those will work as well.  A phototransistor is just a control gate actuated by the presence of light, in this case, infrared light.  By sending +5Vdc out to the phototransistor the collector pin is charged.  The emitter pin is low, and this pin is the return to the Arduino digital pin.  But it’s only low until the meter pulses, then for that brief moment the base pin (mapped as the photocell, not a physical pin) is saturated and allows the 5Vdc to flow through the transistor gate and return back to the Arduino.  Code in the Arduino watches for this voltage and keeps track the pulses, how many and how often and when.  More on this later. You need a two-wire cable run between the Arduino and meter.  Perhaps you could leverage some existing home phone wiring (who has one of those these days?).  Like previously mentioned, +5Vdc runs out one wire, and ultimately returns back on the other wire.  There’s a bit more to the circuit, so if you breadboard it here’s what you are looking for along with some detail on the phototransistor circuit:

What You’ll Need


  • Arduino
  • Small Perf Board(s)*
  • NPN IR Phototransistor
  • 18K Resistor
  • 100nF Ceramic Capacitor
  • Male header strip* (straight or 90°)
  • Female breadboard jumper wire*
  • Connect wire
  • Soldering iron
  • Solder
  • Shielding Material**
  • Project Box
  • 1″ or less fuel line or similar*
  • 1/4″ or 3/16″ Drill Bit
  • 3/8″ Drill Motor
  • Velcro or similar**

* = Optional ** = Depends on meter location

OK Let’s Do This

Shielding Stray IR Light

Assuming you have the wiring roughed in stop and look up?  Do you see sunshine?  If you stood at the meter all day could you make direct eye contact with the sun?  If so, you have more prep work to do.  That’s because the Sun is the largest gross generator of infrared light there is.  That’ll spoil this project quickly unless you tame it.  Stray IR light will trigger false readings so, figure out a way to shield the meter, at least the top.  You don’t need darkness.  I placed foil duct tape on top of mine and made it look stealthy so as not to get a knock at the door.  This is a retail product photo but you can get an idea of the shielding you’re aiming for.  Looking head on at the meter I taped it from about 10:00 to 2:00, and did not roll over the front at all.  Don’t forget to put a hole in the tape or shielding material so you can still see the IR pulse.

Constructing The Project Box

I bought this aluminum box from Fry’s for $7.  My meter is in direct sunlight until about 1:00pm so plastic was not a viable option.  Take the meter and measure from the security/mounting pinch ring to the IR hole in the meters top.  Mine was 3/4″.  Drill a 1/4″ or 3/16″ hole at this point (IN THE BOX NOT THE METER!).  Orient the box and figure out the best way to route the signal wire.  Drill another hole to accommodate the diameter of your wire.  I chose to hot glue some fuel line inside the box at the IR meter hole to raise the IR pHototransistor off the meter housing and create a dark space.  You can probably omit this step, YMMV.  If you are using a small perf-board, figure out where to solder the components, your project box will dictate this part so you’re on your own.  Secure the box to the meter.  I used Velcro so if Johnny Gigawatt knocks and says take it down it’s no drama to do.  (I won’t roll over that easy, but you get the point.)

Protoboard Layout

Protoboard Layout

Wiring The Circuit

So far you should have a fairly shielded meter top and a closed up project box.  Time to wire it up to the Arduino.  How you wire it up depends if you are still breadboarding or are ready to solder to a protoboard.  I recommend breadboarding first.  It’s a waste to explain the wiring details when a picture speaks a thousand words; reference the two images above.  In short, just take the conductor to the phototransistor collector pin and common connect it to a +5V pin and one side of the capacitor (which is NOT polarity sensitive).  The other side of the capacitor goes to ground.  Take the other wire from the phototransistor  emitter pin and connect it common to the Arduino digital pin of choice and one side of the resistor.  The other side of the resistor goes to ground.  I like flashing lights so I also added an orange LED that flashes every time a meter pulse is registered.  To the right is mine wired up in the protoboard. That’s the final product, but you can get a better wiring schematic from this image below. It should really clear this up, and includes the confirmation LED wiring.

Functional Diagram

Functional Diagram

Write Some Code Already

I strongly recommend using an interrupt to catch the pulse (see attachInterrupt and the excellent article Nick wrote).  You could (and I tried) to code for a change in voltage by setting a value low and waiting for it to rise (when the meter pulses).  Then I set the value high and waited for it to go low.  This works but seems somewhat “analog” to do this way, besides, interrupts are (in my experience) more reliable.  In this case I chose to attach an interrupt (zero) to watch for a RISE, when the pulse opens the phototransistor gate and lets the volts through.  Since the pulse seems to have about a 10ms duration and according to the mysterious, hidden datasheet from Radio Shack on this particular phototransistor the RISE detection is only 5uSeconds, the interrupt has plenty of time to detect the pulses.  In the code you can see detailed commenting throughout.  What you do with the kWh var is up to you, but as a hint, if you know how many pulses are registered per minute, you can multiply that by 60 and get the “as accurate as 60 seconds ago” kWh draw.  See the image below to see what I’m doing with this project.

 

 

 

 

My Arduino Code (click to see, pirate it as needed)

//
//
//
#include <CountingStream.h>
#include <Xively.h>
#include <XivelyClient.h>
#include <XivelyDatastream.h>
#include <XivelyFeed.h>
#include <b64.h>
#include <HttpClient.h>
#include <SPI.h>
#include <Ethernet.h>
#include <Dns.h>
#include <SD.h>
#include <OneWire.h>
#include <stdlib.h>
#include <Time.h>
#include <EthernetUdp.h>
// size of buffer used to capture HTTP requests
#define REQ_BUF_SZ   60
#define DS18S20_ID 0x10 //DS18S20 sensor ID
#define DS18B20_ID 0x28 //DS18B20 sensor ID
int daylight = 0;  // variable to store the value coming from the photocell
float amps = 0; //set amps to 0
// Your Xively key and Feed ID to let you upload data 
char xivelyKey[] = "<your Xively key goes here>";
#define xivelyFeed <your Xively feed ID goes here>
//datastreams
char owSensor0[] = "Downstairs";
char owSensor1[] = "Master_Bedroom";
char owSensor2[] = "Front_Room";
char owSensor3[] = "Garage";
char owSensor4[] = "Back_Porch";
char owSensor5[] = "Office";
char owSensor7[] = "Black_Refrigerator";
char photoCell1[] = "Daylight";
char irDetectorPin2[] = "Power_Usage";
char amperes[] = "Amperes";
char kiloWatts_Hourly[] = "kilo-Watts_Hourly";
char kiloWatts_Daily[] = "kilo-Watts_Daily" ;
char kiloWatts_Monthly[] = "kilo-Watts_Monthly";
/*
char owSensor6[] = "Unassigned 1";
char owSensor8[] = "Unassigned 3";
*/
// Define the strings for our datastream IDs
XivelyDatastream datastreams[] = {
XivelyDatastream(owSensor0, strlen(owSensor0), DATASTREAM_FLOAT),
XivelyDatastream(owSensor1, strlen(owSensor1), DATASTREAM_FLOAT),
XivelyDatastream(owSensor2, strlen(owSensor2), DATASTREAM_FLOAT),
XivelyDatastream(owSensor3, strlen(owSensor3), DATASTREAM_FLOAT),
XivelyDatastream(owSensor4, strlen(owSensor4), DATASTREAM_FLOAT),
XivelyDatastream(owSensor5, strlen(owSensor5), DATASTREAM_FLOAT),
XivelyDatastream(irDetectorPin2, strlen(irDetectorPin2), DATASTREAM_FLOAT),
XivelyDatastream(owSensor7, strlen(owSensor7), DATASTREAM_FLOAT),
XivelyDatastream(photoCell1, strlen(photoCell1), DATASTREAM_FLOAT),
XivelyDatastream(amperes, strlen(amperes), DATASTREAM_FLOAT),
XivelyDatastream(kiloWatts_Hourly, strlen(kiloWatts_Hourly), DATASTREAM_FLOAT),
XivelyDatastream(kiloWatts_Daily, strlen(kiloWatts_Daily), DATASTREAM_FLOAT),
XivelyDatastream(kiloWatts_Monthly, strlen(kiloWatts_Monthly), DATASTREAM_FLOAT)
};
// Finally, wrap the datastreams into a feed
XivelyFeed feed(xivelyFeed, datastreams, 13 /* number of datastreams */);
// MAC address from Ethernet shield sticker under board
byte mac[] = { 0x00, 0x10, 0x36, 0x00, 0x25, 0x60 };
char phpServer[] = {"192.168.XXX.YYY"}; //RasPi WWW stuff
EthernetServer server(ah, ah, ahhhh); // create a server at port xxxx (or maybe not?)
File webFile;                         // the web page file on the SD card
File kwFile;                          // the daily and monthly HTM files on the SD card
char HTTP_req[REQ_BUF_SZ] = {0};      // buffered HTTP request stored as null terminated string
char req_index = 0;                   // index into HTTP_req buffer
boolean LED_state[4] = {0};           // stores the states of the LEDs FOR OLD SCHOOL WEB RT INFERFACE @ CURRENT.HTM
//Onewire vars
OneWire  ds(22);                      //set up the OneWire call
byte owSensorId;
float ow0;
float ow1;
float ow2;
float ow3;
float ow4;
float ow5;
float ow6;
float ow7;
float ow8;
char charBuff[10];
String owA = "";
String owB = "";
String owC = "";
String owD = "";
String owE = "";
String owF = "";
String owG = "";
String owH = "";
String owI = "";
boolean leapYear = false;
byte i;
int j;
byte present = 0;
byte type_s;
byte data[12];
byte addr[8];
float celsius, fahrenheit;
float temp;
unsigned long previousOwLoop = 0;
long unlockTime = 1800000;
byte dataReadyToPoll = 0;
unsigned long lockedAt22a = 0;
unsigned long lockedAt22b = 0;
unsigned long lockedAt22c = 0;
unsigned long lockedAt22d = 0;
unsigned long lockedAt22e = 0;
unsigned long lockedAt22f = 0;
unsigned long lockedAt22g = 0;
unsigned long lockedAt22h = 0;
unsigned long lockedAt22i = 0;
boolean locked22a = false;
boolean locked22b = false;
boolean locked22c = false;
boolean locked22d = false;
boolean locked22e = false;
boolean locked22f = false;
boolean locked22g = false;
boolean locked22h = false;
boolean locked22i = false;
float currentTemp;
String tempLocName;
//Serial 1 stuff
int ser1DataVal = 0;
int ser1DataIncoming = 0;
//kW IR Power Pulse vars
volatile byte wattHourPulses = 0;
int wattHourPulsesPerHour = 0;
unsigned int wattHourPulsesPerDay;
unsigned long wattHourPulsesPerMonth;
byte minuteCounter = 0;
byte hourCounter = 0;
byte oldPulses = 0;
float kWh;
char smtpServer[] = {"smtp.<your SMTP domain here>.net"};
//Ethernet clients
EthernetClient email;
EthernetClient Xively;
EthernetClient php;
//NTP
IPAddress timeServer(132, 163, 4, 102);       // time-b.timefreq.bldrdoc.gov
EthernetUDP Udp;
unsigned int localPort = 8888;                // local port to listen for UDP packets
int timeZone = -7;                            // set this shit later
//serial buffer collector stuff
String inputstring = ""; 
boolean input_stringcomplete = false;         // have we received all the data from the PC
//60 second kW shifting
unsigned int counters[60];
unsigned long lastMove;
byte sum;
//******************************BEGIN SDG&E POLL FUNCTION
void powerPoll() {
wattHourPulses++;
counters[59]++;
}
//******************************END SDG&E POLL FUNCTION
void setup() {
inputstring.reserve(100);                   //serial buffer command collector
Serial.begin(115200);
Serial1.begin(19200);                     //serial to Mega2
delay(2000);
pinMode(10,OUTPUT);
digitalWrite(10,LOW);
pinMode(53, OUTPUT); //Ethernet
pinMode(4,OUTPUT);
digitalWrite(4,HIGH);
Serial.print(F("SPI Bus initialized"));
Serial.println();
Serial.print(F("Waiting for DHCP..."));
Serial.println();
int i = 0;
int DHCP = 0;
DHCP = Ethernet.begin(mac);
while( DHCP == 0 && i < 30) {               //Try to get dhcp settings 30 times before giving up
delay(1000);
DHCP = Ethernet.begin(mac);
i++;
}
if(!DHCP) {
Serial.println(F("DHCP FAILED"));
for(;;);                                  //Infinite loop because DHCP Failed
}
Serial.println(F("DHCP Success"));
Serial.print(F("Ethernet initialized"));
Serial.println();
Serial.print(F("IP number assigned by DHCP is "));
Serial.println(Ethernet.localIP());
Serial.println();
Udp.begin(localPort);
Serial.println(F("Waiting for NTP sync"));
setSyncProvider(getNtpTime);
Serial.println();
server.begin();
Serial.print(F("HTTP Server started "));
Serial.println();
Serial.print(F("Live on the web at <your domain here>.com/current.htm"));
Serial.println();
Serial.println();
// initialize SD card
Serial.println(F("Initializing SD card..."));
if (!SD.begin(4)) {
Serial.println(F("ERROR - SD card initialization failed!"));
return;    // init failed
}
Serial.println(F("SUCCESS - SD card initialized.\r\n"));
// check for files on SD Card
kwFile = SD.open("/");
printDirectory(kwFile, 0);
kwFile.close();
if(SD.exists("DAILY.TXT")) {
kwFile = SD.open("DAILY.TXT");
while (kwFile.available()) {
wattHourPulsesPerDay = kwFile.parseInt();
Serial.print(F("Found existing kW pulses logged DTD: "));
Serial.println(wattHourPulsesPerDay);
kwFile.close();
}
} 
else {
Serial.println(F("Missed reading the daily file in setup"));
}
if(SD.exists("MONTHLY.TXT")) {
kwFile = SD.open("MONTHLY.TXT");
while (kwFile.available()) {
wattHourPulsesPerMonth = kwFile.parseInt();
Serial.print(F("Found existing kW pulses logged MTD: "));
Serial.println(wattHourPulsesPerMonth);
kwFile.close();
}
}
else {
Serial.println(F("Missed reading the monthly file in setup"));
}
headerNotes();
pinMode(2, INPUT);
pinMode(3, INPUT);
pinMode(5, INPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);
pinMode(22,OUTPUT);                         //OneWire Bus
pinMode(26, OUTPUT);                        //IR LED confirmation flicker
pinMode(A7, INPUT);                         //daylight sensor
pinMode(A8, OUTPUT);                        //LED flicker on POST to cloud
Serial.println();
Serial.print(F("Arduino PINS initialized"));
Serial.println();
Serial.println();
Serial.print(F("GO"));
Serial.println();
Serial.println();
delay(1000);
Serial.println();
Serial.println(F("1-Wire ready....."));
attachInterrupt (0, powerPoll, RISING);  // to catch the change from LOW to HIGH
}
time_t prevDisplay = 0; // when the digital clock was displayed NTP STUFF, HERE FOR SOME REASON
void loop() {  
EthernetClient client = server.available();  // try to get client
if (client) {  // got client?
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {   // client data available to read
char c = client.read(); // read 1 byte (character) from client
// limit the size of the stored received HTTP request
// buffer first part of HTTP request in HTTP_req array (string)
// leave last element in array as 0 to null terminate string (REQ_BUF_SZ - 1)
if (req_index < (REQ_BUF_SZ - 1)) {
HTTP_req[req_index] = c;          // save HTTP request character
req_index++;
}
// last line of client request is blank and ends with \n
// respond to client only after last line received
if (c == '\n' && currentLineIsBlank) {
// send a standard http response header
client.println("HTTP/1.1 200 OK");
// remainder of header follows below, depending on if
// web page or XML page is requested
// Ajax request - send XML file
if (StrContains(HTTP_req, "ajax_inputs")) {
// send rest of HTTP header
client.println("Content-Type: text/xml");
client.println("Connection: keep-alive");
client.println();
SetLEDs();
// send XML file containing input states
XML_response(client);
}
/*
else if (StrContains(HTTP_req, "jsonKwNow")) {
client.println("Content-Type: application/json");
client.println("Connection: close");
client.println();
// send JSON file containing input states
JSON_response(client);
}
*/
else if (StrContains(HTTP_req, "GET / ") || StrContains(HTTP_req, "GET /index.htm")) {
client.println(F("Content-Type: text/html"));
client.println(F("Connection: close"));
client.println();
webFile = SD.open("index.htm");        // open web page file
}
else if (StrContains(HTTP_req, "GET /history.htm")) {
client.println(F("Content-Type: text/html"));
client.println(F("Connection: close"));
client.println();
webFile = SD.open("history.htm");        // open web page file
}
else if (StrContains(HTTP_req, "GET /theShow.css")) {
client.println(F("Content-Type: text/css"));
client.println(F("Connnection: close"));
client.println();
webFile = SD.open("theShow.css");        // send the stylesheet
}
else if (StrContains(HTTP_req, "GET /current.htm")) {
client.println(F("Content-type: text/html"));
client.println(F("Connection: close"));
client.println();
webFile = SD.open("current.htm");
}
else if (StrContains(HTTP_req, "GET /current.js")) {
client.println(F("Content-type: application/javascript"));
client.println(F("Connection: close"));
client.println();
webFile = SD.open("current.js");
}
else if (StrContains(HTTP_req, "GET /robots.txt")) {
client.println(F("Content-type: text/plain"));
client.println(F("Connection: close"));
client.println();
webFile = SD.open("robots.txt");
}
else if (StrContains(HTTP_req, "GET /MONTHLY.TXT")) {
client.println(F("Content-type: text/plain"));
client.println(F("Connection: close"));
client.println();
webFile = SD.open("MONTHLY.TXT");
}
else if (StrContains(HTTP_req, "GET /DAILY.TXT")) {
client.println(F("Content-type: text/plain"));
client.println(F("Connection: close"));
client.println();
webFile = SD.open("DAILY.TXT");
}
else if (StrContains(HTTP_req, "GET /kwNow")) {
client.println(F("Content-type: text/plain"));
client.println(F("Connection: close"));
client.println();
//webFile = SD.open("DAILY.TXT");
kwNow_response(client);
}
// send web page to client
if (webFile) {
while(webFile.available()) {
client.write(webFile.read());
}
webFile.close();
}
// display received HTTP request on serial port
Serial.print(HTTP_req);
Serial.println();
// reset buffer index and all buffer elements to 0
req_index = 0;
StrClear(HTTP_req, REQ_BUF_SZ);
break;
}
// every line of text received from the client ends with \r\n
if (c == '\n') {
// last character on line of received text
// starting new line with next character read
currentLineIsBlank = true;
} 
else if (c != '\r') {
// a text character was received from client
currentLineIsBlank = false;
}
} // end if (client.available())
} // end while (client.connected())
delay(1);      // give the web browser time to receive the data
client.stop(); // close the connection
} // end if (client) 
//******************************BEGIN 60 SECOND UPDATE
byte i;
if(second() == 59) {
Serial.println();
Serial.print(F("POST DATA STARTED AT "));
Serial.print(millis());
analogWrite(A8,255);        
Serial.println();
Serial.println(F("Calculating kWh and Amps for last 60 seconds"));
getPowerNow();
Serial.println(F("Posting data to Xively.com"));
xivelyPost();
Serial.println(F("--- DONE ---"));
Serial.println();
Serial.print(F("POST DATA ENDED AT "));
Serial.print(millis());
Serial.println();
sendSensorSummary();
Serial.print(F("Free bytes of sRAM = "));
Serial.println(freeRam());
Serial.println();
headerNotes();
if (minute() == 00 || minute() == 15 || minute() == 30 || minute() == 45 ) { //15 minute 'cron' job
dailySdCardUpdate();
monthlySdCardUpdate();
}       
if (minute() == 59) {
wattHourPulsesPerHour = 0;        
}
if (hour() == 23 && minute() == 59) {
wattHourPulsesPerDay = 0;
}
if (month() == 1 || month() == 3 || month() == 5 || month() == 7 || month() == 8 || month() == 10 || month() == 12) {
if (day() == 31 && hour() == 23 && minute() == 59) {
wattHourPulsesPerMonth = 0;
}
}
if (month() == 4 || month() == 6 || month() == 9 ||  month() == 11) {
if (day() == 30 && hour() == 23 && minute() == 59) {
wattHourPulsesPerMonth = 0;
}
}
if (month() == 2) {
if (year() == 2016 || year() == 2020 || year() == 2024 || year() == 2028 || year() == 2032 || year() == 2036) {
leapYear = true;
if (day() == 29 && hour() == 23 && minute() == 59) {
wattHourPulsesPerMonth = 0;
}
}
}
if (leapYear == false && month() == 2) {
if (day() == 28 && hour() == 23 && minute() == 59) {
wattHourPulsesPerMonth = 0;
}
}
Serial.println();
Serial.print(F("POST DATA LOOP ENDED AT "));
Serial.print(millis());
Serial.println();
//delay(1000);
analogWrite(A8,0);
}  //******************************END 60 SECOND UPDATE
oneWirePoll();
int timeZone = -7;                                          // reread it so we can do come math
if (timeStatus() != timeNotSet) {                           // makes the serial mon display time forever
if (now() != prevDisplay) {                               // update the display only if time has changed
prevDisplay = now();
digitalClockDisplay();
}
}
if (wattHourPulses > oldPulses) {
noInterrupts();
oldPulses = wattHourPulses;
interrupts();
wattHourPulsesPerHour++;                                  // add current minute pulse count to hourly pulse count
datastreams[10].setFloat(wattHourPulsesPerHour);
wattHourPulsesPerDay++;                                   // add current minute pulse count to daily pulse count
datastreams[11].setFloat(wattHourPulsesPerDay);
wattHourPulsesPerMonth++;                                 // add current minute pulse count to monthly pulse count
datastreams[12].setFloat(wattHourPulsesPerMonth);
Serial.print(F("Meter Pulse Registered: now :: hour :: day :: month = "));
Serial.print(oldPulses);
Serial.print(" :: ");
Serial.print(wattHourPulsesPerHour);
Serial.print(" :: ");
Serial.print(wattHourPulsesPerDay);
Serial.print(" :: ");
Serial.println(wattHourPulsesPerMonth);
for (int i=0;i<512;i++) {
digitalWrite(26,HIGH);
}
digitalWrite(26,LOW);
}
//sum the array
if (millis() - lastMove >= 1000) {                          // every second copy each array entry to the previous one
lastMove = millis();   
noInterrupts();
for (i = 0; i < 59; i++) {
counters[i] = counters[i + 1]; 
}
counters[59] = 0; 
// total it
sum = 0;
for (i = 0; i < 60; i++) {
sum += counters[i];
}
interrupts();
// show total
Serial.print(F("Rolling 60 second sum @ "));
Serial.print(millis());
Serial.print(F(" millis >> "));
Serial.println(sum);
sendPHPData();
}  // end of another second up
if (input_stringcomplete){                                  //if a string from the PC has been recived in its entierty 
if (inputstring == "dumpToSd!") {
analogWrite(A8, 255);
dailySdCardUpdate();
monthlySdCardUpdate();
delay(5000);
}
if (inputstring == "test!") {
Serial1.print("I just sent this to Mega1 from Mega2\n2");
}
inputstring = "";                                         //clear the string:
input_stringcomplete = false;                             //reset the flage used to tell if we have recived a completed string from the PC
}
///////////////////////////////////////////////////////
//  ser1DataIncoming = Serial1.available();
//  while (ser1DataIncoming != 0)                 //While there is something to be read
//  {
//    ser1DataVal = Serial1.parseInt();             //Reads integers as integer rather than ASCI. Anything else returns 0
//    ser1DataVal = ser1DataVal + 5;
//    Serial1.print(ser1DataVal);                  //Send the new ser1DataVal back to the Tx 
//    ser1DataIncoming = Serial.available();    
//  }
//////////////////////////////////////////////////////
} // END OF MAIN LOOP
void sendPHPData() {
//EthernetClient php;
analogWrite(A8,255);
Serial.println();
Serial.print(F("Initiating connection to pi.<your domain>.com >> "));
delay(250); //This one keeps it from hanging
if (php.connect(phpServer, <your HTTP port>)) {
Serial.print(F("connected >> "));
php.print(F("GET /XXXXXX/YYYYY/someBSFilename.php?routine=POST"));
php.print(F("&minute="));
php.print(sum);
php.print(F("&hour="));
php.print(wattHourPulsesPerHour);
php.print(F("&day="));
php.print(wattHourPulsesPerDay);
php.print(F("&month="));
php.print(wattHourPulsesPerMonth);
php.println(F(" HTTP/1.1"));
php.println(F("Host: www.<your domain here>.com"));
php.println();  
}
else {
Serial.println(F("### FAILED ###\r\n"));
}
//stop client
php.stop();
Serial.print(F("connection closed\r\n"));
while(php.status() != 0) {
delay(5);
}
analogWrite(A8,0);
}
void getPowerNow() {  
kWh = ((wattHourPulses * 60) * .001);  //wH pulses/sec * 60 minutes * wH:kWh ratio
amps = ((kWh * 1000) / 120);
datastreams[6].setFloat(kWh); //set Xively
datastreams[9].setFloat(amps); //set Xively
Serial.println();
noInterrupts();
wattHourPulses = 0;
interrupts();
oldPulses = 0;
}
void sendSensorSummary() {
char charBuff[10];
String owA = "";
String owB = "";
String owC = "";
String owD = "";
String owE = "";
String owF = "";
String owG = "";
String owH = "";
String owI = "";
memset(charBuff, '\0', 10);
dtostrf(ow0, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owA += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow1, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owB += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow2, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owC += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow3, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owD += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow4, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owE += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow5, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owF += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow6, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owG += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow7, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owH += charBuff[i];
}
memset(charBuff, '\0', 10);
dtostrf(ow8, 4, 2, charBuff);
for (int i=0;i<=4;i++) {
owI += charBuff[i];
}
Serial.println();
Serial.print(F("Current Amps = "));
Serial.println(amps);
Serial.println();
Serial.print(F("Current kWh = "));
Serial.println(kWh);
Serial.println();
Serial.print(F("Current Lux = "));
Serial.println(daylight);
Serial.println();
Serial.println(F("||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"));
Serial.println(F("|  Data  | Friendly Name  | Sensor | Alias1 | Alias2 | Alarm |"));
Serial.println(F("||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"));
Serial.println("| "+owA+"  | Downstairs     |  ow0   |  owA   |  0x04  |   "+locked22a+"   |");
Serial.println("| "+owB+"  | Master Bedroom |  ow1   |  owB   |  0xF7  |   "+locked22b+"   |");
Serial.println("| "+owC+"  | Front Room     |  ow2   |  owC   |  0x84  |   "+locked22c+"   |");
Serial.println("| "+owD+"  | Garage         |  ow3   |  owD   |  0x81  |   "+locked22d+"   |");
Serial.println("| "+owE+"  | Back Porch     |  ow4   |  owE   |  0xDC  |   "+locked22e+"   |");
Serial.println("| "+owF+"  | Office         |  ow5   |  owF   |  0x61  |   "+locked22f+"   |");
Serial.println("| "+owG+"  | Unassigned 1   |  ow6   |  owG   |  0xDA  |   "+locked22g+"   |");
Serial.println("| "+owH+"  | Black Refer    |  ow7   |  owH   |  0x0D  |   "+locked22h+"   |");
Serial.println("| "+owI+"  | Unassigned 3   |  ow8   |  owI   |  0xF4  |   "+locked22i+"   |");
Serial.println(F("||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||"));
Serial.println();  
}
void oneWirePoll() {
if (millis() - previousOwLoop > 4163) {
Serial1.print("I just sent this to Mega1 from Mega2\n2");
daylight = analogRead(A7);  //get the value from the daylight sensor
datastreams[8].setFloat(daylight);
Serial.print(F("Daylight Value Read >> Intensity = "));
Serial.println(daylight);  //print the value to Serial monitor
previousOwLoop = millis();
dataReadyToPoll = 1;
//find a device
if (!ds.search(addr)) {
ds.reset_search();
return;
}
if (OneWire::crc8( addr, 7) != addr[7]) {
return;
}
if (addr[0] != DS18S20_ID && addr[0] != DS18B20_ID) {
return;
}
ds.reset();
ds.select(addr);
// Start conversion
ds.write(0x44, 1);
}
if ((millis() - previousOwLoop > 750) && (dataReadyToPoll == 1)) {
dataReadyToPoll = 0;
present = ds.reset();
ds.select(addr);    
ds.write(0xBE);         // Read Scratchpad
// the eighth ROM byte indicates ROM CRC
switch (addr[7]) {
case 0x04: //0x28, 0xDD, 0x9C, 0xDD, 0x04, 0x00, 0x00, 0x04
tempLocName = "Downstairs";
owSensorId = 0;
break;
case 0xF7: //0x28, 0x19, 0x3E, 0xDD, 0x04, 0x00, 0x00, 0xF7
tempLocName = "Master Bedroom";
owSensorId = 1;
break;
case 0x84: //0x28, 0x32, 0xFA, 0xDD, 0x04, 0x00, 0x00, 0x84
tempLocName = "Front Room";
owSensorId = 2;
break;
case 0x81: //0x28, 0x77, 0xCE, 0xDD, 0x04, 0x00, 0x00, 0x81
tempLocName = "Garage";
owSensorId = 3;
break;
case 0xDC: //0x28, 0x0D, 0x7F, 0xDE, 0x04, 0x00, 0x00, 0xDC
tempLocName = "Back Porch";
owSensorId = 4;
break;
case 0x61: //0x28, 0x36, 0x17, 0x10, 0x05, 0x00, 0x00, 0x61
tempLocName = "Office";
owSensorId = 5;
break;
case 0xDA: //0x28, 0xAF, 0x11, 0x10, 0x05, 0x00, 0x00, 0xDA
tempLocName = "Unassigned 1";
owSensorId = 6;
break;
case 0x0D: //0x28, 0x0C, 0x14, 0x10, 0x05, 0x00, 0x00, 0x0D
tempLocName = "Black Refrigerator";
owSensorId = 7;
break;
case 0xF4: //0x28, 0x50, 0xF1, 0x0F, 0x05, 0x00, 0x00, 0xF4
tempLocName = "Unassigned 3";
owSensorId = 8;
break;
default:
Serial.println();
Serial.print(F("Can not determine bit 8 from the ROM"));
Serial.println();
Serial.print(F("I must have found a new sensor on the bus"));
Serial.println();
return;
} 
// the first ROM byte indicates which chip
switch (addr[0]) {
case 0x10:
//Serial.println("  Chip = DS18S20");  // or old DS1820
type_s = 1;
break;
case 0x28:
//Serial.println("  Chip = DS18B20");
type_s = 0;
break;
case 0x22:
//Serial.println("  Chip = DS1822");
type_s = 0;
break;
default:
//Serial.println("Device is not a DS18x20 family device.");
return;
} 
//Serial.print("  Data = ");
//Serial.print(present, HEX);
//Serial.print(" ");
for ( i = 0; i < 9; i++) {           // we need 9 bytes
data[i] = ds.read();
//Serial.print(data[i], HEX);
//Serial.print(" ");
}
//Serial.print(" CRC=");
//Serial.print(OneWire::crc8(data, 8), HEX);
//Serial.println();
// Convert the data to actual temperature
// because the result is a 16 bit signed integer, it should
// be stored to an "int16_t" type, which is always 16 bits
// even when compiled on a 32 bit processor.
int16_t raw = (data[1] << 8) | data[0];
if (type_s) {
raw = raw << 3; // 9 bit resolution default
if (data[7] == 0x10) {
// "count remain" gives full 12 bit resolution
raw = (raw & 0xFFF0) + 12 - data[6];
}
} 
else {
byte cfg = (data[4] & 0x60);
// at lower res, the low bits are undefined, so let's zero them
if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
else if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
else if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
//// default is 12 bit resolution, 750 ms conversion time
}
celsius = (float)raw / 16.0;
fahrenheit = celsius * 1.8 + 32.0;
//Serial.print(celsius);
//Serial.print(" Celsius, ");
Serial.print(tempLocName);
Serial.print(F(" is currently:  "));
Serial.print(fahrenheit);
Serial.println(F("F"));
if (owSensorId == 0) { //Downstairs
ow0 = fahrenheit;
datastreams[0].setFloat(ow0);
if (millis() - lockedAt22a > unlockTime) { // see if we can unlock the timer
locked22a = false;
}
if (locked22a == false && (ow0 < 50.0 || ow0 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow0;         
sendEmail(tempLocName, currentTemp);
locked22a = true;
lockedAt22a = millis();
}
}
} 
else if (owSensorId == 1) { //Master Bedroom
ow1 = fahrenheit;
datastreams[1].setFloat(ow1);
if (millis() - lockedAt22b > unlockTime) { // see if we can unlock the timer
locked22b = false;
}         
if (locked22b == false && (ow1 < 50.0 || ow1 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow1;         
sendEmail(tempLocName, currentTemp);
locked22b = true;
lockedAt22b = millis();
}
}
}
else if (owSensorId == 2) { //Front Room
ow2 = fahrenheit;
datastreams[2].setFloat(ow2);
if (millis() - lockedAt22c > unlockTime) { // see if we can unlock the timer
locked22c = false;
}         
if (locked22c == false && (ow2 < 50.0 || ow2 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow2;         
sendEmail(tempLocName, currentTemp);
locked22c = true;
lockedAt22c = millis();
}
}
}
else if (owSensorId == 3) { //Garage
ow3 = fahrenheit;
datastreams[3].setFloat(ow3);
if (millis() - lockedAt22d > unlockTime) { // see if we can unlock the timer
locked22d = false;
}
if (locked22d == false && (ow3 < 50.0 || ow3 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow3;         
sendEmail(tempLocName, currentTemp);
locked22d = true;
lockedAt22d = millis();
}
}
}
else if (owSensorId == 4) { //Back Porch
ow4 = fahrenheit;
datastreams[4].setFloat(ow4);
if (millis() - lockedAt22e > unlockTime) { // see if we can unlock the timer
locked22e = false;
}         
if (locked22e == false && (ow4 < 50.0 || ow4 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow4;
sendEmail(tempLocName, currentTemp);
locked22e = true;
lockedAt22e = millis();
}
}
}
else if (owSensorId == 5) { //Office
ow5 = fahrenheit;
datastreams[5].setFloat(ow5);
if (millis() - lockedAt22f > unlockTime) { // see if we can unlock the timer
locked22f = false;
}         
if (locked22f == false && (ow5 < 50.0 || ow5 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow5;         
sendEmail(tempLocName, currentTemp);
locked22f = true;
lockedAt22f = millis();
}
}
}
else if (owSensorId == 6) { //Unassigned 1
ow6 = fahrenheit;
//datastreams[6].setFloat(ow6);
if (millis() - lockedAt22g > unlockTime) { // see if we can unlock the timer
locked22g = false;
}         
if (locked22g == false && (ow6 < 50.0 || ow6 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow6;         
sendEmail(tempLocName, currentTemp);
locked22g = true;
lockedAt22g = millis();
}
}
}
else if (owSensorId == 7) { //Black Refrigerator
ow7 = fahrenheit;
datastreams[7].setFloat(ow7);
if (millis() - lockedAt22h > unlockTime) { // see if we can unlock the timer
locked22h = false;
}         
if (locked22h == false && (ow7 < 32.0 || ow7 > 50.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow7;         
sendEmail(tempLocName, currentTemp);
locked22h = true;
lockedAt22h = millis();
}
}
}
else if (owSensorId == 8) { //Unassigned 2
ow8 = fahrenheit;
if (millis() - lockedAt22i > unlockTime) { // see if we can unlock the timer
locked22i = false;
}         
if (locked22i == false && (ow8 < 50.0 || ow8 > 90.0)) { //generate the email
for (int y=0;y<1;y++) {
int currentTemp = ow8;         
sendEmail(tempLocName, currentTemp);
locked22i = true;
lockedAt22i = millis();
}
}
}
}
} 
void xivelyPost() {
XivelyClient xivelyclient(Xively);
int ret = xivelyclient.put(feed, xivelyKey);  // Send to Xively
if(ret == 200){                          // If success, blink LED
analogWrite(A8,0);
delay(50);
analogWrite(A8,255);
delay(50);
analogWrite(A8,0);
delay(50);
analogWrite(A8,255);
delay(50);
analogWrite(A8,0);
delay(50);
analogWrite(A8,255);    
}  
}
void digitalClockDisplay(){
// digital clock display of the time
Serial.print(hour());
printDigits(minute());
printDigits(second());
Serial.print(F(" "));
Serial.print(month());
Serial.print(F("/"));
Serial.print(day());
Serial.print(F("/"));
Serial.print(year()); 
Serial.println(); 
if (((month() == 11) && (day() >= 3) || (month() == 12) || (month() > 0) && (month() < 3) || (month() == 3) && (day() >= 10))) {
timeZone = timeZone++;
}
}
void printDigits(int digits){
// utility for digital clock display: prints preceding colon and leading 0
Serial.print(F(":"));
if(digits < 10)
Serial.print(F("0"));
Serial.print(digits);
}
/*-------- NTP code ----------*/
const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets
time_t getNtpTime() {
while (Udp.parsePacket() > 0) ; // discard any previously received packets
Serial.println(F("Transmit NTP Request"));
sendNTPpacket(timeServer);
uint32_t beginWait = millis();
while (millis() - beginWait < 1500) {
int size = Udp.parsePacket();
if (size >= NTP_PACKET_SIZE) {
Serial.println(F("Receive NTP Response"));
Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
unsigned long secsSince1900;
// convert four bytes starting at location 40 to a long integer
secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
secsSince1900 |= (unsigned long)packetBuffer[43];
return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
}
}
Serial.println(F("No NTP Response :-("));
return 0; // return 0 if unable to get the time
}
// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address) {
// set all bytes in the buffer to 0
memset(packetBuffer, 0, NTP_PACKET_SIZE);
// Initialize values needed to form NTP request
// (see URL above for details on the packets)
packetBuffer[0] = 0b11100011;   // LI, Version, Mode
packetBuffer[1] = 0;     // Stratum, or type of clock
packetBuffer[2] = 6;     // Polling Interval
packetBuffer[3] = 0xEC;  // Peer Clock Precision
// 8 bytes of zero for Root Delay & Root Dispersion
packetBuffer[12]  = 49;
packetBuffer[13]  = 0x4E;
packetBuffer[14]  = 49;
packetBuffer[15]  = 52;
// all NTP fields have been given values, now
// you can send a packet requesting a timestamp:                 
Udp.beginPacket(address, 123); //NTP requests are to port 123
Udp.write(packetBuffer, NTP_PACKET_SIZE);
Udp.endPacket();
} 
byte sendEmail(String tempLocName, int currentTemp) {
byte thisByte = 0;
byte respCode;
if(email.connect(smtpServer,25)) {
Serial.println(F("Connected to smtp.<your SMTP domain here>.net"));
} 
else {
Serial.println(F("Connection to smtp.<your SMTP domain here>.net failed"));
return 0;
}
if(!eRcv()) return 0;
Serial.println(F("Sending helo"));
// change to your public ip
email.println(F("helo live.<your domain here>.com"));
if(!eRcv()) return 0;
Serial.println(F("Sending from <your domain here>2011@<your SMTP domain here>.net"));
// change to your email address (sender)
email.println(F("MAIL From: <your domain here>2011@<your SMTP domain here>.net"));
if(!eRcv()) return 0;
// change to recipient address
Serial.println(F("Sending to: jason@<your domain here>.com & jenny@<your domain here>.com"));
email.println(F("RCPT To: jason@<your domain here>.com"));
email.println(F("RCPT To: jenny@<your domain here>.com"));
if(!eRcv()) return 0;
Serial.println(F("Sending DATA"));
email.println(F("DATA"));
if(!eRcv()) return 0;
Serial.println(F("Sending email"));
// change to recipient address
email.println(F("To: J. Young <jason@<your domain here>.com>"));
// change to your address
email.println(F("From: The WatchDog <<your email here>@<your SMTP domain here>.net>"));
email.println("Subject: " + tempLocName + " is: " + currentTemp + "°F\r\n");
email.println("The " + tempLocName + " is currently " + currentTemp + "°F.\r\n");
//email.println("Click for graphs: https://www.thingspeak.com/channels/<channel ID>#privateview");
email.println(F("."));
if(!eRcv()) return 0;
Serial.println(F("Sending QUIT"));
email.println(F("QUIT"));
if(!eRcv()) return 0;
email.stop();
Serial.println(F("Disconnected from smtp.<your SMTP domain here>.net"));
return 1;
}
byte eRcv() {
byte respCode;
byte thisByte;
int loopCount = 0;
while(!email.available()) {
delay(1);
loopCount++;
// if nothing received for 10 seconds, timeout
if(loopCount > 10000) {
email.stop();
Serial.println(F("\r\nTimeout"));
return 0;
}
}
respCode = email.peek();
while(email.available()) {  
thisByte = email.read();    
Serial.write(thisByte);
}
if(respCode >= '4') {
efail();
return 0;  
}
return 1;
}
void efail() {
byte thisByte = 0;
int loopCount = 0;
email.println(F("QUIT"));
while(!email.available()) {
delay(1);
loopCount++;
// if nothing received for 10 seconds, timeout
if(loopCount > 10000) {
email.stop();
Serial.println(F("\r\nTimeout"));
return;
}
}
while(email.available()) {  
thisByte = email.read();    
Serial.write(thisByte);
}
email.stop();
Serial.println(F("Disconnected from smtp.<your SMTP domain here>.net"));
}
int freeRam () { //check free RAM
extern int __heap_start, *__brkval; 
int v; 
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); 
}
// list SD content
void printDirectory(File dir, int numTabs) {
while(true) {
File entry =  dir.openNextFile();
if (! entry) {
// no more files
//Serial.println("**nomorefiles**");
break;
}
for (uint8_t i=0; i<numTabs; i++) {
Serial.print('\t');
}
Serial.print(entry.name());
if (entry.isDirectory()) {
Serial.println("/");
printDirectory(entry, numTabs+1);
} else {
// files have sizes, directories do not
Serial.print("\t\t");
Serial.println(entry.size(), DEC);
}
entry.close();
}
}
//update the SD card with current day pulse counts
void dailySdCardUpdate() {
if (SD.exists("DAILY.TXT")) {
Serial.print(F("Found an existing DAILY.TXT file >>>> "));
SD.remove("DAILY.TXT");
if (!SD.exists("DAILY.TXT")) {
Serial.print(F("deleted it >> "));
kwFile = SD.open("DAILY.TXT", FILE_WRITE);
if (kwFile) {
Serial.print(F("recreated it >> "));
kwFile.println(wattHourPulsesPerDay);
kwFile.println();
kwFile.close();
Serial.print(F("& wrote "));
Serial.print(wattHourPulsesPerDay);
Serial.println(F(" to it"));
} 
else {
Serial.print(F("Unable to create or write to DAILY.TXT file\r\n"));
}
} 
else {
Serial.print(F("Unable to delete existing DAILY.TXT file\r\n"));
}
}  
}
//update the SD card with current months pulse counts
void monthlySdCardUpdate() {
if (SD.exists("MONTHLY.TXT")) {
Serial.print(F("Found an existing MONTHLY.TXT file >> "));
SD.remove("MONTHLY.TXT");
if (!SD.exists("MONTHLY.TXT")) {
Serial.print(F("deleted it >> "));
kwFile = SD.open("MONTHLY.TXT", FILE_WRITE);
if (kwFile) {
Serial.print(F("recreated it >> "));
kwFile.println(wattHourPulsesPerMonth);
kwFile.println();
kwFile.close();
Serial.print(F("& wrote "));
Serial.print(wattHourPulsesPerMonth);
Serial.println(F(" to it"));
}
else {
Serial.print(F("Unable to create or write to MONTHLY.TXT file\r\n"));
}
} 
else {
Serial.print(F("Unable to delete existing MONTHLY.TXT file\r\n"));
}
}  
}
//print the serial buffer when ! is sent
void serialEvent() {                                               //if the hardware serial port_0 receives a char              
char inchar = (char)Serial.read();                              //get the char we just received
inputstring += inchar;                                           //add it to the inputString
if(inchar == '!') {
input_stringcomplete = true;
}
}
//header notes and per minute reminders
void headerNotes() {
Serial.print(F("\r\n\r\nRunning v0_7c_vera_http_json.ino"));
Serial.print(F("\r\nCompiled on 05/03/2014\r\n"));
Serial.print(F("\r\nTo submit the command enter an exclamation point '!'\r\nThe serial monitor will accept these commands:\r\n"));
Serial.println(F("\"dumpToSd!\" will force the daily and monthly pulse counts to\r\n  write to SD instead of waiting until the next quarter-hour"));
}
// checks if received HTTP request is switching on/off LEDs
// also saves the state of the LEDs
void SetLEDs(void) {
// LED 1 (pin 6)
if (StrContains(HTTP_req, "LED1=1")) {
LED_state[0] = 1;  // save LED state
digitalWrite(6, HIGH);
Serial.println(F("D6 = HIGH"));
}
else if (StrContains(HTTP_req, "LED1=0")) {
LED_state[0] = 0;  // save LED state
digitalWrite(6, LOW);
Serial.println(F("D6 = LOW"));
}
// LED 2 (pin 7)
if (StrContains(HTTP_req, "LED2=1")) {
LED_state[1] = 1;  // save LED state
digitalWrite(7, HIGH);
Serial.println(F("D7 = HIGH"));
}
else if (StrContains(HTTP_req, "LED2=0")) {
LED_state[1] = 0;  // save LED state
digitalWrite(7, LOW);
Serial.println(F("D7 = LOW"));
}
// LED 3 (pin 8)
if (StrContains(HTTP_req, "LED3=1")) {
LED_state[2] = 1;  // save LED state
digitalWrite(8, HIGH);
Serial.println(F("D8 = HIGH"));
}
else if (StrContains(HTTP_req, "LED3=0")) {
LED_state[2] = 0;  // save LED state
digitalWrite(8, LOW);
Serial.println(F("D8 = LOW"));
}
// LED 4 (pin 9)
if (StrContains(HTTP_req, "LED4=1")) {
LED_state[3] = 1;  // save LED state
digitalWrite(9, HIGH);
Serial.println(F("D9 = HIGH"));
}
else if (StrContains(HTTP_req, "LED4=0")) {
LED_state[3] = 0;  // save LED state
digitalWrite(9, LOW);
Serial.println(F("D9 = LOW"));
}
}
// send the XML file with analog values, switch status
//  and LED status
void XML_response(EthernetClient cl) {
int analog_val;                                        // stores value read from analog inputs
int count;                                             // used by 'for' loops
int sw_arr[] = {2, 3, 5};                              // pins interfaced to switches
cl.print(F("<?xml version = \"1.0\" ?>"));
cl.print(F("<inputs>"));
// read analog inputs
for (count = 2; count <= 5; count++) { // A2 to A5
analog_val = analogRead(count);
cl.print(F("<analog>"));
cl.print(analog_val);
cl.println(F("</analog>"));
}
// read switches
for (count = 0; count < 3; count++) {
cl.print(F("<switch>"));
if (digitalRead(sw_arr[count])) {
cl.print(F("ON"));
}
else {
cl.print(F("OFF"));
}
cl.println(F("</switch>"));
}    
// checkbox LED states
// LED1
cl.print(F("<LED>"));
if (LED_state[0]) {
cl.print(F("checked"));
}
else {
cl.print(F("unchecked"));
}
cl.println(F("</LED>"));
// LED2
cl.print(F("<LED>"));
if (LED_state[1]) {
cl.print(F("checked"));
}
else {
cl.print(F("unchecked"));
}
cl.println(F("</LED>"));
// button LED states
// LED3
cl.print(F("<LED>"));
if (LED_state[2]) {
cl.print(F("on"));
}
else {
cl.print(F("off"));
}
cl.println(F("</LED>"));
// LED4
cl.print(F("<LED>"));
if (LED_state[3]) {
cl.print(F("on"));
}
else {
cl.print(F("off"));
}
cl.println(F("</LED>"));
cl.print(F("</inputs>"));
}
/*
void JSON_response(EthernetClient json) {    
json.print("[");
json.print("{ \"sensorNow\":\"");
json.print(sum);
json.println("\"}]");
}
*/
void kwNow_response(EthernetClient kwNow) {
//kwNow.print(sum * 60); //the number of meter pulses in the last minute
kwNow.print("[");
kwNow.print("{ \"kwNow\":\"");
kwNow.print(sum * 60);
kwNow.println("\"}]");
}
// sets every element of str to 0 (clears array)
void StrClear(char *str, char length) {
for (int i = 0; i < length; i++) {
str[i] = 0;
}
}
// searches for the string sfind in the string str
// returns 1 if string found
// returns 0 if string not found
char StrContains(char *str, char *sfind) {
char found = 0;
char index = 0;
char len;
len = strlen(str);
if (strlen(sfind) > len) {
return 0;
}
while (index < len) {
if (str[index] == sfind[found]) {
found++;
if (strlen(sfind) == found) {
return 1;
}
}
else {
found = 0;
}
index++;
}
return 0;
}
//
//
//

Putting It All To Good Use

This entire project would be useless without some metrics.  That is the goal, right?  Here is my dashboard.  The elements are each labeled with their particular metric.  There’s a truckload of tragic math calculations to make this magic happen, particularly within the forecasting elements.  Lot’s of number stretching going on.  You need to know my dataflow to get a better grip on how I do this, but as of today (10/09/2013) I post my data to SQL on RasPi, some data to SD, some to a table in RAM, then via PHP to Xively, ThingSpeak and Open Sense every minute, several metrics, about 13 now, and then in the javaScript of the dashboard page I retrieve some of it back (from the SQL table in RAM) as JSON data via AJAX every few seconds (to spare the SD card).  But that’s a whole other article in itself.  This is the actual power dashboard in real-time (yes, there are others). The polling comes to my RasPi. If you are on “demand billing” then this is a real boon to you to have these metrics as you can now micromanage and tune your power usage to keep the money where it belongs… in your pocket, or mine, your choice.

Http iframes are not shown in https pages in many major browsers. Please read this post for details.

1 Comment

  1. Hello Jason. I’m developing a small industrial project using an Arduino component for low level monitoring and control. Writing the code is a little more than I’m up to at the moment. If you are interested in looking at it let me know. I can give you more details over email.
    Thanks, Paul.

Leave a Comment

Your email address will not be published. Required fields are marked *