Easy Aquaponics System Sensing With Arduino

I have been messing around a little with Arduino and sensing the temperature and conductivity of the fish tank to get a better sense of the health of the overall system. Below is the code. I will be posting picture schematics using Fritzing soon.

This sketch:
* reads temperature (LM335AZ Analog Sensor)
* reads conductivity (see: http://blog.wassupy.com/2010/11/arduino-day-14-simple-conductivity.html)
* posts information to LCD (NOKIA 3310 display)
* posts information to a personal web page (for personal database storage via mySQL, etc)
* Analog Temperature Sensor (LM335AZ) attached to analog in 1
* Conductivity Sensor (see above link) attached to analog pins A2, A5 (measuring signal between the two probes at 1 cm)
* Nokia 3310 connected to digital pins 3,4,5,6,7
* Ethernet shield attached to pins 10, 11, 12, 13
* see http://www.vennie-vollrath.com for specific schematics
To do:
* Dissolved oxygen sensor
* pH sensor
* automatic fish feeder (using stepper motor
* heater control (closer monitoring of heaters increases energy efficiency)
* aquaponic grow bed temperature
* aquaponic grow bed humidity
* total energy consumption of entire aquaponics system

#define PIN_SCE 7 // LCD CS .... Pin 3 on LCD
#define PIN_RESET 6 // LCD RST .... Pin 1 on LCD
#define PIN_DC 5 // LCD Dat/Com. Pin 5 on LCD
#define PIN_SDIN 4 // LCD SPIDat . Pin 6 on LCD
#define PIN_SCLK 3 // LCD SPIClk . Pin 4 on LCD
// LCD Gnd .... Pin 2 on LCD
// LCD Vcc .... Pin 8 on LCD
// LCD Vlcd ... Pin 7 on LCD
#define LCD_C LOW
#define LCD_D HIGH
#define LCD_X 84 //width of screen (columns)
#define LCD_Y 48 //height of screen (rows)
#define LCD_CMD 0
char buff[25]; //buffer for converting doubles to strings
char buffers[25]; //buffer for converting doubles to strings
char buffer[256]; //longer buffer for passing web site string
float temp_in_kelvin=0, temp_in_fahrenheit=0; //initiate temperature variables to 0
int index = 0; // the index of the current reading
int total = 0; // the running total
int average = 0; // the average
int inputPin = 1; // input pin for LM335AZ temperature sensor
const float ArduinoVoltage = 5.00; // Voltage of the Arduino Uno
const float ArduinoResolution = ArduinoVoltage / 1024; //analog resolution for Arduino
const float resistorValue = 2200.0; // Resister size used in conductivity reading
int threshold = 3; // Conductivity threshold
int inPin = A2; // input pin for conductivity
int outputPin = A5; // output pin for conductivity

//initialize ethernet variables and client
byte mac[] = {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED}; // assign a MAC address for the ethernet controller.
byte ip[] = { 192, 168, 1, 102 }; // ip in lan assigned to arduino
EthernetClient client; // initialize the library instance:
long lastConnectionTime = 0; // last time you connected to theserver, in milliseconds
boolean lastConnected = false; // state of the connection last time through the main loop
const int postingInterval = 15000; // delay between updates to Pachube.com & personal server
// (pachube.com seems to error when trying to go under 10 seconds)

static const byte ASCII[][5] =
{0x00, 0x00, 0x00, 0x00, 0x00} // 20
,{0x00, 0x00, 0x5f, 0x00, 0x00} // 21 !
,{0x00, 0x07, 0x00, 0x07, 0x00} // 22 \"
,{0x14, 0x7f, 0x14, 0x7f, 0x14} // 23 #
,{0x24, 0x2a, 0x7f, 0x2a, 0x12} // 24 $
,{0x23, 0x13, 0x08, 0x64, 0x62} // 25 %
,{0x36, 0x49, 0x55, 0x22, 0x50} // 26 &
,{0x00, 0x05, 0x03, 0x00, 0x00} // 27 \'
,{0x00, 0x1c, 0x22, 0x41, 0x00} // 28 (
,{0x00, 0x41, 0x22, 0x1c, 0x00} // 29 )
,{0x14, 0x08, 0x3e, 0x08, 0x14} // 2a *
,{0x08, 0x08, 0x3e, 0x08, 0x08} // 2b +
,{0x00, 0x50, 0x30, 0x00, 0x00} // 2c ,
,{0x08, 0x08, 0x08, 0x08, 0x08} // 2d -
,{0x00, 0x60, 0x60, 0x00, 0x00} // 2e .
,{0x20, 0x10, 0x08, 0x04, 0x02} // 2f /
,{0x3e, 0x51, 0x49, 0x45, 0x3e} // 30 0
,{0x00, 0x42, 0x7f, 0x40, 0x00} // 31 1
,{0x42, 0x61, 0x51, 0x49, 0x46} // 32 2
,{0x21, 0x41, 0x45, 0x4b, 0x31} // 33 3
,{0x18, 0x14, 0x12, 0x7f, 0x10} // 34 4
,{0x27, 0x45, 0x45, 0x45, 0x39} // 35 5
,{0x3c, 0x4a, 0x49, 0x49, 0x30} // 36 6
,{0x01, 0x71, 0x09, 0x05, 0x03} // 37 7
,{0x36, 0x49, 0x49, 0x49, 0x36} // 38 8
,{0x06, 0x49, 0x49, 0x29, 0x1e} // 39 9
,{0x00, 0x36, 0x36, 0x00, 0x00} // 3a :
,{0x00, 0x56, 0x36, 0x00, 0x00} // 3b ;
,{0x08, 0x14, 0x22, 0x41, 0x00} // 3c ,{0x14, 0x14, 0x14, 0x14, 0x14} // 3d =
,{0x00, 0x41, 0x22, 0x14, 0x08} // 3e >
,{0x02, 0x01, 0x51, 0x09, 0x06} // 3f ?
,{0x32, 0x49, 0x79, 0x41, 0x3e} // 40 @
,{0x7e, 0x11, 0x11, 0x11, 0x7e} // 41 A
,{0x7f, 0x49, 0x49, 0x49, 0x36} // 42 B
,{0x3e, 0x41, 0x41, 0x41, 0x22} // 43 C
,{0x7f, 0x41, 0x41, 0x22, 0x1c} // 44 D
,{0x7f, 0x49, 0x49, 0x49, 0x41} // 45 E
,{0x7f, 0x09, 0x09, 0x09, 0x01} // 46 F
,{0x3e, 0x41, 0x49, 0x49, 0x7a} // 47 G
,{0x7f, 0x08, 0x08, 0x08, 0x7f} // 48 H
,{0x00, 0x41, 0x7f, 0x41, 0x00} // 49 I
,{0x20, 0x40, 0x41, 0x3f, 0x01} // 4a J
,{0x7f, 0x08, 0x14, 0x22, 0x41} // 4b K
,{0x7f, 0x40, 0x40, 0x40, 0x40} // 4c L
,{0x7f, 0x02, 0x0c, 0x02, 0x7f} // 4d M
,{0x7f, 0x04, 0x08, 0x10, 0x7f} // 4e N
,{0x3e, 0x41, 0x41, 0x41, 0x3e} // 4f O
,{0x7f, 0x09, 0x09, 0x09, 0x06} // 50 P
,{0x3e, 0x41, 0x51, 0x21, 0x5e} // 51 Q
,{0x7f, 0x09, 0x19, 0x29, 0x46} // 52 R
,{0x46, 0x49, 0x49, 0x49, 0x31} // 53 S
,{0x01, 0x01, 0x7f, 0x01, 0x01} // 54 T
,{0x3f, 0x40, 0x40, 0x40, 0x3f} // 55 U
,{0x1f, 0x20, 0x40, 0x20, 0x1f} // 56 V
,{0x3f, 0x40, 0x38, 0x40, 0x3f} // 57 W
,{0x63, 0x14, 0x08, 0x14, 0x63} // 58 X
,{0x07, 0x08, 0x70, 0x08, 0x07} // 59 Y
,{0x61, 0x51, 0x49, 0x45, 0x43} // 5a Z
,{0x00, 0x7f, 0x41, 0x41, 0x00} // 5b [
,{0x02, 0x04, 0x08, 0x10, 0x20} // 5c ¥
,{0x00, 0x41, 0x41, 0x7f, 0x00} // 5d ]
,{0x04, 0x02, 0x01, 0x02, 0x04} // 5e ^
,{0x40, 0x40, 0x40, 0x40, 0x40} // 5f _
,{0x00, 0x01, 0x02, 0x04, 0x00} // 60 `
,{0x20, 0x54, 0x54, 0x54, 0x78} // 61 a
,{0x7f, 0x48, 0x44, 0x44, 0x38} // 62 b
,{0x38, 0x44, 0x44, 0x44, 0x20} // 63 c
,{0x38, 0x44, 0x44, 0x48, 0x7f} // 64 d
,{0x38, 0x54, 0x54, 0x54, 0x18} // 65 e
,{0x08, 0x7e, 0x09, 0x01, 0x02} // 66 f
,{0x0c, 0x52, 0x52, 0x52, 0x3e} // 67 g
,{0x7f, 0x08, 0x04, 0x04, 0x78} // 68 h
,{0x00, 0x44, 0x7d, 0x40, 0x00} // 69 i
,{0x20, 0x40, 0x44, 0x3d, 0x00} // 6a j
,{0x7f, 0x10, 0x28, 0x44, 0x00} // 6b k
,{0x00, 0x41, 0x7f, 0x40, 0x00} // 6c l
,{0x7c, 0x04, 0x18, 0x04, 0x78} // 6d m
,{0x7c, 0x08, 0x04, 0x04, 0x78} // 6e n
,{0x38, 0x44, 0x44, 0x44, 0x38} // 6f o
,{0x7c, 0x14, 0x14, 0x14, 0x08} // 70 p
,{0x08, 0x14, 0x14, 0x18, 0x7c} // 71 q
,{0x7c, 0x08, 0x04, 0x04, 0x08} // 72 r
,{0x48, 0x54, 0x54, 0x54, 0x20} // 73 s
,{0x04, 0x3f, 0x44, 0x40, 0x20} // 74 t
,{0x3c, 0x40, 0x40, 0x20, 0x7c} // 75 u
,{0x1c, 0x20, 0x40, 0x20, 0x1c} // 76 v
,{0x3c, 0x40, 0x30, 0x40, 0x3c} // 77 w
,{0x44, 0x28, 0x10, 0x28, 0x44} // 78 x
,{0x0c, 0x50, 0x50, 0x50, 0x3c} // 79 y
,{0x44, 0x64, 0x54, 0x4c, 0x44} // 7a z
,{0x00, 0x08, 0x36, 0x41, 0x00} // 7b {
,{0x00, 0x00, 0x7f, 0x00, 0x00} // 7c |
,{0x00, 0x41, 0x36, 0x08, 0x00} // 7d }
,{0x10, 0x08, 0x08, 0x10, 0x08} // 7e ?
,{0x00, 0x06, 0x09, 0x09, 0x06} // 7f ?

void LcdCharacter(char character)
LcdWrite(LCD_D, 0x00);
for (int index = 0; index {
LcdWrite(LCD_D, ASCII[character - 0x20][index]);
LcdWrite(LCD_D, 0x00);

void LcdClear(void)
for (int index = 0; index {
LcdWrite(LCD_D, 0x00);

void LcdInitialise(void)
pinMode(PIN_DC, OUTPUT);

digitalWrite(PIN_RESET, LOW);
digitalWrite(PIN_RESET, HIGH);

LcdWrite( LCD_CMD, 0x21 ); // LCD Extended Commands.
LcdWrite( LCD_CMD, 0xBf ); // Set LCD Vop (Contrast). //B1
LcdWrite( LCD_CMD, 0x04 ); // Set Temp coefficent. //0x04
LcdWrite( LCD_CMD, 0x14 ); // LCD bias mode 1:48. //0x13
LcdWrite( LCD_CMD, 0x0C ); // LCD in normal mode. 0x0d for inverse
LcdWrite(LCD_C, 0x20);
LcdWrite(LCD_C, 0x0C);

void LcdString(char *characters)
while (*characters)

void LcdWrite(byte dc, byte data)
digitalWrite(PIN_DC, dc);
digitalWrite(PIN_SCE, LOW);
digitalWrite(PIN_SCE, HIGH);

// gotoXY routine to position cursor
// x - range: 0 to 84
// y - range: 0 to 5
void gotoXY(int x, int y)
LcdWrite( 0, 0x80 | x); // Column.
LcdWrite( 0, 0x40 | y); // Row.

void drawLine(void)
unsigned char j;

for(j=0; j {
gotoXY (j,0);
LcdWrite (1,0x01);

for(j=0; j {
gotoXY (j,5);
LcdWrite (1,0x80);

for(j=0; j {
gotoXY (83,j);
LcdWrite (1,0xff);

for(j=0; j {
gotoXY (0,j);
LcdWrite (1,0xff);


void setup() {
//initialize the lcd
// start serial port:
// give the ethernet module time to boot up:
// start the Ethernet connection:
Ethernet.begin(mac, ip);
// initialize the pins for the conductivity sensor
pinMode(outputPin, OUTPUT);
pinMode(inPin, INPUT);

void loop() {
// check to see if the client is available
if (client.available()) {
char c = client.read();

// if there\'s no net connection, but there was one last time
// through the loop, then stop the client:
if (!client.connected() && lastConnected) {

// if you\'re not connected, and ten seconds have passed since
// your last connection, then connect again and send data:
if(!client.connected() && (millis() - lastConnectionTime > postingInterval)) {
temp_in_kelvin = analogRead(1) * 0.004882812 * 100;
temp_in_fahrenheit = ((temp_in_kelvin - 2.5) * 9 / 5) - 489.67;
String temp_f = dtostrf(temp_in_fahrenheit,5,2,buff); //convert to string
// Put text in Box (using the double to string function built into arduino 1.0 IDE)
LcdString(dtostrf(temp_in_fahrenheit,5,1,buff)); //

Serial.println(temp_f); //print the raw data from temperature pin for troubleshooting
String dataString = String(temp_f); // convert the data to a String to send it to pachube.com:

//initialize or reset the variables for the conductivity sensor calculations to 0
int analogValue=0;
int oldAnalogValue=0;
float returnVoltage=0.0;
float resistance=0.0;
double Siemens;
float TDS=0.0;
// get the conductivity values by passing electrivity between the probes 1cm apart
// (do not get any values if it is pure water - probes are likely out of water)
// this is a crude & imprecise way of getting conductivity but it is a cheap way to monitor variations
while(((oldAnalogValue-analogValue)>threshold) || (oldAnalogValue {
oldAnalogValue = analogValue;
digitalWrite( outputPin, HIGH );
delay(10); // allow ringing to stop
analogValue = analogRead(inPin);
digitalWrite( outputPin, LOW );
//calculate voltage
Serial.print(\"Return voltage = \");
returnVoltage = analogValue * ArduinoResolution;
Serial.println(\" volts\");
//calculate ohms
resistance = ((5.00 * resistorValue) / returnVoltage) - resistorValue;
Serial.println(\" Ohms.\");
// calculate siemens
Siemens = 1.0/(resistance/1000000) + 1700; // calibrate the 1700 value here using conductivity meter
Serial.println(\" microSiemens.\");
//calculate total dissolved solids (TDS)
TDS = 500 * (Siemens/1000);
Serial.println(\" PPM.\");
if(returnVoltage>4.9) Serial.println(\"metal\");
//print values to Nokia 3310 LCD Screen

// get raw temperature reading and buffer values for posting to personal database
int temp = analogRead(1);
sprintf(buffer, \"GET /update.php?temp=%d&volts=%d HTTP/1.1\", temp, analogValue);

//post to personal database if there is a connection
if (client.connect(\"www.vyour_web_site_here.com\", 80)) {
Serial.println(\"personal web site...\");
client.println(\"Host: your_web_site_here.com\");
client.println(\"Connection: close\\n\");
// prepare datastring for posting to pachube.com
String siemen = dtostrf(Siemens,5,2,buff);
dataString += \",\";
dataString += String(siemen);
// store the state of the connection for next time through
// the loop:
lastConnected = client.connected();


// this method makes a HTTP connection to the server:
void sendData(String thisData) {
// if there\'s a successful connection:
if (client.connect(\"www.pachube.com\", 80)) {
// send the HTTP PUT request.
// fill in your feed address here:
client.print(\"PUT /api/yourfeednumber.csv HTTP/1.1\\n\");
client.print(\"Host: www.pachube.com\\n\");
// fill in your Pachube API key here:
client.print(\"X-PachubeApiKey: your_pachube_api_key\\n\");
client.print(\"Content-Length: \");
client.println(thisData.length(), DEC);
// last pieces of the HTTP PUT request:
client.print(\"Content-Type: text/csv\\n\");
client.println(\"Connection: close\\n\");
// here\'s the actual content of the PUT request:
// note the time that the connection was made:
lastConnectionTime = millis();
else {
// if you couldn\'t make a connection:
Serial.println(\"connection failed\");
// this happens periodically-don\'t be alarmed

Urban Road Salt Monitoring

Erin and I have been “citizen volunteer monitoring” the conductivity of streams in and around Madison for the past year. It has been an extremely interesting look into how the salinity levels in streams in the area vary from month-to-month. The study which is being conducted by the WDNR and UW-Extension encourages citizen monitors to measure conductivity levels in streams - especially during the months of December through March when snow events often result in over-salting of roads and sidewalks. If there is “an event”, typically a heavy snowfall or runoff day, we will get a message from the coordinator asking us to pop out to our sites and take a quick sample. Here are a few pictures of us in action:



Salinity in some streams being measured by volunteers throughout the state have reached levels where it could potentially have a harmful effect on the organisms in the streams. Organisms are affected differently by the amount of salt in the water.

We use a $70 conductivity meter, a long plastic pvc pipe, and sampling bottle to take our measurements. The point of the long PVC pipe is to keep us out of the stream and our feet dry - but this doesn’t always work. :) We enjoy monitoring the streams and it is a great way to get outside, assist with scientific research, and to learn about how urban living affects our watersheds.

Home Aquaponics Update: 8 Months In, 7 Lessons Learned

It has been 8 months since we set up our home aquaponics system. What we've learned:

  1. Peppers grow amazingly well in aquaponics
    • We have had great success with jalopenos, green bell peppers, banana peppers, and hungarian wax. Peppers are easily matted down, spread out, and manipulated and will still produce an amazing number of peppers. We have 8 bell peppers on one plant right now and many more buds coming.
  2. Lettuce grows very well but will bolt quickly
    • We have grown a variety of lettuces over the past few months. We have eaten it and not gotten sick contrary to thoughts about having a turtle in an aquaponics environment. We actually feed the lettuce leaves to the turtle and fish as well and he loves it.
  3. Tomatoes get out of hand and need a lot of space
    • Tomatoes will grow like weeds in an aquaponics system. They will quickly take over and shade any smaller plants. It wasn't until we removed our tomato plants that the peppers were able to flourish.
  4. Worms love aquaponics
    • After viewing a youtube video by Australian aquaponics guru Murray Hallam we added a few red wiggler worms from our worm compost bin. The worms have taken off and are thriving in the aquaponics system further helping to break down the fish waste and provide nutrients for the system.
  5. Spiders are a great way to get rid of aphids.
    • Our aphid problem has virtually disappeared - or at least there is now a balance. We contemplated introducing asian beetles to counteract the aphid issue but eventually a few spiders found their way into the pepper plants and took care of them naturally & natively.
  6. Spraying the plants seems to keep the plants healthier & they look better!
    • Almost daily we will spray the leaves of all the plants with water from the fish tank. This seems to keep the leaves healthier, prettier, and improves growth.
  7. Instead of cycling 15 minutes every hour we now have the pump set to go for 15 minutes every two hours.
    • Not only does this save energy but the plants seem to be growing better. I may consider bumping it up to 15 minutes every three hours soon to see how the plants react.

Below are a few pictures of our current batch of plants.