 |
|
07-08-2012, 09:14 PM
|
#1
|
|
Turgid Member
Feedback Score: 0 reviews
Join Date: Feb 2011
Location: Las Vegas, NV
Posts: 460
Liked 26 Times on 24 Posts Likes Given: 2
|
Arduino - Networked Temperature Monitor
|
|
I have two large upright freezers in my garage that I use as beer coolers. Both use an E-bay aquarium temperature controller, to keep my beers at about 50°.
Since I have Rolladen Security Shutters installed on all of the exterior doors and windows of my home, including the door between the house and the garage, I don't go into the garage often.
One day my wife came from the garage and told me that one of the freezers was making a racket. When I investigated, it turned out that the fan that circulates the cold air was on its way out, and the freezer was struggling to hold temperature. I replaced the fan, and everything was copacetic, but I was concerned about the possibility of another problem that might cause one of the freezers to not hold temperature.
What I needed was something that would allow me to monitor the freezers remotely.
I initially considered one of the wireless indoor/outdoor thermometers that I use in my kitchen, but the wireless signal would not penetrate the metal skin of the freezer.
Then I hit upon using an Arduino microcontroller board to run some One-Wire temperature sensors, and have it serve a web page over my home intranet when interrogated.
I then picked up the following parts:
Freetronics EtherTen: This is a single board that is 100% Ardunio Uno-compatible, and has the equivalent of an Arduino Ethernet Shield baked into a single board. It's 100% compatible with standard Arduino libraries, and costs about the same or slightly less than the street cost of the two Arduino boards it replaces (unless of course you buy knock-offs off of ebay). It also reduces the overall height of the project, which involves "stacking" the cards.
Freetronics Short Protoshield: This is a prototyping breadboad that fits behind the RJ-45 jack on the Etherten or Ethernet Shield. I used this to hold the connectors and components for the One-Wire system.
DS18B20 Temperature Probes: These probes use a Maxim DS18B20 Digital Thermometer that uses the One-Wire technology that allows multiple devices to share a simple two or three wire microlan. I chose to use the three wire setup, which simply involved placing a 4k7 ohm resistor between DS2 of the Etherten (Uno) and 5V
Adafruit I2C RGB 16x2 LCD Display: This is a slick little kit that allows you to run an LCD display that can change backlight color under software control. It can also be used as a stackable shield, or connected with just 4 wires from the Etherten.
Once I had the boards assembled and stacked, I downloaded the IDE from the Arduino "Getting Started" page. I use OSX, Linux and Windows, so I got the IDE for all three and installed them. I also downloaded the "Adafruit RGB LCD Shield Library" and the "OneWire Library" from their respective sites, and loaded them into the appropriate locations in the IDE.
I then wrote some code that will read each probe, and display the result on both the display, and on a simple text-based web page. I used the RGB backlighting to indicate whether or not the temperature was within a specified range. If the temp was too high, the display would turn red:
If the temp was too cold it would turn blue:
But if everything was correct, then the display would be green:
You can watch the monitor in action here.
If I ran into a situation where one was too cold, and one was too hot, I opted to display a red screen. Additionally, the red and blue screens both blink on and off as another visual indication of a problem.
I use Powerline Communication Adapters that allow me to use my house wiring for ad hoc ethernet communication. I've been using the older 200 MBPS adapter for a while now, and they work great. I can simply put one of the adapters in my garage, connect it to the Etherten, and then interrogate it from any other computer in my home using a web browser:
I also use red, blue and green on the web pages to show too hot, too cold, and just right.
I'm planning on doing something similar to monitor "Barney", so stay tuned for further developments.
|
|
|
07-08-2012, 09:36 PM
|
#2
|
|
Emperor of the Universe
Feedback Score: 0 reviews
Join Date: Apr 2012
Location: Carlisle, PA
Posts: 1,205
Liked 24 Times on 24 Posts
|
Hi
How many probes do you think you could support without adding more hardware than the probes themselves?
Bob
|
|
|
07-08-2012, 10:47 PM
|
#3
|
|
Feedback Score: 2 reviews
Join Date: Apr 2010
Location: New Jersey
Posts: 183
Liked 11 Times on 11 Posts Likes Given: 36
|
Awesome stuff, definitely will be ripping this off. Well done.
|
|
|
07-08-2012, 11:14 PM
|
#4
|
|
Turgid Member
Feedback Score: 0 reviews
Join Date: Feb 2011
Location: Las Vegas, NV
Posts: 460
Liked 26 Times on 24 Posts Likes Given: 2
|
Quote:
Originally Posted by carlisle_bob
Hi
How many probes do you think you could support without adding more hardware than the probes themselves?
Bob
|
I bought 50 of the DS18B20's and breadboarded 15 of them, no problems. As long as you don't use parasitic mode, ie, as long as you use a single pull-up resistor on the data line, I think the biggest problem would be the time lag polling each probe.
I'm using an RJ-45 patch panel wired in parallel, and am using some of the several bazillion ethernet cables I have laying around to make new probes.
Supposedly there can be reflection problems if you use a star-type network instead of a short stub off of a main line, but I haven't seen it in my tests.
|
|
|
07-08-2012, 11:30 PM
|
#5
|
|
Turgid Member
Feedback Score: 0 reviews
Join Date: Feb 2011
Location: Las Vegas, NV
Posts: 460
Liked 26 Times on 24 Posts Likes Given: 2
|
Quote:
Originally Posted by cosmatics
Awesome stuff, definitely will be ripping this off. Well done.
|
Here's the current code:
Code:
/*
RGB-LCD and Web Temperature monitor utilizes Dallas Semiconductor / Maxim DS18B20
One-Wire Digital Temperature sensors to monitor two freezers used as beer storage coolers.
The temperatures are available from a 16x2 RGB LCD and by accessing a small text based web page
generated in response to a "GET" command.
One-Wire data channel is on digital pin 2, which is pulled up to +5v with a 4K7 resistor.
The ethernet interface is the Wiznet W5100 based Ethernet Shield.
If you are using multiple Arduino/Ethernet shields, each one must have a unique MAC address and
IP Address.
*/
// include the library code:
#include <Wire.h>
#include <Adafruit_MCP23017.h>
#include <Adafruit_RGBLCDShield.h>
#include <SPI.h>
#include <Ethernet.h>
#include <OneWire.h>
// Debug defines
// #define SerialDebug
/********** LCD Stuff **********/
// The shield uses the I2C SCL and SDA pins. On classic Arduinos
// this is Analog 4 and 5 so you can't use those for analogRead() anymore
// However, you can connect other I2C sensors to the I2C bus and share
// the I2C bus.
Adafruit_RGBLCDShield lcd = Adafruit_RGBLCDShield();
// These constants make it easy to set the backlight color
const int BLOFF = 0x0;
const int RED = 0x1;
const int YELLOW = 0x3;
const int GREEN = 0x2;
const int TEAL = 0x6;
const int BLUE = 0x4;
const int VIOLET = 0x5;
const int WHITE = 0x7;
const int cols = 16;
const int rows = 2;
const byte nbsp = 0x20; // space character
const byte zeroChar = 0x30; // zero character
const char degChar = 0xdf; // degree character
bool addFSpace = FALSE;
bool showRed = FALSE;
bool showBlue = FALSE;
bool showAddresses = FALSE;
int rowNum = 0;
/********** Ethernet / Web Stuff **********/
// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = { 0xDE, 0xAD, 0xBA, 0xBE, 0x00, 0x04 };
IPAddress ip(192,168,1,178);
const int BUFSIZE = 100; // buffer for internet request
// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(80);
/********** OneWire Stuff ************/
const int dsDataPin = 2; // One Wire data Bus
const int maxSensors = 2; // total number of DS18B20 sensors
OneWire ds(dsDataPin); // OneWire bus on pin 2
typedef struct
{
char *sensorName;
char *xmlName;
byte addr[8];
float degF;
int tooHot; // if temperature is above this value, display is red
int tooCold; // if temperature is below this value, display is blue
} Sensor;
Sensor ds18[maxSensors] =
{
{"Cooler 1 ", "Cooler1", {0x28,0x88,0xA4,0xF4,0x03,0x00,0x00,0xE2}, 0.0, 90, 80},
{"Cooler 2 ", "Cooler2", {0x28,0xC0,0xDF,0x7E,0x03,0x00,0x00,0xE9}, 0.0, 90, 80}
};
int cnt = 0;
void setup()
{
// set up the LCD's number of columns and rows:
lcd.begin(cols, rows);
lcd.setBacklight(WHITE);
// Print a message to the LCD.
// Open serial communications and wait for port to open:
Serial.begin(9600);
#if defined SerialDebug
Serial.println(F("Checking for OneWire Devices"));
#endif
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
server.begin();
#if defined SerialDebug
Serial.print("server is at ");
Serial.println(Ethernet.localIP());
#endif
}
void getOneWire(void)
{
byte i;
byte present = 0;
byte type_s;
byte data[12];
#if defined SerialDebug
Serial.print(F("ROM "));
Serial.print(cnt);
Serial.print(F(" = "));
for( i = 0; i < 8; i++)
{
Serial.write(nbsp);
if(ds18[cnt].addr[i] < 16){Serial.write(zeroChar);} // prepend hex 0-15 with a 0
Serial.print(ds18[cnt].addr[i], HEX);
}
if (OneWire::crc8(ds18[cnt].addr, 7) != ds18[cnt].addr[7]) {
Serial.println(F("CRC is not valid!"));
return;
}
Serial.println();
#endif
type_s = 0;
ds.reset();
ds.select(ds18[cnt].addr);
ds.write(0x44,1); // start conversion, with parasite power on at the end
delay(1000); // maybe 750ms is enough, maybe not
// we might do a ds.depower() here, but the reset will take care of it.
present = ds.reset();
ds.select(ds18[cnt].addr);
ds.write(0xBE); // Read Scratchpad
#if defined SerialDebug
Serial.print(F(" Data = "));
if(present < 16){Serial.write(zeroChar);} // prepend hex 0-15 with a 0
Serial.print(present, HEX);
Serial.write(nbsp);
#endif
for ( i = 0; i < 9; i++) { // we need 9 bytes
data[i] = ds.read();
#if defined SerialDebug
if(data[i] < 16){Serial.write(zeroChar);} // prepend hex 0-15 with a 0
Serial.print(data[i], HEX);
Serial.write(nbsp);
#endif
}
#if defined SerialDebug
Serial.print(F(" CRC="));
Serial.print(OneWire::crc8(data, 8), HEX);
Serial.println();
#endif
// convert the data to actual temperature
unsigned int raw = (data[1] << 8) | data[0];
unsigned char t_mask[4] = {0x7, 0x3, 0x1, 0x0};
byte cfg = (data[4] & 0x60) >> 5;
raw &= ~t_mask[cfg];
ds18[cnt].degF = ((((float)raw / 16.0) * 1.8) + 31.0); //the raw value is Celsius, convert to Fahrenheit
#if defined SerialDebug
Serial.print(" Temperature = ");
Serial.print(ds18[cnt].degF);
Serial.println(" Fahrenheit");
#endif
if( ++cnt >= maxSensors){cnt = 0;}
}
void checkEthernet(void)
{
char clientline[BUFSIZE];
int index = 0;
// listen for incoming clients
EthernetClient client = server.available();
if (client) {
Serial.println("new client");
// an http request ends with a blank line
boolean currentLineIsBlank = true;
while (client.connected())
{
if (client.available())
{
char c = client.read();
// If it isn't a new line, add the character to the buffer
if (c != '\n' && c != '\r')
{
clientline[index] = c;
index++;
// are we too big for the buffer? start tossing out data
if (index >= BUFSIZE)
index = BUFSIZE -1;
// continue to read more data!
continue;
}
// got a \n or \r new line, which means the string is done
clientline[index] = 0;
// Print it out for debugging
Serial.println(clientline);
// if you've gotten to the end of the line (received a newline
// character) and the line is blank, the http request has ended,
// so you can send a reply
if (c == '\n' && currentLineIsBlank)
{
if((strstr(clientline, "GET / ") != 0) ||
(strstr(clientline, "index.htm") != 0) ||
(strstr(clientline, "index.html") != 0))
{
// send a standard http response header
client.println(F("HTTP/1.1 200 OK"));
client.println(F("Content-Type: text/html"));
client.println(F("Connnection: close"));
client.println();
client.println(F("<!DOCTYPE HTML>"));
client.println("<html><head>");
// add a meta refresh tag, so the browser pulls again every 5 seconds:
client.println(F("<meta http-equiv=\"refresh\" content=\"5\">"));
client.println(F("</head>"));
client.println(F("<body><table border=\"5\" align=\"center\"><tr>"));
for (int n=0; n<maxSensors; n++)
{
if(n<=maxSensors)
{
client.print(F("<td align=\"center\" valign=\"center\"><font size=\"10\"> "));
if((int)ds18[n].degF > ds18[n].tooHot)
{
client.print(F("<font color=\"red\">"));
}else if((int)ds18[n].degF < ds18[n].tooCold){
client.print(F("<font color=\"blue\">"));
}else{
client.print(F("<font color=\"green\">"));
}
client.print(ds18[n].sensorName);
client.print(F(" <br />"));
client.print(ds18[n].degF, 0);
client.println(F("°F</font></font></td>"));
}
}
client.println(F("</tr></table></body>"));
client.println(F("</html>"));
break;
}else if (strstr(clientline, "buttcrack.xml") !=0){
#if defined SerialDebug
Serial.print(F("Sending buttcrack.xml..."));
#endif
client.println(F("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>"));
client.println(F("<!--Arduino Buttcrack-->"));
client.println(F("<buttcrack>"));
for (int n = 0; n < maxSensors; n++)
{
client.print(F("<"));
client.print(ds18[n].xmlName);
client.print(F(">"));
client.print(ds18[n].degF,0);
client.print(F("</"));
client.print(ds18[n].xmlName);
client.println(F(">"));
}
client.println(F("</buttcrack>"));
delay(1);
client.stop();
}else{
// everything else is a 404
client.println(F("HTTP/1.1 404 Not Found"));
client.println(F("Content-Type: text/html"));
client.println();
client.println(F("<h2>File Not Found</h2>"));
delay(1);
client.stop();
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
} else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
}
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
#if defined SerialDebug
Serial.println(F("client disonnected"));
#endif
}
}
void displayLCDTemp()
{
if(maxSensors==0)
{
lcd.setBacklight(BLOFF);
delay(500);
lcd.setBacklight(RED);
lcd.print(F("Sensor Failure!!"));
lcd.setCursor(0, 1);
lcd.print(F("Sensor Failure!!"));
}else{
showRed = FALSE;
showBlue = FALSE;
for(int n=rowNum;n<maxSensors;n++)
{
if(ds18[n].degF > ds18[n].tooHot)
{
showRed = TRUE;
showBlue = FALSE;
}else if(ds18[n].degF < ds18[n].tooCold){
showBlue = TRUE;
}
}
if(showRed)
{
lcd.setBacklight(BLOFF);
delay(500);
lcd.setBacklight(RED);
}else if(showBlue){
lcd.setBacklight(BLOFF);
delay(500);
lcd.setBacklight(BLUE);
}else{
lcd.setBacklight(GREEN);
}
for(int n=rowNum;n<maxSensors;n++)
{
if(ds18[n].degF>=100)
{
addFSpace=TRUE;
break;
}else{
addFSpace=FALSE;
}
}
if((rowNum+rows) >= maxSensors){rowNum = maxSensors-rows;}
for(int n=rowNum,cnt=0;cnt<rows;cnt++,n++)
{
lcd.setCursor(0, cnt);
lcd.print(ds18[n].sensorName);
if(ds18[n].degF < 100 && addFSpace)
{
lcd.print(F(" = "));
}else{
lcd.print(F(" = "));
}
lcd.print(ds18[n].degF,0);
lcd.print(degChar);
lcd.print(F(" "));
#if defined SerialDebug
Serial.print(F("n = "));
Serial.print(n);
Serial.print(F(" cnt = "));
Serial.print(cnt);
Serial.print(F(" name = "));
Serial.print(ds18[n].sensorName);
Serial.print(F(" temp = "));
Serial.println(ds18[n].degF,0);
#endif
}
}
}
void checkButtons()
{
uint8_t buttons = lcd.readButtons();
switch(buttons)
{
case BUTTON_UP:
if(rowNum > 0){rowNum--;}
#if defined SerialDebug
Serial.print(F("UP Button Pushed "));
Serial.print(F("rowNum = "));
Serial.println(rowNum);
#endif
break;
case BUTTON_DOWN:
if(rowNum < maxSensors-1){rowNum++;}
#if defined SerialDebug
Serial.println(F("DOWN Button Pushed"));
Serial.print(F("rowNum = "));
Serial.println(rowNum);
#endif
break;
case BUTTON_RIGHT:
#if defined SerialDebug
Serial.println(F("RIGHT Button Pushed"));
#endif
break;
case BUTTON_LEFT:
#if defined SerialDebug
Serial.println(F("LEFT Button Pushed"));
#endif
break;
case BUTTON_SELECT:
#if defined SerialDebug
Serial.println(F("SELECT Button Pushed"));
#endif
rowNum = 0;
break;
}
}
#if defined SerialDebug
void displayAddresses(void)
{
/********** Get Sensor Addresses **********
Used to get initial values for Sensor structure. Enable SerialDebug, and obtain DS18B20 addresses
from the serial terminal. Plug those values into the Sensor's "addr" array, re-compile and upload
with SeralDebug disabled. Note that when SerialDebug is enabled, response times on buttons and
display are greatly reduced, so be sure to disable SerialDebug when not needed.
******************************************/
byte addr[8];
int cntx = 0;
while ( ds.search(addr))
{
Serial.print(F("Sensor "));
Serial.print(cntx);
Serial.print(F("= {"));
for( int i = 0; i < 8; i++)
{
Serial.print(F("0x"));
if(addr[i] < 16){Serial.write(0x30);} // prepend hex 0-15 with a 0
Serial.print(addr[i], HEX);
if(i < 7){Serial.print(F(","));}
}
Serial.println(F("}"));
cntx++;
delay(500);
}
Serial.print(cntx);
Serial.print(F(" Sensor"));
if(cntx == 1)
{
Serial.println(F(" detected"));
}else{
Serial.println(F("s detected"));
}
ds.reset_search();
}
#endif
void loop()
{
getOneWire();
checkEthernet();
checkButtons();
#if defined SerialDebug
displayAddresses();
#endif
displayLCDTemp();
// delay(500);
}
The first thing you need to do is enable the "SerialDebug" define, and run the code while examining the serial monitor. The "displayAddresses" code will then interrogate the microlan for all of the ds18b20s that are connected, and show each address as a byte array. Take each address and plug it into the appropriate location in the ds18 Sensor array. You can add as many sensors as you want, just make sure that the maxSensors const equals the number of sensors in the array. You can then re-compile with SerialDeug off (saves about 2K of codespace and speeds things up) and re-run the code.
If you have more than 2 sensors, you can use the "UP"and "DOWN" buttons to vertically scroll the sensors on the display. The display should only change color if one of the sensors currently displayed is out of bounds.
Just be aware that the web page is pretty simple, and may just try to print all of the sensor info on a single line (I'll fix that later).
|
|
|
07-09-2012, 02:46 AM
|
#6
|
|
Feedback Score: 0 reviews
Join Date: Mar 2011
Location: Fort Lauderdale, Florida
Posts: 213
Liked 8 Times on 8 Posts
|
Subscribed! Awesome idea.
Do you have the page listed under a DNA server so you can access it from anywhere?
|
|
|
07-09-2012, 03:12 AM
|
#7
|
|
Turgid Member
Feedback Score: 0 reviews
Join Date: Feb 2011
Location: Las Vegas, NV
Posts: 460
Liked 26 Times on 24 Posts Likes Given: 2
|
Quote:
Originally Posted by Kyled93
Subscribed! Awesome idea.
Do you have the page listed under a DNA server so you can access it from anywhere?
|
Nah, it's not like I can leave my job if my beer's getting warm 
It's on an internal subnet right now, although I do have the ability to twitter an alert if I want to add the code.
I'm primarily interested in checking it without having to roll up the security shutter to my garage just to check the temperature. I'm also interested in building another to monitor and log my brewstand temperatures onto an SD card during a session. 
|
|
|
07-09-2012, 03:18 AM
|
#8
|
|
Turgid Member
Feedback Score: 0 reviews
Join Date: Feb 2011
Location: Las Vegas, NV
Posts: 460
Liked 26 Times on 24 Posts Likes Given: 2
|
FYI, I just created two new temp probes using a flat flexible telephone extension cable that was lying around. It was a flat 4-wire cable, and the two probe cables are about 10ft each, and are working just fine. 
|
|
|
07-09-2012, 02:42 PM
|
#9
|
|
Feedback Score: 0 reviews
Join Date: Feb 2010
Location: Louisiana
Posts: 764
Liked 6 Times on 6 Posts Likes Given: 2
|
Subscribed. I'm about to embark on a similar project.
|
|
|
07-09-2012, 04:15 PM
|
#10
|
|
Feedback Score: 0 reviews
Join Date: Oct 2010
Location: Bowmansville, PA
Posts: 85
Liked 2 Times on 2 Posts
|
Subscribed. I love projects like this and can't wait to get started on some of my own.
__________________
Gregg G^3 Swartley
|
|
|
| Thread Tools |
|
|
| Display Modes |
Linear Mode
|
|
|
|