Boarduino vivarium temperature monitor
I like snakes. This weekend I will be collecting my new snake, he’s a baby Royal Python. I have been worried about the temperature in the vivarium I will be housing him so I built an ethernet enabled monitoring device to do a science!

Its a BoArduino, an HD4470 compatible LCD display, a WIZ810MJ ethernet module (in an adapter board) and some discrete components with a TMP36 for temperature monitoring.
This is the result set for the last day. The live graph is here.

The circuit uses nearly all of the digital pins (it would use them all if the ethernet library supported interrupts). The LCD is connected using digital pins 3 to 8. Pins 3 to 6 are the data lines, 7 is the enable pin and 8 is RS. The ethernet module is connected using digital pins 9 to 13 being Reset, SS, MOSI, MISO and SCLK. The temperature sensor is connected to Analog pin 0. It outputs 10mV per degree Celcius with a 500mV offset so that 0C is 500mv, 50C is 1V etc. The code sketch is below.
#include
// Required for the LCD
#include
// Required for ethernet
#include
#include
#include
// Required for the IINCHIP macros
#include
// The ethernet reset control pin
int resetPin = 9;
// The temperature analog input pin
int tempAnPin = 0;
// String buffer
char buffer[256];
// Hardcoded MAC, IP and gateway
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 1, 140 };
byte gw[] = { 192, 168, 1, 254 };
// Create an LCD control object. Doesn't use regular pins as
// we need some of them for the Ethernet SPI.
LiquidCrystal lcd(8, 7, 6, 5, 4, 3);
// Hardcoded server and port
byte svr[] = { 192, 168, 1, 128 };
int port = 80;
// A client class for connecting to a remote server to send data
Client client(svr, port);
/**
* Writes a byte to a register in the W5100 chip used by the
* WIZ810MJ ethernet board.
*
* @param addr The address in the W5100 controller to write.
* @param data The byte to write.
*/
void writeByte(int addr, byte data) {
IINCHIP_ISR_DISABLE();
IINCHIP_SpiInit();
IINCHIP_CSoff();
IINCHIP_SpiSendData(0xf0);
IINCHIP_SpiSendData((addr & 0xff00) >> 8);
IINCHIP_SpiSendData(addr & 0xff);
IINCHIP_SpiSendData(data);
IINCHIP_CSon();
IINCHIP_ISR_ENABLE();
}
// The four IP addres bytes (we're not IP6 compatible here)
byte ip0, ip1, ip2, ip3;
/**
* Initialise the hardware
*/
void setup() {
// Reset the ethernet board
pinMode(resetPin, OUTPUT);
digitalWrite(resetPin, HIGH);
delay(1); // 1ms
digitalWrite(resetPin, LOW);
delay(1); // 1ms
digitalWrite(resetPin, HIGH);
// Start ethernet
Ethernet.begin(mac, ip, gw);
// Enable ping. Useful for debugging.
writeByte(0x0000, 0x00);
// Read the IP address (Check to see its working right)
ip0 = readByte(0x000f);
ip1 = readByte(0x0010);
ip2 = readByte(0x0011);
ip3 = readByte(0x0012);
// Form the output string (Adds about a K to the code size)
sprintf(buffer, "%d.%d.%d.%d", ip0, ip1, ip2, ip3);
// Display it
lcd.home();
lcd.print(buffer);
#ifdef DEBUG
Serial.begin(9600);
#endif
}
// The value of the temperature analog pin
int tempAnValue;
// The temperature
int temperature;
// The temperature components;
int tempInt;
int tempFrac;
// A counter for sychronization. Allows server to work out if a
// 'packet' is missing
int count = 0;
/**
* Main processing thread
*/
void loop() {
// Read the temperature
tempAnValue = analogRead(tempAnPin);
// Fixed point math to calculate the temperature (multiply then divide
// to reduce rounding issues) Gives temperature in 100ths of degree C
// 5000 millivolts is the maximum voltage
// 1024 is the maximum ADC value
// 500mv is 0 degrees C
temperature = ((tempAnValue * 50000) / 1024) - 5000;
// Since we're only going to report to 1 decimal place, add 5 for correct
// rounding
tempInt = (temperature + 5) / 100;
tempFrac = ((temperature + 5) % 100) / 10;
// Form the output string
sprintf(buffer, "Temp: %d.%d C", tempInt, tempFrac);
// Display it
lcd.clear();
lcd.print(buffer);
#ifdef DEBUG
{
sprintf(buffer,
"HEAD /newtemp.php?count=%d&temp=%d.%d HTTP/1.1",
count, tempInt, tempFrac);
Serial.println(buffer);
Serial.println("Host: my.host.org");
Serial.println("Connection: close");
Serial.println();
}
#endif
#ifndef NO_ETHERNET
// Send to server
if (client.connect()) {
// If connected send the packet count and temperature
sprintf(buffer,
"HEAD /newtemp.php?count=%d&temp=%d.%d HTTP/1.1",
count, tempInt, tempFrac);
client.println(buffer);
client.println("Host: my.host.org");
client.println("Connection: close");
client.println();
client.stop();
} else {
// If failed sad face
lcd.print(" :-(");
}
#endif
// Update packet counter
count++;
// Sleep for a minute
delay(60000);
}
A couple of things to note are:
- I use integer math. avrgcc, the compiler, supports floats but its just more efficient and requires less memory to use fixed point integer math when its just one or two values you need to represent.
- I use a count value sent with each data packet so that if one goes missing you can tell, Currently the code at the other end doesn’t use it though.
- I send a HEAD request with a query string containing the values. My request is not technically valid HTTP but the server doesn’t have a problem. A HEAD request returns the headers for a request but no body. Since I’m ignoring the whole response anyway I don’t need the body!
The script on the web server that receives the data is below. It is very simple PHP. Since PHP doesn’t support HEAD requests directly in its super-globals (watch this space 🙂 I process the query string by hand. As I know the format of the data since I control the client I’m not worried about URL decoding the data. It writes three values (the time in seconds since the UNIX epoch, the packet count and the temperature), colon separated, as a line to a text file. It returns nothing.
Another script generates a graph as a PNG file based on the data. This code is below.
$datapoint['temperature'] ?
$maxtemp : $datapoint['temperature'];
}
// Get the axis dimensions. Round up and down to the nearest
// degree C and hour.
$lowtime = intval($mintime / 3600) * 3600;
$hightime = (intval($maxtime / 3600) + 1) * 3600;
$difftime = $hightime - $lowtime;
$lowtemp = intval($mintemp);
$hightemp = intval($maxtemp) + 1;
$difftemp = $hightemp - $lowtemp;
// Create the image
$image = imagecreate(WIDTH, HEIGHT);
if ($image) {
$background = imagecolorallocate($image, 255, 255, 255);
$black = imagecolorallocate($image, 0, 0, 0);
$red = imagecolorallocate($image, 255, 0, 0);
$blue = imagecolorallocate($image, 0, 0, 255);
// Draw the axes
imageline($image, 20, 20, 20, 485, $black);
imageline($image, 15, 480, 980, 480, $black);
imageline($image, 15, 20, 20, 20, $black);
imageline($image, 980, 480, 980, 485, $black);
for ($i = 3600; $i < $difftime; $i += 3600) {
$x = 20 + (($i * 960) / $difftime);
imageline($image, $x, 480, $x, 483, $black);
}
for ($i = 1; $i < $difftemp; $i++) {
$y = 480 - (($i * 460) / $difftemp);
imageline($image, 17, $y, 20, $y, $black);
}
// Draw the labels
imagestring($image, FONT, 8, 490, date("H:i", $lowtime), $black);
imagestring($image, FONT, 970, 490, date("H:i", $hightime), $black);
imagestringup($image, FONT, 2, 485, $lowtemp . 'C', $black);
imagestringup($image, FONT, 2, 25, $hightemp . 'C', $black);
// Draw the points
// Position of the first point
$prevx = (($data[0]['time'] - $lowtime) * 960) / $difftime;
$prevy = (($data[0]['temperature'] - $lowtemp) * 460) / $difftemp;
// Draw line from previous point to current point
for ($i = 1; $i < $datapoints; $i++) {
$x = (($data[$i]['time'] - $lowtime) * 960) / $difftime;
$y = (($data[$i]['temperature'] - $lowtemp) * 460) / $difftemp;
imageline($image, $prevx + 20, 480 - $prevy, $x + 20, 480 - $y, $red);
$prevx = $x;
$prevy = $y;
}
// Finally time and date stamp
$generated = 'Generated: ' . date("r");
imagestring($image,
FONT,
WIDTH - 15 - (imagefontwidth(FONT) * strlen($generated)),
HEIGHT - 15,
$generated,
$blue);
// Output the image
header('Content-Type:Â image/png');
imagepng($image);
// Destroy it
imagedestroy($image);
}
?>
This was a fun little project and as can be seen from the snap shot of the graph changing the bulb significantly changes the maximum temperature of the vivarium. I will be running with the smaller wattage bulb.
October 30th, 2009 at 5:35 pm
great work!
I have a couple questions regarding the temp sensors.
I’ve been thinking of doing a similar project, however as a fellow ball python owner, i know that we need to monitor not just the air temp, but also the ground temp on the warm and cool side. Have you thought about surface mounted type sensors?
What about humidity?
I am also putting together a vivarium for some dendros (dart frogs) and have similar concerns for them
thanks!
great work and thanks for sharing!
October 30th, 2009 at 6:46 pm
You could use the boarduino to control the output of the light. Use a triac and you can clip the ac going into the bulb so you can produce the amount of light needed to stabilize the temp.
October 30th, 2009 at 8:17 pm
@wrecks Actually that’s the next step 🙂 I need an opto-isolator as a zero point crossing detector and an opto-triac driver. Watch this space 🙂
October 30th, 2009 at 7:31 pm
very cool, you should check out openflashcharts for the graph generation. they worked pretty well for me for an arduino current monitoring project i did awhile ago (see http://jarv.org/pwrmon_current.shtml )
October 30th, 2009 at 8:50 pm
@John They’re very kewl. Thanks for the tip.
October 30th, 2009 at 9:03 pm
@Andrew Currently its just the one sensor taking the ambient air temperature in the middle of the tank. However there are several spare ADCs on the AVR so there’s no reason why you can’t have more. The sensor has a flat on the package so you could fix it to the wall of the viv I would think although I’ve never tried it.
I’m having noise issues with the sensor. I’m thinking a low pass filter and some amplification. I’ve also been looking at some 1 wire sensors (which would transmit the temperature to the AVR as digital and hence not have the noise issue).
I’ve not looked at humidity at all but this is still very much the early stages.
October 30th, 2009 at 9:41 pm
hey very nice project!
i use the same temp sensors, just take a couple of readings (maybee 10 with 20ms or so delay between them) and then average them. should do the trick!
as the noise is random it will be 0 in average.
October 30th, 2009 at 10:40 pm
@jan I was discussing something similar not that long ago. Glad to see it will work. Thanks.
November 11th, 2009 at 10:52 pm
I’ve been looking to do something similar with a planted viv that has a water feature. I’m hoping to add a humidity sensor that can kick on a relay with misters and do the same with fans if the temp gets too high. I’m thinking that the humidity sensor that sparkfun sells might not be able to deal with the high humidity that would be in a regularly misted vivarium though..
November 12th, 2009 at 5:32 pm
[…] started from Cyberspice’s code and changed the arduino code to make use of the DB18S20 and the ethernet […]