Project showcase
Tanker - A IoT Aquarium

Tanker - A IoT Aquarium © LGPL

An aquarium controller that manages many aspects of the aquarium such as lighting, temperature, water flow, energy consumption, and feeding.

  • 3,771 views
  • 1 comment
  • 8 respects

Components and supplies

10988 01
Temperature Sensor
×1
Arduino ACS712
×1

Apps and online services

About this project

About This Project

This is my final project entry and it is my favorite. I call it tanker. It is a aquarium controller that manages many aspects of the aquarium such as lighting, temperature, water flow, energy consumption, feeding and soon CO2 control. It uses a waterproof temperature probe (DS18B20) as its basis for regulating the temp at 77.0F, a ACS712 to monitor energy consumption, and several relays to control the outlets devices such as the filter, heater and lights are plugged into. I added a small OLED screen via I2C on the case of the Mega to see real time temperature and connection status to Cayenne without a computer or smartphone. The filter relay is setup so if the internet is down, it defaults to "on."

Hacked

I also hacked am inexpensive fish food feeder for controlled automated feeding by adding a relay and repurposing the micro switch inside so the Arduino knows when to stop the rotation of the food drum. I did have a AC solenoid setup for the CO2 control but I decided to remove it due to safety concerns. It was getting too hot in my opinion so I'm looking into other means for that aspect. I also plan to upgrade the power supply to a 2A instead of the 700ma currently. 700 should be enough to run it but I'm having power dropouts when the relays flip and it occasionally makes the ESP8266 drop the connection momentarily. I also may add a dosing pump for the plant food and a real time clock for calculating kWH. The cover and CO2 holders I designed and printed with my Ultimaker 2 clone. Overall I'm very happy with the project. This is my second version of the aquarium controller.

Fish Feeding

When feeding the fish, one presses "feed fish..." and that turns on the "Fish Eating" indicator. That triggers the pump to turn off so the fish can eat in peace and prevent food from wasting by getting into the filter.

Code

#include <CayenneESP8266Shield.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <SimpleTimer.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define ONE_WIRE_BUS 10
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
SimpleTimer timer;
char token[] = "token";
char ssid[] = "wifi ssid";
char password[] = "password here";
#define EspSerial Serial
ESP8266 wifi(EspSerial);
#define OLED_RESET 12
Adafruit_SSD1306 display(OLED_RESET);
#define LOGO16_GLCD_HEIGHT 64
#define LOGO16_GLCD_WIDTH  128
#define foodTrig 9
#define foodThrow 8
#define currentPin A2
#define filterPin 7
const unsigned long sampleTime = 100000UL;
const unsigned long numSamples = 500UL;
const unsigned long sampleInterval = sampleTime / numSamples;
const int adc_zero = 510;
int feedButton;
int filterButton;
int trigger;
float temp;
int feedingTimer;
int currentTimer;
int displayTimer;
bool online = false;
void setup()
{
	display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
	display.clearDisplay();
	for (int16_t i = 0; i<display.height(); i += 2) {
			display.drawCircle(display.width() / 2, display.height() / 2, i, WHITE);
			display.display();
			delay(1);
		}
	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(5,25);
	display.println("tanker 2.1");
	display.display();
	for (int16_t i = 0; i<display.height(); i += 2) {
			display.drawCircle(display.width() / 2, display.height() / 2, i, BLACK);
			display.display();
			delay(1);
		}
	display.clearDisplay();
	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(5,25);
	display.println("tanker 2.1");
	display.display();
	EspSerial.begin(9600);
	delay(10);
	sensors.begin();
	Cayenne.begin(token, wifi, ssid, password);
	feedingTimer = timer.setInterval(600000, feedingDone);
	currentTimer = timer.setInterval(5000, getCurrent);
	displayTimer = timer.setInterval(10000, displayUpdate);
	timer.disable(feedingTimer);
	pinMode(foodTrig, INPUT_PULLUP);
	pinMode(currentPin, INPUT);
	pinMode(filterPin, OUTPUT);
	pinMode(foodThrow, OUTPUT);
	Cayenne.virtualWrite(V3, LOW);
}
void loop()
{
	timer.run();
	Cayenne.run();
	if (filterButton == 1)
		{
			digitalWrite(filterPin, 1);
		}
	else
		{
			digitalWrite(filterPin, 0);
		}
	if (feedButton == 1)
		{
			Cayenne.virtualWrite(V3, HIGH);
			timer.enable(feedingTimer);
			digitalWrite(foodThrow, HIGH);
			delay(500);
			trigger = digitalRead(foodTrig);
			while (trigger == HIGH)
				{
					trigger = digitalRead(foodTrig);
					delay(100);
				}
			digitalWrite(foodThrow, LOW);
			feedButton = 0;
			Cayenne.virtualWrite(V1, LOW);
		}
}
void feedingDone()
{
	Cayenne.virtualWrite(V3, LOW);
	timer.disable(feedingTimer);
}
void getCurrent()
{
	unsigned long currentAcc = 0;
	unsigned int count = 0;
	unsigned long prevMicros = micros() - sampleInterval;
	while (count < numSamples)
		{
			if (micros() - prevMicros >= sampleInterval)
				{
					int adc_raw = analogRead(currentPin) - adc_zero;
					currentAcc += (unsigned long)(adc_raw * adc_raw);
					++count;
					prevMicros += sampleInterval;
				}
		}
	float rms = sqrt((float)currentAcc / (float)numSamples) * (75.7576 / 1024.0);
	int watts = rms * 120;
	Cayenne.virtualWrite(V4, rms);
	Cayenne.virtualWrite(V11, rms);
	Cayenne.virtualWrite(V15, watts);
}
void displayUpdate()
{
	display.clearDisplay();
	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(0,0);
	display.print("Temp:");
	display.print(temp);
	display.setCursor(25,30);
	display.print("Cayenne");
	display.setCursor(30,50);
	if (online == true)
		{
			display.print("Online");
		}
	else
		{
			display.print("OFFLINE!");
		}
	display.display();
}
CAYENNE_CONNECTED()
{
	online = true;
}
CAYENNE_DISCONNECTED()
{
	online = false;
	digitalWrite(filterPin, 0);
}
CAYENNE_OUT(V0)
{
	sensors.requestTemperatures();
	temp = sensors.getTempFByIndex(0);
	Cayenne.virtualWrite(V0, temp);
	Cayenne.virtualWrite(V10, temp);
}
CAYENNE_IN(V1)
{
	feedButton = getValue.asInt();
}
CAYENNE_IN(V5)
{
	filterButton = getValue.asInt();
}

What's Connected

  • Arduino Mega
  • ESP8266-01S
  • Mega Protoboard
  • AC/DC 5V 700ma power supply
  • Dallas 18B20 waterproof temp probe .96"
  • Bi Color OLED display
  • ACS712 current sensor hacked generic fish food auto feeder (added 5v relay inside)
  • 4x 5v Relay board
  • Cascade Canister Filter
  • 100W Aquarium heater
  • 2x Compact Fluorescent Lights

Coming soon: CO2 valve

Connected via Cayenne dashboard: Arduino Mega (for ambient temp & humidity).

Triggers & Alerts

I use several triggers for this project. I use two triggers to control the aquarium heater based on the temp sensor, two triggers to control the filter when the fish are eating, and a notification/alarm if the temp gets out of range.

Scheduling

The scheduler turn on and off the light everyday and also feeds the fish once a day in the evening.

Dashboard Screenshots

Photos of the Project

Video

Code

Code snippet #1Plain text
#include <CayenneESP8266Shield.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <SimpleTimer.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>


#define ONE_WIRE_BUS 10

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

SimpleTimer timer;

char token[] = "token";
char ssid[] = "wifi ssid";
char password[] = "password here";


#define EspSerial Serial
ESP8266 wifi(EspSerial);

#define OLED_RESET 12
Adafruit_SSD1306 display(OLED_RESET);


#define LOGO16_GLCD_HEIGHT 64
#define LOGO16_GLCD_WIDTH  128


#define foodTrig 9
#define foodThrow 8
#define currentPin A2
#define filterPin 7

const unsigned long sampleTime = 100000UL;
const unsigned long numSamples = 500UL;
const unsigned long sampleInterval = sampleTime / numSamples;
const int adc_zero = 510;

int feedButton;
int filterButton;
int trigger;
float temp;
int feedingTimer;
int currentTimer;

int displayTimer;
bool online = false;



void setup()
{

	display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
	display.clearDisplay();

	for (int16_t i = 0; i<display.height(); i += 2) {
			display.drawCircle(display.width() / 2, display.height() / 2, i, WHITE);
			display.display();
			delay(1);
		}


	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(5,25);
	display.println("tanker 2.1");
	display.display();

	for (int16_t i = 0; i<display.height(); i += 2) {
			display.drawCircle(display.width() / 2, display.height() / 2, i, BLACK);
			display.display();
			delay(1);
		}

	display.clearDisplay();
	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(5,25);
	display.println("tanker 2.1");
	display.display();





	EspSerial.begin(9600);
	delay(10);

	sensors.begin();



	Cayenne.begin(token, wifi, ssid, password);

	feedingTimer = timer.setInterval(600000, feedingDone);
	currentTimer = timer.setInterval(5000, getCurrent);

	displayTimer = timer.setInterval(10000, displayUpdate);
	timer.disable(feedingTimer);

	pinMode(foodTrig, INPUT_PULLUP);
	pinMode(currentPin, INPUT);
	pinMode(filterPin, OUTPUT);
	pinMode(foodThrow, OUTPUT);

	Cayenne.virtualWrite(V3, LOW);
}

void loop()
{
	timer.run();
	Cayenne.run();


	if (filterButton == 1)
		{
			digitalWrite(filterPin, 1);
		}
	else
		{
			digitalWrite(filterPin, 0);
		}



	if (feedButton == 1)
		{

			Cayenne.virtualWrite(V3, HIGH);

			timer.enable(feedingTimer);
			digitalWrite(foodThrow, HIGH);
			delay(500);

			trigger = digitalRead(foodTrig);
			while (trigger == HIGH)
				{
					trigger = digitalRead(foodTrig);
					delay(100);
				}
			digitalWrite(foodThrow, LOW);
			feedButton = 0;

			Cayenne.virtualWrite(V1, LOW);
		}
}


void feedingDone()
{
	Cayenne.virtualWrite(V3, LOW);
	timer.disable(feedingTimer);
}


void getCurrent()
{
	unsigned long currentAcc = 0;
	unsigned int count = 0;
	unsigned long prevMicros = micros() - sampleInterval;
	while (count < numSamples)
		{
			if (micros() - prevMicros >= sampleInterval)
				{
					int adc_raw = analogRead(currentPin) - adc_zero;
					currentAcc += (unsigned long)(adc_raw * adc_raw);
					++count;
					prevMicros += sampleInterval;
				}
		}

	float rms = sqrt((float)currentAcc / (float)numSamples) * (75.7576 / 1024.0);
	int watts = rms * 120;
	Cayenne.virtualWrite(V4, rms);
	Cayenne.virtualWrite(V11, rms);
	Cayenne.virtualWrite(V15, watts);

}


void displayUpdate()
{
	display.clearDisplay();
	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(0,0);
	display.print("Temp:");
	display.print(temp);
	display.setCursor(25,30);
	display.print("Cayenne");
	display.setCursor(30,50);
	if (online == true)
		{
			display.print("Online");
		}
	else
		{
			display.print("OFFLINE!");
		}

	display.display();
}



CAYENNE_CONNECTED()
{
	online = true;
}

CAYENNE_DISCONNECTED()
{
	online = false;
	digitalWrite(filterPin, 0);
}







CAYENNE_OUT(V0)
{
	sensors.requestTemperatures();
	temp = sensors.getTempFByIndex(0);
	Cayenne.virtualWrite(V0, temp);
	Cayenne.virtualWrite(V10, temp);
}
CAYENNE_IN(V1)
{
	feedButton = getValue.asInt();
}
CAYENNE_IN(V5)
{
	filterButton = getValue.asInt();
}
Code snippet #2Plain text
#include <CayenneESP8266Shield.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <SimpleTimer.h>
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>


#define ONE_WIRE_BUS 10

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

SimpleTimer timer;

char token[] = "token";
char ssid[] = "wifi ssid";
char password[] = "password here";


#define EspSerial Serial
ESP8266 wifi(EspSerial);

#define OLED_RESET 12
Adafruit_SSD1306 display(OLED_RESET);


#define LOGO16_GLCD_HEIGHT 64
#define LOGO16_GLCD_WIDTH  128


#define foodTrig 9
#define foodThrow 8
#define currentPin A2
#define filterPin 7

const unsigned long sampleTime = 100000UL;
const unsigned long numSamples = 500UL;
const unsigned long sampleInterval = sampleTime / numSamples;
const int adc_zero = 510;

int feedButton;
int filterButton;
int trigger;
float temp;
int feedingTimer;
int currentTimer;

int displayTimer;
bool online = false;



void setup()
{

	display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
	display.clearDisplay();

	for (int16_t i = 0; i<display.height(); i += 2) {
			display.drawCircle(display.width() / 2, display.height() / 2, i, WHITE);
			display.display();
			delay(1);
		}


	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(5,25);
	display.println("tanker 2.1");
	display.display();

	for (int16_t i = 0; i<display.height(); i += 2) {
			display.drawCircle(display.width() / 2, display.height() / 2, i, BLACK);
			display.display();
			delay(1);
		}

	display.clearDisplay();
	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(5,25);
	display.println("tanker 2.1");
	display.display();





	EspSerial.begin(9600);
	delay(10);

	sensors.begin();



	Cayenne.begin(token, wifi, ssid, password);

	feedingTimer = timer.setInterval(600000, feedingDone);
	currentTimer = timer.setInterval(5000, getCurrent);

	displayTimer = timer.setInterval(10000, displayUpdate);
	timer.disable(feedingTimer);

	pinMode(foodTrig, INPUT_PULLUP);
	pinMode(currentPin, INPUT);
	pinMode(filterPin, OUTPUT);
	pinMode(foodThrow, OUTPUT);

	Cayenne.virtualWrite(V3, LOW);
}

void loop()
{
	timer.run();
	Cayenne.run();


	if (filterButton == 1)
		{
			digitalWrite(filterPin, 1);
		}
	else
		{
			digitalWrite(filterPin, 0);
		}



	if (feedButton == 1)
		{

			Cayenne.virtualWrite(V3, HIGH);

			timer.enable(feedingTimer);
			digitalWrite(foodThrow, HIGH);
			delay(500);

			trigger = digitalRead(foodTrig);
			while (trigger == HIGH)
				{
					trigger = digitalRead(foodTrig);
					delay(100);
				}
			digitalWrite(foodThrow, LOW);
			feedButton = 0;

			Cayenne.virtualWrite(V1, LOW);
		}
}


void feedingDone()
{
	Cayenne.virtualWrite(V3, LOW);
	timer.disable(feedingTimer);
}


void getCurrent()
{
	unsigned long currentAcc = 0;
	unsigned int count = 0;
	unsigned long prevMicros = micros() - sampleInterval;
	while (count < numSamples)
		{
			if (micros() - prevMicros >= sampleInterval)
				{
					int adc_raw = analogRead(currentPin) - adc_zero;
					currentAcc += (unsigned long)(adc_raw * adc_raw);
					++count;
					prevMicros += sampleInterval;
				}
		}

	float rms = sqrt((float)currentAcc / (float)numSamples) * (75.7576 / 1024.0);
	int watts = rms * 120;
	Cayenne.virtualWrite(V4, rms);
	Cayenne.virtualWrite(V11, rms);
	Cayenne.virtualWrite(V15, watts);

}


void displayUpdate()
{
	display.clearDisplay();
	display.setTextSize(2);
	display.setTextColor(WHITE);
	display.setCursor(0,0);
	display.print("Temp:");
	display.print(temp);
	display.setCursor(25,30);
	display.print("Cayenne");
	display.setCursor(30,50);
	if (online == true)
		{
			display.print("Online");
		}
	else
		{
			display.print("OFFLINE!");
		}

	display.display();
}



CAYENNE_CONNECTED()
{
	online = true;
}

CAYENNE_DISCONNECTED()
{
	online = false;
	digitalWrite(filterPin, 0);
}







CAYENNE_OUT(V0)
{
	sensors.requestTemperatures();
	temp = sensors.getTempFByIndex(0);
	Cayenne.virtualWrite(V0, temp);
	Cayenne.virtualWrite(V10, temp);
}
CAYENNE_IN(V1)
{
	feedButton = getValue.asInt();
}
CAYENNE_IN(V5)
{
	filterButton = getValue.asInt();
}

Comments

Similar projects you might like

IoT Blink - Getting started with IoT

Project showcase by AppShed Support

  • 4,763 views
  • 1 comment
  • 22 respects

IOT Based Water Manangment Flownomo and D’Smart_Switch

Project showcase by dArtofScience

  • 2,972 views
  • 0 comments
  • 2 respects

IoT Fireplace

Project showcase by vapor83

  • 1,113 views
  • 0 comments
  • 2 respects

Aquarium Auto Refill With Arduino

Project showcase by RobotGeek Projects Team

  • 8,631 views
  • 0 comments
  • 16 respects

IoT Aquarium Light Controller

Project in progress by Neto Carrer

  • 10,612 views
  • 2 comments
  • 27 respects

Making a Cheap Aquarium Parameters Controller

Project tutorial by Boisse Martin

  • 8,891 views
  • 4 comments
  • 21 respects
Add projectSign up / Login