Power Display

May 19, 2011 Note:  I took this device off ethernet.  Problems with the WiShield came up and there isn't a device that compares well with it right now.  I converted the display to XBee and it's working great.  The description below still applies for the most part and I'm keeping it for history.  Scroll down for the latest script and description.

October, 2010:

I just finished prototype work on a device to display the power usage in real time.  I took an Arduino, Wishield, serial display and some LEDs and cobbled them together and added a ton of code.  This device interrogates my power monitor and puts the value up on the LCD display.  It also forwards the data to Pachube to take the load off my laptop and returns it to its real purpose, surfing the web.  I always wanted an accurate clock so I hit the NIST servers and get the time response back and put it up on the second line of my display.  Now I have a clock to set the rest of the house up with.  Then, because it would be fun, I added a three color LED and code to go from green to red to give an immediate visual indication of the relative power level.

Here it is with a green indicator:



And this is when the power level is high:



The problem now is that the indicator looks pretty good in the daytime, but at night it looks like I have a Jedi Knight wielding a color changing light saber in the room.  Obviously I need to consider a light sensor and more code to control that as well.  The three other LEDs give me indications of wifi connection and data in and out for making sure the thing is alive.  Not shown here is an indicator on the LCD display that tells me when the power data hasn't updated.  If too long passes without update the device reboots and tries to establish communication.  This is how I overcome power failures that leave the devices out of sync.  Thanks LadyAda!

The indicator light works from 100% green to zero and zero red to 100% so it's sort of yellow in the middle.  I found this to be easy to adapt to.  I also coded different levels for peak and off peak periods such that the indicator shows the appropriate color depending on the time.  During peak periods I max red at 2kW and it moves to 10kW during off peak periods.  Yes, I probably use too much power.

Hopefully, this is not the final form factor.  I plan on something to pretty it up and mount it on the wall.  I'm leaning toward a picture frame kind of display that has room for other data as I develop it.  I would need room for temperatures, wind speed, etc to do this right.  I plan on interfaces to it being over my internal wifi network at some point.

10/19/2010

I got a photoresistor and added it to the system to control the indicator level  (I updated the sketch on this page already).  Now it doesn't light up half the house when the lights are out.  I guess I'm out of excuses and need to start really looking at putting this thing into some kind of enclosure.

11/09/2010

Got a pointer to a company called PacTec.  They had a wall mount cover that is made to fit standard (US) wall electrical boxes.  So, I got one and put the entire device into the box.  I wish someone made a bezel for the LCD displays, and maybe they do, but I couldn't find one.  However, it looks pretty good.  I still have to add a reset button (on order) for those rare occasions when something hangs it up but it's essentially done.

Here's the finished product working its little heart out on the wall


Update Feb 10, 2011:  While I was working on the thermostats (see tabs above) I ran across bezels that would fit the LCD display.  After some careful filing and strategic placement of hot glue, I installed one on this device.  I think it looks nice and it hides the small amount of light that leaks around the LCD display.


I also heavily modified the sketch to work off the automatic time update provided by the Arduino time library.  It now automatically gets the time from my in-house time server and seems to work very well with the exception of a bug in the house clock (see tab above).  Since this sketch was running out of ram as well, I also added code to move the strings into flash.  The revised sketch is below.

The Old Arduino Sketch

/*
 * Sketch to grab data from my power monitor and forward it to pachube.  It also displays real
 * power and NIST derived time on a serial enabled LCD.
 * Credits:
 * Most of the power measurement and calculations were invented by Trystan Lea and documented at
 * http://openenergymonitor.org/.  I modified it for the split phase 240 in residential use in the U.S.
 *
 * The code to get time from the NIST servers was posted by spidermonkey04 on the AsyncLabs Forum at
 * http://asynclabs.com/forums/viewtopic.php?f=19&t=152
 *
 * The clever tricks to POST to Pachube were posted by GregEigsti on the AsyncLabs Forum at
 * http://asynclabs.com/forums/viewtopic.php?f=15&t=86&start=10
 *
 * The idea for the color display was totally stolen from Alan Meany
 * http://www.alanmeany.net/ambientknowledge.html  I'm sure I didn't use the same techniques as he did,
 * but the idea came from there.
 *
 * I'm using a ladyada boot rom (purchased directly from her site) to overcome weak network problems
 * and multiple vulnerable arduinos by rebooting when necessary.  A lot of the remaining code was
 * taken from example sketches supplied by the Arduino site www.arduino.cc
 *
 * Open source rules!
 */

#include <WiServer.h>
#include <Time.h>
#include <SoftwareSerial.h>
#include <avr/wdt.h>
#include <MemoryFree.h>
#include <avr/pgmspace.h>

#define WIRELESS_MODE_INFRA 1
#define WIRELESS_MODE_ADHOC 2
#define tzOffset -7

// Wireless configuration parameters ----------------------------------------
unsigned char local_ip[] = {
  192,168,0,201}; // IP address of WiShield
unsigned char gateway_ip[] = {
  192,168,0,1}; // router or gateway IP address
unsigned char subnet_mask[] = {
  255,255,255,0}; // subnet mask for the local network
const prog_char ssid[] PROGMEM = {
  "ThompsonHouse"}; // max 32 bytes

unsigned char security_type = 1; // 0 - open; 1 - WEP; 2 - WPA; 3 - WPA2

// WPA/WPA2 passphrase
const prog_char security_passphrase[] PROGMEM = {
  "12345678"}; // max 64 characters

// WEP 128-bit keys
// sample HEX keys
prog_uchar wep_keys[] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Key 0
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Key 1
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Key 2
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Key 3
};

// setup the wireless mode
// infrastructure - connect to AP
// adhoc - connect to another WiFi device
unsigned char wireless_mode = WIRELESS_MODE_INFRA;

unsigned char ssid_len;
unsigned char security_passphrase_len;
// End of wireless configuration parameters ----------------------------------------

int realPower;
int myyear, mymonth, myday, myhour, myminute, mysecond;
time_t lastpowerCheck = 0;
time_t tNow;
int powerIndicator = 0;
int timedisplaydone = 0;
char pachubeData[60];
char general[50];
int generali;

void showMem(){
  strcpy_P(general,PSTR("Mem = "));
  Serial.print(general);
  Serial.println(freeMemory());
}

#define LCDrxPin 3
#define LCDtxPin 4
SoftwareSerial mySerial = SoftwareSerial(LCDrxPin, LCDtxPin);
//
//  Sparkfun LCD display routines
//
void LCDgoto(int position) { //position = line 1: 0-15, line 2: 16-31, 31+ defaults back to 0
  if (position<16)
  {
    mySerial.print(0xFE, BYTE);              //command flag
    mySerial.print((position+128), BYTE);    //position
  }
  else if (position<32){
    mySerial.print(0xFE, BYTE);   //command flag
    mySerial.print((position+48+128), BYTE);          //position
  }
  else
  {
    LCDgoto(0);
  }
}

void LCDline1()
{
  mySerial.print(0xFE, BYTE);   //command flag
  mySerial.print(128, BYTE);    //position to line 1
}

void LCDline2()
{
  mySerial.print(0xFE, BYTE);   //command flag
  mySerial.print(192, BYTE);    //position to line 2
}


// Displays the time on the LCD screen
void showTime()
{
  tNow = now();
  LCDline2();
  strcpy_P(general,PSTR("                "));
  mySerial.print(general); //sixteen spaces to clear line
  LCDline2();
  if (hour(tNow) < 10)          //leading zero
    mySerial.print('0', BYTE);
  mySerial.print(hour(tNow));
  mySerial.print(":");
  if (minute(tNow) < 10)            //leading zero
    mySerial.print('0', BYTE);
  mySerial.print(minute(tNow));
  mySerial.print(" ");
  if (month(tNow) < 10)            //leading zero
    mySerial.print('0', BYTE);
  mySerial.print(month(tNow));
  mySerial.print("/");
  if (day(tNow) < 10)            //leading zero
    mySerial.print('0', BYTE);
  mySerial.print(day(tNow));
  mySerial.print("/");
  mySerial.print(year(tNow) - 2000);
}

// Indicator light handling
// Not using the Blue LED
#define greenPin 3      // LED connected to these pins
#define redPin 6
#define OFFPEAKMAX 0
#define PEAKMAX 1
#define PEAKSTART 2
#define PEAKEND 3
// entries are: off peak max, peak max, peak start hour, peak end hour
prog_int32_t pwrRangeArray [7][4] PROGMEM = {
  {
    10000,10000,12,19  }
  ,   //Sunday no peak period
  {
    10000,2000,12,19  }
  ,    //Monday
  {
    10000,1350,12,19  }
  ,    //Tuesday
  {
    10000,2000,12,19  }
  ,    //Wednesday
  {
    10000,2000,12,19  }
  ,    //Thursday
  {
    10000,2000,12,19  }
  ,    //Friday
  {
    10000,10000,12,19  }
}; //Saturday no peak period
void alloff()
{
  analogWrite(redPin, 255);
  analogWrite(greenPin, 255);
}

void colorLamp(int pwrlevel)
{

  int indred, indgreen;
  int thisHour, thisDay, redrange, greenrange, redpercent, greenpercent;
  long i, x, y, rangemax;
#define redbright 255            // set for level of brightness and range of color
#define greenbright 75
#define reddim 127
#define greendim 38
#define indicatorPin 5

  thisHour = hour();
  thisDay = weekday()-1;
  if (thisHour >= pgm_read_dword_near(&pwrRangeArray[thisDay][PEAKSTART]) &&
    thisHour < pgm_read_dword_near(&pwrRangeArray[thisDay][PEAKEND]))
    rangemax = pgm_read_dword_near(&pwrRangeArray[thisDay][PEAKMAX]);
  else
    rangemax = pgm_read_dword_near(&pwrRangeArray[thisDay][OFFPEAKMAX]); // suck the value out of the table
  //  strcpy_P(general,PSTR("value from peak array is "));
  //  Serial.print(general);
  //  Serial.println(rangemax);
  x = (long)pwrlevel;                 //explicitly converting because I'm not sure
  i = (x * 100) / rangemax;           //calculate percentage of power range
  if (i > 100)                        //unlike television, don't go over 100%
    i=100;

  y = 1024 - analogRead(indicatorPin); //check how bright the room is
  //  Serial.println(y); //debugging
  if (y >500){
    redrange = reddim;
    greenrange = greendim;
  }
  else {
    redrange = redbright;
    greenrange = greenbright;
  }
  //red percentage is direct so leave it alone
  redpercent = i;
  //green is what's left over from 100%
  greenpercent = 100 - i;
  //so red will be a direct percentage of 255 (bright);
  indred = (redrange * redpercent) / 100; // the range of red
  indgreen = (greenrange * greenpercent) / 100; //and green's range
  // now since the leds are backwards reverse them
  indred = 255 - indred;
  indgreen = 255 - indgreen;
  analogWrite(greenPin, indgreen);
  analogWrite(redPin, indred);
}

// Time Server variables and routines
uint8 Time_ip[] = {
  192,168,0,204};     // Time machine
GETrequest getTime(Time_ip, 80, "TimeServer", "/time_t");

// Gets the current time from home time server and sets the Arduino clock
time_t gogetTime(){  // I used this routine to avoid crazy typecasting
  getTime.submit();
  return(0);         // zero means it failed, the routine below will set it properly
}
int i = 0;
char copy[100] = {
  0};

void timeData(char* data, int len) { //actual routine that does the work

  // Collect the data returned by the server
  if (len != 0) {
    strncat(copy,data,len);
    return;
  }
  i = 44;
  tNow=0;
  while( isdigit(copy[i]) ){
    tNow = tNow * 10 + (copy[i++] - '0');
  }
  setTime(tNow);
  strcpy_P(general, PSTR("time data sync complete"));
  Serial.println(general);
  copy[0] = '\0';
  i = 0;
  lastpowerCheck = now();   // reset the last power check just in case
  showTime();
}
//end of time server specifics

// Power monitor variables and routines
//
uint8 Power_ip[] = {
  192,168,0,200};     //Local Power monitor in the garage
GETrequest getPower(Power_ip, 80, "PowerMonitor", "/");

// Function handles data from power monitor
void powerData(char* data, int len) {
  int i, number;
  char* copy;

  // Print the data returned by the server
  // Note that the data is not null-terminated, may be broken up into smaller packets, and
  // includes the HTTP header.
  if (len == 0) return; // empty packet no action
  data += 19;           //skip the header
  len -= 19;
  strncpy(pachubeData, data, len);
  pachubeData[len]= 0;
  copy = data;
  LCDline1();
  strcpy_P(general, PSTR("                "));
  mySerial.print(general); //sixteen spaces to clear line
  LCDline1();
  number = atoi(copy);
  realPower = number;
  mySerial.print(number);         //Actual power usage gets displayed here.
  strcpy_P(general, PSTR(" Watts"));
  mySerial.print(general);
  colorLamp(number);              //set the light color
  lastpowerCheck = now();
  powerIndicator = 0;
  LCDgoto(15);
  mySerial.print(" ");
  //  Serial.println(pachubeData);   //out to serial for debugging
}
// end of power monitor specifics

//Pachube specific variables and routines
//
char hostName[] = "www.pachube.com\nX-PachubeApiKey: e24b69b27040f1ac44cb8d7a284e1f0c0c298f02c7e2cf18016d4c9a7f76fa42\nConnection: close";
char url[] = "/api/9511.csv?_method=put";  // test feed to try this out
// A request that POSTS data to Pachube
uint8 Pachube_ip[] = {
  173,203,98,29};       //Pachube data logging ip address
POSTrequest postPachube(Pachube_ip, 80, hostName, url, PachubefeedData);

void PachubeData(char* data, int len)
{
  //     while (len-- > 0) {           //debugging
  //      Serial.print(*(data++));
  //     }
}

/* format the data that will be put on Pachube */
void PachubefeedData()
{
  //build feed data for Pachube
  WiServer.print(pachubeData);  // Pass on data from Power Monitor
  //   Serial.println("pachube updated");
}
// end of Pachube specifics

void setup() {

  Serial.begin(57600);          //talk to it port
  Serial.println("Init...");
  //  pinMode(LCDrxPin, INPUT);        //serial for LCD
  pinMode(LCDtxPin, OUTPUT);
  mySerial.begin(9600);
  mySerial.print(0x7C, BYTE);   // Intensity to max
  mySerial.print(157, BYTE);
  delay(100);
  mySerial.print(0xFE, BYTE);   //command flag
  mySerial.print(0x01, BYTE);   //clear command.
  delay(100);
  strcpy_P(general, PSTR("Power Init..."));
  mySerial.print(general);    //let them know it's alive
  LCDline2();
  strcpy_P(general, PSTR("Time Init..."));
  mySerial.print(general);
  strcpy_P(general, PSTR("LCD Init..."));
  Serial.println(general);


  wdt_enable(WDTO_8S);          // I'm giving the network 8 seconds to set itself up
  // Initialize WiServer (no need to serve web pages)
  WiServer.init(NULL);

  wdt_reset();                  // made it, turn off the watchdog until things start working
  wdt_disable();
  WiServer.setIndicatorPins(5, 7);              //This could make it fun to monitor.
  //However, the Wishield uses 2,8,9,10,11,12,13
  //and I used 3, 4 for LCD display, that leaves 5,6,7 for
  //tx, rx web indicators.
  //  WiServer.enableVerboseMode(true);           //for debugging

  // Have the return data functions called when the servers respond
  getPower.setReturnFunc(powerData);       // data back from power monitor
  getTime.setReturnFunc(timeData);         // time server data handling
  postPachube.setReturnFunc(PachubeData);  // Pachube site return data
  setSyncInterval(15 * 60);                // update time every so often
  setSyncProvider(gogetTime);              // this will immediately go get the time
  while(timeStatus() == timeNotSet){
    delay(10);
    WiServer.server_task();
  }
  powerIndicator = 1;                      //flag power data as invalid
  pinMode(redPin, OUTPUT);                 // sets the pin as output for indicator LED
  pinMode(greenPin, OUTPUT);
  alloff();
  strcpy_P(general,PSTR("Setup Complete"));
  Serial.println(general);
}

boolean firsttime = true;

void loop(){

  if(firsttime == true){  // keep from running out of memory!!
    showMem();
    firsttime = false;
  }

  // Check if it's time to get a power update
  if ((second() % 10) == 0 && !getPower.isActive()) {  // every ten seconds if a request isn't already out
    getPower.submit();  
  }
  // it's been 40 seconds since the last power update, flag it as possibly invalid (other arduino died)
  if (((now() - lastpowerCheck) > 40) && (powerIndicator == 0))  {
    LCDgoto(15);
    mySerial.print("!");  //power data is invalid indicator
    powerIndicator = 1;
    strcpy_P(general, PSTR("lost power monitor"));
    Serial.println(general);
  }  
  // it's been 90 seconds since the last power update, Now we're going to reset the board
  if (((now() - lastpowerCheck) > 90) && powerIndicator == 1)  {
    LCDgoto(15);
    mySerial.print("*");  //power data is dead indicator
    strcpy_P(general, PSTR("Reboot"));
    Serial.println(general);
    wdt_enable(WDTO_8S);
    while(1){
    };  //hang up and wait for it
  }
 if((second() % 60) == 0 && timedisplaydone == 0) // since I'm only displaying minutes, update on minute boundary
  {
    showTime();
    timedisplaydone = 1; // only let it display once during the second
  }
  if ((second() % 60) == 1 )
    timedisplaydone = 0;   // let it display now keeps it from displaying multiple times during the second

  // Every so often update Pachube, but only if power is valid  
  if ((second() % 60 == 30) && !postPachube.isActive() && powerIndicator == 0)
  {
    postPachube.submit();
    //    Serial.println("Pachube updated");
  }
  // Run WiServer
  WiServer.server_task();
}

New Implementation


May 19, 2011:  I had problems with the WiShield and AsyncLabs has stopped producing them.  Shucks, but  fortunately I have already implemented a couple of devices with XBee communications.  Naturally, I went straight for the XBee craft box and implemented an XBee solution for the Power Display.  The ability to update my Pachube feed was already incorporated in the House Controller and time is provided by my House Clock, so who needs the internet?

Since the start of this project I have learned a lot and discovered a bunch of shortcuts and techniques.  The TimeAlarm library that is not mentioned or used very much is especially useful.  Those things that take a few seconds (or hours) to do can be set up and checked on with timers and alarms.  For example, to blink an LED, turn it on and then set a timer for a second from now to turn it off.  One can update a display with a recurring timer set for 5 seconds to update a display and send messages every 30 seconds to update data.  Just set the timer, write the call back routine and away you go.  No counting milliseconds or confusing code wrapped around counters, it just works the way you would expect it to.  You have the benefit of being able to set an alarm a month away if you want.  So, the new code for the Power Display relies heavily on the alarms.  I also rely heavily on NewSoftSerial.  This code just isn't properly understood and used.  I still don't know all the things I can do with it, but I have my XBee connected to a software serial port as well as the serial LCD display.  They work great.

I also discovered tabs in the Arduino IDE.  Yes, I knew they were there, but it seemed like an unnecessary complication when I was writing code.  Then, when working on the House Controller, the script got so long I couldn't find things, I added a tab.  Then another one, etc.  I can separate sections nicely and switch from one to the other when chasing bugs.  So, the Power Display uses tabs, that's why there are two sections of code below; one for the main section and one for XBee handling.

The new sketch is below and I'm keeping the old one above both for a comparison and because it has some cool stuff in it regarding Wifi updates to Pachube, overcoming weak network problems and such.  I like the new XBee enabled Power Display a lot more and I can display the inside and outside temperature using the various sensors around the house.  I repurposed the LEDs that used to indicate ethernet activity to report the incoming data from the XBee network.  The top LED shows a power level packet, the middle a time packet and the bottom one a packet from the House Controller.  There were a few reasons for this.  There needs to be something to show activity to let you know it's alive.  Troubleshooting.  And I had the darn things mounted and taking them out would leave holes!

Update Sept 10, 2011:  I found out that NewsoftSerial uses interrupts and that conflicts with the led indicator that goes from red to green depending on usage.  It wasn't much of a problem, but I wanted to redo that code anyway since I wasn't sure I had done it correctly.  I used the map() function to calculate the values and moved one of the XBee pins.  Works nicely now; at least, until the next bug or change.  The newest sketch is shown below

New Sketch Main Section

/*
 * Sketch to grab data from my power monitor and display real
 * power and satellite derived time on a serial enabled LCD.
 * Credits:
 * The idea for the color display was totally stolen from Alan Meany
 * http://www.alanmeany.net/ambientknowledge.html  I'm sure I didn't use the same techniques as he did,
 * but the idea came from there.
 *
 * Open source rules!
 */

// This version has changes for using map() to handle the indicator led
// the indicator led was confusing because it works from 255-off to 0-full on.
// And, the green and red work backwards to each other to change color from green
// (low usage) to red (high usage).  Additionally,the green led grossly
// overpowers the red one so it was really confusing to decide how to handle
// this.  Especially when I want to dim it when the room lights are out.
// Otherwise, it looks like a color changing searchlight.
//
// I also moved the XBee SoftwareSerial port to stop conflict with
// pwm pins

#include <Time.h>
#include <TimeAlarms.h>
#include <SoftwareSerial.h>
#include <avr/wdt.h>
#include <avr/pgmspace.h>

time_t tNow;
float realPower,apparentPower,powerFactor,rmsCurrent,rmsVoltage,frequency;
int insideTemp, outsideTemp;

char Dbuf[50];
char Dbuf2[50];
char xbeeIn[100]; // accumulated XBee input

#define LCDrxPin 3
#define LCDtxPin 4
SoftwareSerial lcdSerial = SoftwareSerial(LCDrxPin, LCDtxPin);
#define xbeeRxPin 2
#define xbeeTxPin 12
SoftwareSerial xbeeSerial = SoftwareSerial(xbeeRxPin, xbeeTxPin);
#define powerLed 7
#define timeLed 8
#define statusLed 9

void showMem(){
  uint8_t * heapptr, * stackptr;
 
  strcpy_P(Dbuf,PSTR("Mem = "));
  Serial.print(Dbuf);
  stackptr = (uint8_t *)malloc(4);   // use stackptr temporarily
  heapptr = stackptr;                // save value of heap pointer
  free(stackptr);                    // free up the memory again (sets stackptr to 0)
  stackptr =  (uint8_t *)(SP);       // save value of stack pointer
  Serial.println(stackptr - heapptr);
}

//
//  Sparkfun LCD display routines
//
void LCDgoto(int position) { //position = line 1: 0-15, line 2: 16-31, 31+ defaults back to 0
  if (position<16)
  {
    lcdSerial.print(0xFE, BYTE);              //command flag
    lcdSerial.print((position+128), BYTE);    //position
  }
  else if (position<32){
    lcdSerial.print(0xFE, BYTE);   //command flag
    lcdSerial.print((position+48+128), BYTE);          //position
  }
  else
  {
    LCDgoto(0);
  }
}

void LCDline1()
{
  lcdSerial.print(0xFE, BYTE);   //command flag
  lcdSerial.print(128, BYTE);    //position to line 1
}

void LCDline2()
{
  lcdSerial.print(0xFE, BYTE);   //command flag
  lcdSerial.print(192, BYTE);    //position to line 2
}

void showPower(){
 
  LCDline1();
  strcpy_P(Dbuf, PSTR("                "));
  lcdSerial.print(Dbuf); //sixteen spaces to clear line
  LCDline1();
  strcpy_P(Dbuf2,PSTR("%dW %dF %dF"));
  sprintf(Dbuf,Dbuf2,(int)round(realPower), outsideTemp, insideTemp);
  lcdSerial.print(Dbuf);         //Actual power usage gets displayed here.
  colorLamp((int)round(realPower));              //set the light color
}

// Displays the time on the LCD screen
void showTime(){
 
  tNow = now();
  LCDline2();
  strcpy_P(Dbuf, PSTR("                "));  //clear out line 2
  lcdSerial.print(Dbuf);
  strcpy_P(Dbuf2,PSTR("%2d:%02d %d/%d/%02d"));
  sprintf(Dbuf,Dbuf2,hour(tNow),minute(tNow),month(tNow),day(tNow),year(tNow)-2000);
  LCDline2();
  lcdSerial.print(Dbuf);
}

// Indicator light handling
// Not using the Blue LED
#define greenPin 3      // LED connected to these pins
#define redPin 6
#define OFFPEAKMAX 0
#define PEAKMAX 1
#define PEAKSTART 2
#define PEAKEND 3
// entries are: off peak max, peak max, peak start hour, peak end hour
prog_int32_t pwrRangeArray [7][4] PROGMEM = {
  {
    10000,10000,12,19    }
  ,   //Sunday no peak period
  {
    10000,2000,12,19    }
  ,    //Monday
  {
    10000,1350,12,19    }
  ,    //Tuesday
  {
    10000,2000,12,19    }
  ,    //Wednesday
  {
    10000,2000,12,19    }
  ,    //Thursday
  {
    10000,2000,12,19    }
  ,    //Friday
  {
    10000,10000,12,19    }
}; //Saturday no peak period

void alloff()
{
  analogWrite(redPin, 255);
  analogWrite(greenPin, 255);
}

void colorLamp(int pwrlevel)
{

  int indred, indgreen;
  int thisHour, thisDay, redrange, greenrange, redpercent, greenpercent;
  long i, x, y, rangemax;
#define redbright 0            // set for level of brightness and range of color
#define greenbright 225
#define reddim 127
#define greendim 240
#define indicatorPin 5         // sample for ambient light in room

  thisHour = hour();
  thisDay = weekday()-1;
  if (thisHour >= pgm_read_dword_near(&pwrRangeArray[thisDay][PEAKSTART]) &&
    thisHour < pgm_read_dword_near(&pwrRangeArray[thisDay][PEAKEND]))
    rangemax = pgm_read_dword_near(&pwrRangeArray[thisDay][PEAKMAX]);
  else
    rangemax = pgm_read_dword_near(&pwrRangeArray[thisDay][OFFPEAKMAX]); // suck the value out of the table
  //  strcpy_P(Dbuf,PSTR("value from peak array is "));
  //  Serial.print(Dbuf);
  //  Serial.println(rangemax);
  x = (long)pwrlevel;                 //explicitly converting because I'm not sure
  x = x > rangemax ? rangemax : x;     //don't bother with over the max stuff

  y = 1024 - analogRead(indicatorPin); //check how bright the room is
  //  Serial.println(y); //debugging
  if (y >500){                        // to dim the led when the room lights are off
    redrange = reddim;
    greenrange = greendim;
//    Serial.print("Dim, ");
  }
  else {
    redrange = redbright;
    greenrange = greenbright;
//    Serial.print("Bright, ");
  }
  indred=map(x,rangemax,0,redrange,255);
  analogWrite(redPin,indred);
  indgreen=map(x,0,rangemax,greenrange,255);
  analogWrite(greenPin,indgreen);
//  Serial.print(x);
//  Serial.print(", ");
//  Serial.print(indred);
//  Serial.print(", ");
//  Serial.println(indgreen);
}

void setup() {

  Serial.begin(57600);          //talk to it port
  Serial.println("Init...");
  // color power indicator
  pinMode(redPin, OUTPUT);      // sets the pin as output for indicator LED
  pinMode(greenPin, OUTPUT);
  alloff();
  pinMode(powerLed, OUTPUT);    // indicator LEDs to tell me that data
  digitalWrite(powerLed, LOW);  // is coming in over XBee network
  pinMode(timeLed, OUTPUT);
  digitalWrite(powerLed, LOW);
  pinMode(statusLed, OUTPUT);
  digitalWrite(powerLed, LOW);
 
  pinMode(LCDtxPin, OUTPUT);     // The output pin for serial LCD
  lcdSerial.begin(9600);
  lcdSerial.print(0x7C, BYTE);   // Intensity to max
  lcdSerial.print(157, BYTE);
  delay(100);
  lcdSerial.print(0xFE, BYTE);   //command flag
  lcdSerial.print(0x01, BYTE);   //clear command.
  delay(100);
  strcpy_P(Dbuf, PSTR("Data Init..."));
  lcdSerial.print(Dbuf);    //let them know it's alive
  LCDline2();
  strcpy_P(Dbuf, PSTR("Time Init..."));
  lcdSerial.print(Dbuf);
  strcpy_P(Dbuf, PSTR("LCD Init..."));
  Serial.println(Dbuf);

//  wdt_enable(WDTO_8S);          //
//  wdt_reset();                  //
//  wdt_disable();

  xbeeSerial.begin(9600);
  strcpy_P(Dbuf,PSTR("Setup Complete"));
  Serial.println(Dbuf);
}

void initTimers(){
  Alarm.timerRepeat(60, showTime);   // update lcd clock every minute
  Alarm.timerRepeat(5, showPower);   // update power display every few seconds
  Alarm.timerRepeat(30, houseStatus); // this gets the outside and inside temp from controller
  Alarm.timerRepeat(15*60, showMem);  //keeping tabs on memory usage
}
 
boolean firsttime = true;
boolean alarmsSet = false;

void loop(){

  if(firsttime == true){  // keep from running out of memory!!
    showMem();
    firsttime = false;
    memset(xbeeIn,0,sizeof(xbeeIn));
  }
  if (timeStatus() != timeNotSet){
    tNow = now(); // just to be sure the time routines are ok
    if (!alarmsSet){
      initTimers();
      alarmsSet = true;
      Serial.println("Timers set up");
      showTime();
      showPower();
      houseStatus();
    }
    Alarm.delay(0); // and the alarm routines as well
  }
  xbeeSerial.listen();
  while (xbeeSerial.available()){
    checkXbee();
  }
}


XBee Handling

#define WaitForFrameStart 1
#define LengthHighByte 2
#define LengthLowByte 3
#define PayloadCapture 4
#define CheckChecksum 5

uint8_t received;
int framestatus = WaitForFrameStart;
byte checksum;
char* payloadbuffer;
int datalength = 0;
int savedDataLength;

const char statusCommand[] PROGMEM = {
  0x10,0x01,0x00,0x00,0x00,0x00,
  0x00,0x00,0xff,0xff,0xff,0xfe,0x00,0x00,'?','\r'};


void sendXbee_P(const char* command, int length){
  byte checksum = 0;
  int i;
  Dbuf[0] = 0x7e;  // begin frame
  Dbuf[1] = length >>8;
  Dbuf[2] = length & 0xff;
  // copy command into buffer calculating checksum at the same time
  for(i = 0; i<length; i++){
    char c = pgm_read_byte(command + i); // can't use command[i]
    Dbuf[i+3] = c;
    checksum += c;
  }
  Dbuf[i+3] = 0xff - checksum;
  Serial.println("Status Request");
  for(i = 0; i<length + 4; i++){
    xbeeSerial.print(Dbuf[i]);
    //    printByteData(Dbuf[i]);
    //    Serial.print(" ");
  }
  //  Serial.println();
  Serial.println("Request Sent");
}

void checkXbee(){

  if (xbeeSerial.available()) {
    received = (uint8_t)xbeeSerial.read();
    switch( framestatus ){
    case WaitForFrameStart:
      if(received != 0x7E)
        break;
      else {
        framestatus = LengthHighByte;
        //        Serial.print("Frame start, ");
        checksum = 0;
        payloadbuffer = xbeeIn;
      }
      break;
    case LengthHighByte:
      datalength = received;
      framestatus = LengthLowByte;
      break;
    case LengthLowByte:
      datalength = (datalength * 256) + received;
      savedDataLength = datalength;
      //      Serial.print("length ");
      //      Serial.print(datalength);
      //      Serial.print(", ");
      framestatus = PayloadCapture;
      break;
    case PayloadCapture:
      if(payloadbuffer >= xbeeIn + sizeof(xbeeIn)){
        strcpy_P(Dbuf,PSTR("Reached end of buffer, ignore packet"));
        Serial.println(Dbuf);
        framestatus = WaitForFrameStart;
        break;
      }
      *payloadbuffer++ = received;
      //      printByteData(received);
      //      Serial.print(" ");
      datalength--;
      checksum += received;
      if (datalength == 0){
        framestatus = CheckChecksum;
        *payloadbuffer = '\0';
      }
      break;
    case CheckChecksum:
      checksum += received;
      if (checksum == 0xFF){
        //        Serial.println("Checksum valid.");
        xbeeFrameDecode(xbeeIn, savedDataLength);
      }
      else {
        strcpy_P(Dbuf,PSTR("Checksum Invalid ***********"));
        Serial.println(Dbuf);
      }
      framestatus = WaitForFrameStart;
      break;
    default:
      break;
    }
  }
}

void xbeeFrameDecode(char* buffer, int length){

  switch ( *buffer){
  case 0x90:
    {
      //     Serial.println("Receive Data Frame");
      buffer++;                //skip over the frame type
      length--;
      //     Serial.print("Source 64 bit address: ");
      for(int i=0; i<8; i++){  //address the frame came from
        //       printByteData(*buffer);
        //       if (i == 3)
        //         Serial.print(" ");
        buffer++;
        length--;
      }
      //     Serial.println();
      //     Serial.print("Source 16 bit network address: ");
      for(int i=0; i<2; i++){  //16 bit network address the frame came from
        //       printByteData(*buffer);
        buffer++;
        length--;
      }
      //     Serial.println();
      //     Serial.print("Receive options: ");  // options byte
      //     printByteData(*buffer);
      //     Serial.println();
      buffer++;
      length--;
      //     char* tmp = buffer;
      //     while(length-- > 0){ //assuming the actual data is ascii
      //       Serial.print(*tmp++);
      //     }
      processXbee(buffer);
      break;
    }
  case 0x92:
    {
      Serial.println("Data Sample");
      break;
    }
  case 0x88:
    {
      Serial.println("AT Command Response Frame");
      break;
    }
  case 0x8B:
    {
      Serial.println("Transmit Status Frame");
      break;
    }
  default:
    {
      Serial.println("Unimplemented Frame Type");
      break;
    }
  }
}

void printByteData(uint8_t Byte){
  Serial.print((uint8_t)Byte >> 4, HEX);
  Serial.print((uint8_t)Byte & 0x0f, HEX);
}

void processXbee(char* buf){
  time_t temptime = 0;
  char* data;

  //  Serial.println(buf);
  if((strstr_P(buf,PSTR("Time"))) != 0){
    Serial.println("Time Data");
    digitalWrite(timeLed,HIGH);
    Alarm.timerOnce(1,timeLedOff);
    data = strtok_r(buf, ",", &buf);  // skip to the actual t_time value
    data = strtok_r(0, ",", &buf),   // get the t_time variable
    temptime = atol(data);
    if(abs(temptime - now()) >= 5){  // only if it matters
      Serial.println("Setting Time");
      setTime(temptime);
    }
  }
  else if (strstr_P(buf,PSTR("Power")) != 0){
    Serial.println("Power Data");
    digitalWrite(powerLed,HIGH);
    Alarm.timerOnce(1,powerLedOff);
    data = strtok_r(buf, ",", &buf);   // skip to actual power data
    realPower = atof(strtok_r(0, ",", &buf));
    apparentPower = atof(strtok_r(0, ",", &buf));
    powerFactor = atof(strtok_r(0, ",", &buf));
    rmsVoltage = atof(strtok_r(0, ",", &buf));  //reverse these after fixing monitor !!
    rmsCurrent = atof(strtok_r(0, ",", &buf));
    frequency = atof(strtok_r(0, ",", &buf));
  }
  else if (strstr_P(buf,PSTR("Pool")) != 0){
    Serial.println("Pool Data");
  }
  else if (strstr_P(buf,PSTR("Status")) != 0){
    Serial.println("Status Data");
    digitalWrite(statusLed,HIGH);
    Alarm.timerOnce(1,statusLedOff);
    data = strtok_r(buf, ",", &buf);   // skip to power data
    data = strtok_r(buf, ",", &buf);   // skip to time data
    data = strtok_r(buf, ",", &buf);   // skip to outside temp data
    insideTemp= atoi(strtok_r(0, ",", &buf));
    outsideTemp= atoi(strtok_r(0, ",", &buf));
  }
}

void houseStatus(){
  sendXbee_P(statusCommand, sizeof(statusCommand));
}

void powerLedOff(){
  digitalWrite(powerLed,LOW);
}
void timeLedOff(){
  digitalWrite(timeLed,LOW);
}
void statusLedOff(){
  digitalWrite(statusLed,LOW);
}

3 comments:

  1. Nice Project. I would design with DSPIC30F , but the topology will be same :)

    ReplyDelete
  2. I know it's been quite a number of years since you did this. Can you post an update? I was wondering if you may have switched over to a Raspberry Pi?

    ReplyDelete
    Replies
    1. The power display is still running exactly the same hardware as before. It works well and I have learned to look at it as I walk by.

      However, I have raspberry Pi controller, weather computer, and graphing procesor. I NAS database server, arduino room temperature monitors, and a crap load of other stuff.

      I even monitor he house status from a laptop and tablet.

      The problem, of course, is that I have so many different kinds of hardware running in the house that it's hard keeping track of it.

      The reason I've been posting very little is that I got my silly self involved in local politics, and that is literally eating up all my time.

      Delete