Greenhouse automation

So I'm back after being long gone. Sick of internships hehehe
Anyways a friend just asked me to build him a greenhouse.
Now it seemed cool to automate it with Arduino.

So for some requirements:
*Measure temperature, humidity and soil moisture
*Control lights, fan and watersupply
*Simple overview of current conditions
*Graph conditions in Excel

Measuring temperature and humidity is simple with an SHT71 sensor
Controlling with a few relays
I'll be pulling log data over the serial connection and using Gobetwino to store them in as a .csv file

I had most schematics in my head but I drew them up for you guys.
Here are the two sensors, and yes the nail trick I "borrowed" from the Garduino instructable.

Here is the control panel which shows power, status of temperature, humidity and soil moistness. Whether lights, fan or pump are on and mains switches for lights, fan and pump.

And the relay board, and yes I have forgotten about the diodes in this drawing.

I also came up with some code

#include <Sensirion.h>
#include <Time.h>

//Temperature and Humidity sensor
const uint8_t dataPin  =  2;
const uint8_t clockPin =  3;

float temperature;
float humidity;
float dewpoint;

Sensirion tempSensor = Sensirion(dataPin, clockPin);

//Temperature and Humidity interval time
long previousMillis = 0;
long interval = 300000;

//Soil moisture
int moistureSensor = 0;
int moisture_val;

//Serial stuff
char buffer[5];

//Relay output pins
const int lightPin = 4;
const int waterPin = 5;
const int fanPin = 6;

//Status LED pins
const int tempgoodPin = 7;
const int tempbadPin = 8;
const int soilgoodPin = 9;
const int soilbadPin = 10;
const int humgoodPin = 11;
const int humbadPin = 12;

////Lightstatus
int lightstatus = 0;
int lastlightstatus = 0;

//Fanstatus
int fanstatus = 0;
int lastfanstatus = 0;

//Watering
int watertime = 1000; //In milliseconds
int waterstatus = 0;
int lastwaterstatus = 0;

void setup()
{
  Serial.begin(9600);
  setTime(20,7,0,17,5,2011);
  pinMode(lightPin, OUTPUT);
  pinMode(waterPin, OUTPUT);
  pinMode(fanPin, OUTPUT);
  pinMode(tempgoodPin, OUTPUT);
  pinMode(tempbadPin, OUTPUT);
  pinMode(soilgoodPin, OUTPUT);
  pinMode(soilbadPin, OUTPUT);
  pinMode(humgoodPin, OUTPUT);
  pinMode(humbadPin, OUTPUT);
}

void loop()
{
  unsigned long currentMillis = millis();
  time_t t = now();
  
  //Check Temperature.... and report every 5 minutes
  if(currentMillis - previousMillis > interval) {
    previousMillis = currentMillis;
    
    tempSensor.measure(&temperature, &humidity, &dewpoint);

    moisture_val = analogRead(moistureSensor);
    
    Serial.print("Temperature: ");
    Serial.print(temperature);
    Serial.print(" C, Humidity: ");
    Serial.print(humidity);
    Serial.print(" %, Dewpoint: ");
    Serial.print(dewpoint);
    Serial.print(" C, Soil Moisture: ");
    Serial.println(moisture_val);
    Serial.print("#S|TEMPLOG|[");
    Serial.print(itoa((temperature), buffer, 10));
    Serial.print(";");
    Serial.print(itoa((humidity), buffer, 10));
    Serial.print(";");
    Serial.print(itoa((dewpoint), buffer, 10));
    Serial.print(";");
    Serial.print(itoa((moisture_val), buffer, 10));
    Serial.println("]#");
  }
  
  
  /////Light trigger
  if(0 <= hour(t)) {
    if(18 > hour(t)) {
    lightstatus = 1;
    }
  };
  if(18 <= hour(t)) {
    if(24 >= second(t)) {
    lightstatus = 0;
    }
  };
  //Light compare, set status and report
  if(lightstatus != lastlightstatus) {
    if(lightstatus == 1) {
      Serial.println("#S|ACTIONLOG|[Light On]#");
    };
    if(lightstatus == 0) {
      Serial.println("#S|ACTIONLOG|[Light Off]#");
    };
    digitalWrite(lightPin, lightstatus);
    lastlightstatus = lightstatus;
  }
  
  
  
  /////Fan trigger
  if(temperature <= 25) {
    fanstatus = 0;
  }
  if(humidity <= 60) {
    fanstatus = 0;
  }
  if(temperature > 28) {
    fanstatus = 1;
  }
  if(humidity > 80) {
    fanstatus = 1;
  }
  //Fan compare, set status and report
  if(fanstatus != lastfanstatus) {
    if(fanstatus == 1) {
      Serial.println("#S|ACTIONLOG|[Fan On]#");
    }
    if(fanstatus == 0) {
      Serial.println("#S|ACTIONLOG|[Fan Off]#");
    }
    digitalWrite(fanPin, fanstatus);
    lastfanstatus = fanstatus;
  }
  
  
  /////Water trigger
  if(moisture_val > 1000) {
    waterstatus = 0;
  }
  if(moisture_val < 850) {
    waterstatus = 1;
  }
  //Fan compare, set status and report
  if(waterstatus != lastwaterstatus) {
    if(waterstatus == 1) {
      Serial.println("#S|ACTIONLOG|[Pump On]#");
      digitalWrite(waterPin, waterstatus);
        delay(watertime);
        waterstatus = 0;
        Serial.println("#S|ACTIONLOG|[Pump Off]#");
        digitalWrite(waterPin, waterstatus);
        lastwaterstatus = waterstatus;
      }
    }
  
  
  /////Status LEDs
  //Temperature
  if(temperature < 22) {
    digitalWrite (tempgoodPin, LOW);
    digitalWrite (tempbadPin, HIGH);
  }
  else if(temperature > 28) {
    digitalWrite (tempgoodPin, LOW);
    digitalWrite (tempbadPin, HIGH);
  }
  else {
    digitalWrite (tempbadPin, LOW);
    digitalWrite (tempgoodPin, HIGH);
  }
  //Humidity
  if(humidity < 60) {
    digitalWrite (humgoodPin, LOW);
    digitalWrite (humbadPin, HIGH);
  }
  else if(humidity > 80) {
    digitalWrite (humgoodPin, LOW);
    digitalWrite (humbadPin, HIGH);
  }
  else {
    digitalWrite (humbadPin, LOW);
    digitalWrite (humgoodPin, HIGH);
  }
  //Soil
  if(moisture_val < 850) {
    digitalWrite (soilgoodPin, LOW);
    digitalWrite (soilbadPin, HIGH);
  }
  else if(moisture_val > 1000) {
    digitalWrite (soilgoodPin, LOW);
    digitalWrite (soilbadPin, HIGH);
  }
  else {
    digitalWrite (soilbadPin, LOW);
    digitalWrite (soilgoodPin, HIGH);
  }
}

I'm going to order all the parts this week so I'll be keeping you guys posted.
And ofcourse all suggestions are welcome!!

Hi SpencerH,

Do you really have 3 phase power or are the three "mains" wired the same active.

6A @ 240V means huge motors, are they really that big?

I gather they are DPDT relays and the "to CP" contacts are just to show they are working with the other pole doing the pump/whatever control.


Rob

Hey,
It is just two phase mains. And chose the 6A fuse as the max current for the relay is 7A. But I can always swap the fuse for a smaller one as the lights will probably draw a maximum of 1A. And the switches on the control panel are just a sort of kill switch just in case something like the water pump freaks out.

Just another quick update,
it seems my buddy wanted to do a bit more than I posted yesterday including a humidifier and some error reporting on the control panel.
The error stuff I'm still working on but due to the lack of left over pins I had to use a shift register to do the control panel leds.
Besides that I cleaned up the code a little bit. Actual build pictures are coming soon, ordered the electronics and starting the physical build this week.

#include <Sensirion.h>
#include <Time.h>

//Temperature and Humidity sensor
const uint8_t dataPin  =  2;
const uint8_t clockPin =  3;

float temperature;
float humidity;
float dewpoint;

Sensirion tempSensor = Sensirion(dataPin, clockPin);

//Temperature and Humidity interval time
long previousMillis = 0;
long interval = 300000;

//Soil moisture sensor
int moistureSensor = A0;
int moisture_val;

//Serial stuff
char buffer[5];

//Relay output pins
const int lightPin = 4;
const int waterPin = 5;
const int fanPin = 6;
const int humPin = 7;

//Control panel shift register pins and ON / OFF data string
int stats[15];
int previousstats[15];
const int data = 8;
const int clock = 9;
const int latch = 10;

//Light status
int lightstatus = 0;
int lastlightstatus = 0;

//Fan status
int fanstatus = 0;
int lastfanstatus = 0;

//Humidifier status
int humtime = 1000; //In milliseconds
int humstatus = 0;
int lasthumstatus = 0;

//Watering status
int watertime = 1000; //In milliseconds
int waterstatus = 0;
int lastwaterstatus = 0;

void setup()
{
  Serial.begin(9600);
  setTime(20,7,0,17,5,2011);
  pinMode(lightPin, OUTPUT);
  pinMode(waterPin, OUTPUT);
  pinMode(fanPin, OUTPUT);
  pinMode(humPin, OUTPUT);
  pinMode(data, OUTPUT);
  pinMode(clock, OUTPUT);
  pinMode(latch, OUTPUT);
}

void loop()
{
  unsigned long currentMillis = millis();
  time_t t = now();
  
  //Check Temperature.... and report every 5 minutes
  if(currentMillis - previousMillis > interval) {
    previousMillis = currentMillis;
    
    tempSensor.measure(&temperature, &humidity, &dewpoint);

    moisture_val = analogRead(moistureSensor);
    
    Serial.print("Temperature: ");
    Serial.print(temperature);
    Serial.print(" C, Humidity: ");
    Serial.print(humidity);
    Serial.print(" %, Dewpoint: ");
    Serial.print(dewpoint);
    Serial.print(" C, Soil Moisture: ");
    Serial.println(moisture_val);
    Serial.print("#S|TEMPLOG|[");
    Serial.print(itoa((temperature), buffer, 10));
    Serial.print(";");
    Serial.print(itoa((humidity), buffer, 10));
    Serial.print(";");
    Serial.print(itoa((dewpoint), buffer, 10));
    Serial.print(";");
    Serial.print(itoa((moisture_val), buffer, 10));
    Serial.println("]#");
  }
  
  
  /////Light trigger
  if((0 <= hour(t)) && (18 > hour(t))) { ///Between which hours on
    lightstatus = 1;
  }
  if((18 <= hour(t)) && (24 >= hour(t))) { ///Between which hours off
    lightstatus = 0;
  }
  //Light compare, set status and report
  if(lightstatus != lastlightstatus) {
    if(lightstatus == 1) {
      Serial.println("#S|ACTIONLOG|[Light On]#");
    }
    if(lightstatus == 0) {
      Serial.println("#S|ACTIONLOG|[Light Off]#");
    }
    digitalWrite(lightPin, lightstatus);
    lastlightstatus = lightstatus;
  }
  
  
  
  /////Fan trigger
  if(temperature <= 25 || humidity <= 60) { ///Turn off threshold
    fanstatus = 0;
  }
  if(temperature > 28 || humidity > 80) { ///Turn on threshold
    fanstatus = 1;
  }
  //Fan compare, set status and report
  if(fanstatus != lastfanstatus) {
    if(fanstatus == 1) {
      Serial.println("#S|ACTIONLOG|[Fan On]#");
    }
    if(fanstatus == 0) {
      Serial.println("#S|ACTIONLOG|[Fan Off]#");
    }
    digitalWrite(fanPin, fanstatus);
    lastfanstatus = fanstatus;
  }
  
  
  
  /////Water trigger
  if(moisture_val > 1000) { ///Turn off threshold
    waterstatus = 0;
  }
  if(moisture_val < 850) { ///Turn on threshold
    waterstatus = 1;
  }
  //Pump compare, set status and report
  if(waterstatus != lastwaterstatus) {
    if(waterstatus == 1) {
      Serial.println("#S|ACTIONLOG|[Pump On]#");
      digitalWrite(waterPin, waterstatus);
        delay(watertime);
        waterstatus = 0;
        Serial.println("#S|ACTIONLOG|[Pump Off]#");
        digitalWrite(waterPin, waterstatus);
        lastwaterstatus = waterstatus;
      }
   }
  
  
  
  ////Humidifier trigger
  if(humidity > 70) { ///Turn off threshold
    humstatus = 0;
  }
  if(humidity < 50) { ///Turn on threshold
    humstatus = 1;
  }
  //Humidifier compare, set status and report
  if(humstatus != lasthumstatus) {
    if(waterstatus == 1) {
      Serial.println("#S|ACTIONLOG|[Humidifier On]#");
      digitalWrite(humPin, humstatus);
        delay(humtime);
        humstatus = 0;
        Serial.println("#S|ACTIONLOG|[Humidifier Off]#");
        digitalWrite(humPin, humstatus);
        lasthumstatus = humstatus;
      }
   }
   
   
   
  /////Status LEDs
  ////These store the ON and OFF data in the 'stats' string to be used when shifting out
  //Temperature
  if((temperature < 22) || (temperature > 28)) {
    stats[2] = 0;
    stats[3] = 1;
  }
  else {
    stats[2] = 1;
    stats[3] = 0;
  }
  //Humidity
  if((humidity < 60) || (humidity > 80)) {
    stats[4] = 0;
    stats[5] = 1;
  }
  else {
    stats[4] = 1;
    stats[5] = 0;
  }
  //Soil
  if((moisture_val < 850) || (moisture_val > 1000)) {
    stats[6] = 0;
    stats[7] = 1;
  }
  else {
    stats[6] = 1;
    stats[7] = 0;
  }
  ////Checks if update to control panel is needed
  //And copies 'stats' string charachter by character to 'previousstats'
  if(stats != previousstats) {
    led();
    for(int z = 1; z<15; z++) {
      previousstats[z] = stats[z];
    }
  }
  
  
}


////Changes stats string to bits and shifts them out
void led() {
  byte bitBuffer1 = 0;
  bitWrite(bitBuffer1, 1, stats[1]);
  bitWrite(bitBuffer1, 2, stats[2]);
  bitWrite(bitBuffer1, 3, stats[3]);
  bitWrite(bitBuffer1, 4, stats[4]);
  bitWrite(bitBuffer1, 5, stats[5]);
  bitWrite(bitBuffer1, 6, stats[6]);
  bitWrite(bitBuffer1, 7, stats[7]);
  byte bitBuffer2 = 0;
  bitWrite(bitBuffer2, 1, stats[8]);
  bitWrite(bitBuffer2, 2, stats[9]);
  bitWrite(bitBuffer2, 3, stats[10]);
  bitWrite(bitBuffer2, 4, stats[11]);
  bitWrite(bitBuffer2, 5, stats[12]);
  bitWrite(bitBuffer2, 6, stats[13]);
  bitWrite(bitBuffer2, 7, stats[14]);
  digitalWrite(latch, LOW);
  shiftOut(data, clock, MSBFIRST, bitBuffer2);
  shiftOut(data, clock, MSBFIRST, bitBuffer1);
  digitalWrite(latch, HIGH);
}

And as always all suggestions are welcome

 /////Light trigger
  if((0 <= hour(t)) && (18 > hour(t))) { ///Between which hours on
    lightstatus = 1;
  }
  if((18 <= hour(t)) && (24 >= hour(t))) { ///Between which hours off
    lightstatus = 0;
  }

could be

 /////Light trigger
  lightstatus = hour(t) < 18 ? 1 : 0;
 if(stats != previousstats) {

This compares the addresses of the two arrays, they will never be ==

As they are arrays of binary numbers you have to use memcmp() to compare them.

Then you can use memcpy() to move one into the other.


Rob

I think I've trimmed some more fat

   byte stats;
  byte previousstats;
   
   #define TEMP_RED_BIT		B00000001
   #define TEMP_GREEN_BIT	B00000010
   #define HUMID_RED_BIT	B00000100
   #define HUMID_GREEN_BIT	B00001000
   #define MOIST_RED_BIT	B00010000
   #define MOIST_GREEN_BIT	B00100000
   
   
  stats = 0;
  if((temperature < 22) || (temperature > 28)) {
    stats |= TEMP_GREEN_BIT   // green on
  } else {
    stats |= TEMP_RED_BIT;    // red on
  }
  //Humidity
  if((humidity < 60) || (humidity > 80)) {
    stats |= HUMID_GREEN_BIT   // green on
  } else {
    stats |= HUMID_RED_BIT;    // red on
  }
  //Soil
  if((moisture_val < 850) || (moisture_val > 1000)) {
    stats |= MOIST_GREEN_BIT   // green on
  } else {
    stats |= MOIST_RED_BIT;    // red on
  }
 
  ////Checks if update to control panel is needed
  if(stats != previousstats) {
	digitalWrite(latch, LOW);
	shiftOut(data, clock, MSBFIRST, stats);  // don't need the led() function any more
	digitalWrite(latch, HIGH);
        previousstats = stats;
  }

You could also use the same arrangement but change the #defines to bit numbers and have bitWrites() instead of the &= and |= with bit patterns.


Rob

Hi Rob,

This is a really cool project. I wanted to share with you some arduino code that I posted that lets you relay data like this to google app engine. I have a whole open source project going that you could use to get this data onto spreadsheets, android etc.

Here is the arduino

http://nimbits.blogspot.com/2010/11/data-in-connect-arduino-to-cloud.html

Here is the web site: www.nimbits.com

A lot of guys i know are using it to automate greenhouses

Ben

Thanks for the links,

I'm experienced in Wed dev and also embedded micros but have never tied the two together and that blog/tute gives me a better idea of how to do it.

I'd like to do this some day before too long, at present I'm designing a monitoring and control network (www.busnet.robgray.com) and that is taking all my time and will for a while I suspect.

When it's running I'd like to be able to share data on the web like that.


Rob

Hey Ben,
I was also thinking about doing that but as I don't have an ehternet shield and my friend didn't want to spend more money when he is only gonna look at the data at home.
Anyways that was my first goal, maybe even intergrate it with google charts but as it wasn't needed and costs more that option was deleted.

So to get back to the project, I redesigned the circuit to use a pair of shift registers for the control panel and due to that I decided to give the panel it's own voltage regulator so I wouldn't bother the Arduino too much.
Just to be sure I double checked the current tolerances on all the parts of the relay board, I just got a lamp and it draws 2.2A at 250V so that should easily work.
And I swapped the transistors on the relay board for optocouplers as I had a pile of them lying around the house. I checked the current limits and I have about 20mA overhead.

On the code side the only thing I have changed is in the interval.

Before:
If (5 minutes have passed) {
Check temp, humidity and soil moistness
}
If (temp > 28) {
Turn fan on
}
etc......
Now:
If (5 minutes have passed) {
Check temp, humidity and soil moistness
If (temp > 28) {
Turn fan on
}
etc......

Just so the Arduino only waits for the 5 minutes and not for stats that don't change anyways.
Anyways here's the code and schematics. Parts are en route so physical photos this weekend I hope.

#include <Sensirion.h>
#include <Time.h>

//Temperature and Humidity sensor
const uint8_t dataPin  =  2;
const uint8_t clockPin =  3;

float temperature;
float humidity;
float dewpoint;

Sensirion tempSensor = Sensirion(dataPin, clockPin);

//Temperature and Humidity interval time
long previousMillis = 0;
long interval = 300000;

//Soil moisture sensor
int moistureSensor = A0;
int moisture_val;

//Serial stuff
char buffer[5];

//Relay output pins
const int lightPin = 4;
const int waterPin = 5;
const int fanPin = 6;
const int humPin = 7;

//Control panel shift register pins and ON / OFF data string
int stats[15];
int previousstats[15];
const int data = 8;
const int clock = 9;
const int latch = 10;

//Light status
int lightstatus = 0;
int lastlightstatus = 0;

//Fan status
int fanstatus = 0;
int lastfanstatus = 0;

//Humidifier status
int humtime = 1000; //In milliseconds
int humstatus = 0;
int lasthumstatus = 0;

//Watering status
int watertime = 1000; //In milliseconds
int waterstatus = 0;
int lastwaterstatus = 0;

void setup()
{
  Serial.begin(9600);
  setTime(20,7,0,17,5,2011);
  pinMode(lightPin, OUTPUT);
  pinMode(waterPin, OUTPUT);
  pinMode(fanPin, OUTPUT);
  pinMode(humPin, OUTPUT);
  pinMode(data, OUTPUT);
  pinMode(clock, OUTPUT);
  pinMode(latch, OUTPUT);
}

void loop()
{
  unsigned long currentMillis = millis();
  time_t t = now();
  
  //Check Temperature.... and report every 5 minutes
  if(currentMillis - previousMillis > interval) {
    previousMillis = currentMillis;
    
    tempSensor.measure(&temperature, &humidity, &dewpoint);

    moisture_val = analogRead(moistureSensor);
    
    Serial.print("Temperature: ");
    Serial.print(temperature);
    Serial.print(" C, Humidity: ");
    Serial.print(humidity);
    Serial.print(" %, Dewpoint: ");
    Serial.print(dewpoint);
    Serial.print(" C, Soil Moisture: ");
    Serial.println(moisture_val);
    Serial.print("#S|TEMPLOG|[");
    Serial.print(itoa((temperature), buffer, 10));
    Serial.print(";");
    Serial.print(itoa((humidity), buffer, 10));
    Serial.print(";");
    Serial.print(itoa((dewpoint), buffer, 10));
    Serial.print(";");
    Serial.print(itoa((moisture_val), buffer, 10));
    Serial.println("]#");
    update();
  }
  
  
  /////Light trigger
  if((0 <= hour(t)) && (18 > hour(t))) { ///Between which hours on
    lightstatus = 1;
  }
  if((18 <= hour(t)) && (24 >= hour(t))) { ///Between which hours off
    lightstatus = 0;
  }
  //Light compare, set status and report
  if(lightstatus != lastlightstatus) {
    if(lightstatus == 1) {
      Serial.println("#S|ACTIONLOG|[Light On]#");
    }
    if(lightstatus == 0) {
      Serial.println("#S|ACTIONLOG|[Light Off]#");
    }
    digitalWrite(lightPin, lightstatus);
    lastlightstatus = lightstatus;
  }
}
  
  
  void update() {
  /////Fan trigger
  if(temperature <= 25 || humidity <= 60) { ///Turn off threshold
    fanstatus = 0;
  }
  if(temperature > 28 || humidity > 80) { ///Turn on threshold
    fanstatus = 1;
  }
  //Fan compare, set status and report
  if(fanstatus != lastfanstatus) {
    if(fanstatus == 1) {
      Serial.println("#S|ACTIONLOG|[Fan On]#");
    }
    if(fanstatus == 0) {
      Serial.println("#S|ACTIONLOG|[Fan Off]#");
    }
    digitalWrite(fanPin, fanstatus);
    lastfanstatus = fanstatus;
  }
  
  
  
  /////Water trigger
  if(moisture_val > 1000) { ///Turn off threshold
    waterstatus = 0;
  }
  if(moisture_val < 850) { ///Turn on threshold
    waterstatus = 1;
  }
  //Pump compare, set status and report
  if(waterstatus != lastwaterstatus) {
    if(waterstatus == 1) {
      Serial.println("#S|ACTIONLOG|[Pump On]#");
      digitalWrite(waterPin, waterstatus);
        delay(watertime);
        waterstatus = 0;
        Serial.println("#S|ACTIONLOG|[Pump Off]#");
        digitalWrite(waterPin, waterstatus);
        lastwaterstatus = waterstatus;
      }
   }
  
  
  
  ////Humidifier trigger
  if(humidity > 70) { ///Turn off threshold
    humstatus = 0;
  }
  if(humidity < 50) { ///Turn on threshold
    humstatus = 1;
  }
  //Humidifier compare, set status and report
  if(humstatus != lasthumstatus) {
    if(waterstatus == 1) {
      Serial.println("#S|ACTIONLOG|[Humidifier On]#");
      digitalWrite(humPin, humstatus);
        delay(humtime);
        humstatus = 0;
        Serial.println("#S|ACTIONLOG|[Humidifier Off]#");
        digitalWrite(humPin, humstatus);
        lasthumstatus = humstatus;
      }
   }
   
   
   
  /////Status LEDs
  ////These store the ON and OFF data in the 'stats' string to be used when shifting out
  //Temperature
  if((temperature < 22) || (temperature > 28)) {
    stats[2] = 0;
    stats[3] = 1;
  }
  else {
    stats[2] = 1;
    stats[3] = 0;
  }
  //Humidity
  if((humidity < 60) || (humidity > 80)) {
    stats[4] = 0;
    stats[5] = 1;
  }
  else {
    stats[4] = 1;
    stats[5] = 0;
  }
  //Soil
  if((moisture_val < 850) || (moisture_val > 1000)) {
    stats[6] = 0;
    stats[7] = 1;
  }
  else {
    stats[6] = 1;
    stats[7] = 0;
  }
  ////Checks if update to control panel is needed
  //And copies 'stats' string charachter by character to 'previousstats'
  if(stats != previousstats) {
    led();
    for(int z = 1; z<15; z++) {
      previousstats[z] = stats[z];
    }
  }
  
  
}


////Changes stats string to bits and shifts them out
void led() {
  byte bitBuffer1 = 0;
  bitWrite(bitBuffer1, 1, stats[1]);
  bitWrite(bitBuffer1, 2, stats[2]);
  bitWrite(bitBuffer1, 3, stats[3]);
  bitWrite(bitBuffer1, 4, stats[4]);
  bitWrite(bitBuffer1, 5, stats[5]);
  bitWrite(bitBuffer1, 6, stats[6]);
  bitWrite(bitBuffer1, 7, stats[7]);
  byte bitBuffer2 = 0;
  bitWrite(bitBuffer2, 1, stats[8]);
  bitWrite(bitBuffer2, 2, stats[9]);
  bitWrite(bitBuffer2, 3, stats[10]);
  bitWrite(bitBuffer2, 4, stats[11]);
  bitWrite(bitBuffer2, 5, stats[12]);
  bitWrite(bitBuffer2, 6, stats[13]);
  bitWrite(bitBuffer2, 7, stats[14]);
  digitalWrite(latch, LOW);
  shiftOut(data, clock, MSBFIRST, bitBuffer2);
  shiftOut(data, clock, MSBFIRST, bitBuffer1);
  digitalWrite(latch, HIGH);
}

Control Panel:


Relay board:

And Rob, I'm so sorry I haven't gotten around to putting any of your modifications in it. I've just been drawing and modifying the code in my lectures.

Hi Spencer,

I already put a ton of effort into writing this web tier for arduino data and it integrates into Google Charts, the source code for loading google charts etc is all on the web site. Hopefully it helps you guys out and saves you some $. If you don't have an Ethernet Shield, I have a .net and java SDK that makes it really easy to pull data from arduinos attached to a USB and push the data up to Nimbits Data Points on the web or to an instance of Nimbits Server running locally. Take a look at the SDK links on www.nimbits.com

One of my users just wrote a cool Google Gadget that lets you see your data on a google site or igoogle:

Hope this helps.

Ben