Hackster is hosting Hackster Holidays, Ep. 6: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Monday!Stream Hackster Holidays, Ep. 6 on Monday!
Pop Gheorghe
Published © GPL3+

IOT Aquarium controller

An opensource 3D printed aquarium controller with IOT capabilities.

IntermediateFull instructions provided2 hours10,138

Things used in this project

Hardware components

28BYJ-48 5V
×1
ULN2003 5-12V Stepper Motor Driver Board
×1
Photo resistor
Photo resistor
×1
5 mm LED: Red
5 mm LED: Red
×1
50x70mm Prototyping PCB
×1
DS18B20 waterproof
×1
NeoPixel strip
NeoPixel strip
×1
Jumper wires (generic)
Jumper wires (generic)
×1
2pcs 3x15mm screw and nuts
×1
Resistor 10k ohm
Resistor 10k ohm
×1
Resistor 220 ohm
Resistor 220 ohm
×1
Resistor 4,7k ohm
×1

Software apps and online services

Microsoft Visual Studio Code
Arduino IDE
Arduino IDE
Blynk
Blynk

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Screwdriver set
Pliers set

Story

Read more

Custom parts and enclosures

main_body

Main body of the IOT Aquarium controller

Feeding mechanism P1

Feeding mechanism P2

Feeding mechanism P3

Feeding mechanism small P3

neopixel_strip

Schematics

iotaquarium_sckxOcBI84.fzz

Code

IOTAquarium.ino

C/C++
/***************  IOTAquarium   ***********************
  This is a project to help you manage your fish tank. Folowing functions are currently implemented:
      -fish feed
      -empty food tank allert
      -water temperature monitoring
      -light controler(using 5v neopixel)

  Used pins:
  28BYJ-48 motor/ULN2003A (IN1,IN2,IN3,IN4) <-> Smart7688 DUO(13,12,11,10)
  DS18B20 (red,yellow,gray) <-> Smart7688 DUO(5V,D3,GND)+4,7k betwen 5V and DATA
  LDR (VCC,data) <-> Smart7688 DUO(5V,A5)+10k betwen GND and DATA
 ******************************************************/

#include <stdlib.h>

//******  Steper library
#include <Stepper.h>//include stepper library for moving the feeder
// change this to fit the number of steps per revolution for your mottor

//configure the steper on the connected pins
const int s1 = 13;
const int s2 = 12;
const int s3 = 11;
const int s4 = 10;
int del = 2000;//delay betwen steps
//*******************************

//******  LDR
int ldrPin = A5; //pin on wich is connected the LDR
int ldrValue = 0; //initial value for the LDR value
//*******************************

//****** Temperature
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 3// Data wire is plugged into pin 3
OneWire oneWire(ONE_WIRE_BUS);// Setup a oneWire instance to communicate with any OneWire devices
DallasTemperature sensors(&oneWire);// Pass our oneWire reference to Dallas Temperature.
//*******************************

//****** Neopixel
#include <Adafruit_NeoPixel.h>
#define PIN            8// We use pin * for comunicating with 5v neopixel strip
#define NUMPIXELS      16//curently I had only 4 neopixel. I need here a improvement
Adafruit_NeoPixel light = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);// When we setup the NeoPixel library, we tell it how many pixels, and which pin to use to send signals.
//colors
byte R = 0;
byte G = 0;
byte B = 0;
//intensity
byte I = 255;
//*******************************

//****** Serial port comunication
#include <SerialCommand.h>
SerialCommand sCmd;     //This SerialCommand object will be used to comunicate betwen Atmega32U4 and MT7688
//*******************************


//****** Keeping up with the time
#include <TimeLib.h>
#include <TimeAlarms.h>
AlarmId a1, a2, a3;//needed alarms objects
String days_to_feed = "";//variable needed for understanding in wich days feeding needs to happen

void setup() {
  Serial.begin(9600); //sets serial port for communication
  Serial1.begin(57600);  // open internal serial connection to MT7688AN
  sensors.begin();  // start up the library
  light.setBrightness(255);// start with full brighness
  light.begin(); // this initializes the NeoPixel library.

  // Define recognized commands callbacks for SerialCommand
  sCmd.addCommand("FEED",   feed);          // function to feed the fish
  sCmd.addCommand("L",     lights);  // function to start/stop the lights and establish color with intensity
  sCmd.addCommand("T",     times);  // time sincronization
  sCmd.addCommand("A",     alarms);  // alarm setup
  sCmd.setDefaultHandler(unrecognized);      // handler for command that isn't matched  (says "What?")

  //allocate functions to each alarm
  a1 =  Alarm.alarmRepeat(1, 1, 1, light_on);
  a2 =  Alarm.alarmRepeat(1, 1, 1, light_off);
  a3 =  Alarm.alarmRepeat(1, 1, 1, feed_me);
  //configure stepper pins as output
  pinMode(s1, OUTPUT);
  pinMode(s2, OUTPUT);
  pinMode(s3, OUTPUT);
  pinMode(s4, OUTPUT);

}

void loop() {
  ldrValue = analogRead(ldrPin); // read the value from the sensor
  sensors.requestTemperatures(); // send the command to get temperatures
  String tmp = "";//temporary variable to make the message that will be sent over serial
  tmp = tmp + sensors.getTempCByIndex(0);
  tmp = tmp + ";";
  tmp = tmp + ldrValue;
  Serial1.println(tmp); //send the values to the MT7688
  sCmd.readSerial();     // see if MT7688 is telling us something
  //digitalClockDisplay();//just for debugging if you need to see what time it is
  Alarm.delay(5); // wait a little for the alarms to work

}

//function to set color and brightness for the neopixel
void becuri(byte R, byte G, byte B, byte J) {
  for (int i = 0; i < 16; i++) {
    light.setPixelColor(i, light.Color(0, 0, 0)); //establish neopixel colors
  }


  for (int i = 0; i < map(J, 0, 100, 0, 16); i++) {
    // pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
    light.setPixelColor(i, light.Color(R, G, B)); //establish neopixel colors
  }
  light.show(); // this sends the updated pixel color to the hardware.


}

//function to move the motor and feed the fish
void feed() {

  for (int i = 0; i <= 80; i++) {
    forwardsFull();
  };
  for (int i = 0; i <= 80; i++) {
    backwardsFull();
  };
  motorOff();

}

//function for lights with 4 parameters that reads the message from MT7688 and makes the Atmega32U4 to act acordingly
void lights() {
  int aNumber, bNumber, cNumber, intens;
  char *arg;
  arg = sCmd.next();
  if (arg != NULL) {
    aNumber = atoi(arg);    // converts a char string to an integer
    //Serial.print("First argument was: "); //only debuging
    //Serial.println(aNumber); //only debuging
  }
  else {
    aNumber = 0;
  }

  arg = sCmd.next();
  if (arg != NULL) {
    bNumber = atol(arg);
    // Serial.print("Second argument was: ");
    // Serial.println(bNumber);
  }
  else {
    bNumber = 0;
  }

  arg = sCmd.next();
  if (arg != NULL) {
    cNumber = atol(arg);
    // Serial.print("3 argument was: ");
    // Serial.println(cNumber);
  }
  else {
    cNumber = 0;
  }

  arg = sCmd.next();
  if (arg != NULL) {
    intens = atol(arg);
    // Serial.print("4 argument was: ");
    //  Serial.println(intens);
  }
  else {
    intens = 0;
  }
  R = aNumber; G = bNumber; B = cNumber; I = intens;
  becuri(R, G, B, I);

}

//function for arduino time sincronization
void times() {
  time_t bNumber;
  char *arg;

  arg = sCmd.next();
  if (arg != NULL) {
    int tmp;
    bNumber = 0;
    for (int i = 0; i < 10; i++) {
      tmp = (int)arg[i] - 48;
      bNumber = (10 * bNumber) + tmp;
    }
  }
  else {
    bNumber = 0;
  }
  setTime(bNumber);
  //Serial.print(bNumber);
}

//Get the defined alarms from MT7688 and set them in our arduino time alarm library
void alarms() {
  int aNumber;
  long bNumber, cNumber;
  String dNumber;
  char *arg;

  arg = sCmd.next();
  if (arg != NULL) {
    aNumber = atoi(arg);
  }
  else {
    aNumber = 0;
  }
  arg = sCmd.next();
  if (arg != NULL) {
    bNumber = atol(arg);
  }
  else {
    bNumber = 0;
  }


  arg = sCmd.next();
  if (arg != NULL) {
    cNumber = atol(arg);
  }
  else {
    cNumber = 0;
  }

  arg = sCmd.next();
  if (arg != NULL) {
    dNumber = arg;
  }
  else {
    dNumber = "";
  }
  byte h, m, s;



  if (aNumber == 1)
  {
    Alarm.free(a1);
    Alarm.free(a2);
    h = bNumber / (60 * 60);
    m = (bNumber % (60 * 60)) / 60;
    s = ((bNumber % (60 * 60)) % 60);
    Alarm.disable(a1);
    a1 =  Alarm.alarmRepeat(h, m, s, light_on);
    Alarm.enable(a1);
    //Serial.println(Alarm.read(a1));
    //Serial.print(h);
    //Serial.print(" ");
    //Serial.print(m);
    //Serial.print(" ");
    //Serial.println(s);
    h = cNumber / (60 * 60);
    m = (cNumber % (60 * 60)) / 60;
    s = ((cNumber % (60 * 60)) % 60);
    //Serial.print(h);
    //Serial.print(" ");
    //Serial.print(m);
    //Serial.print(" ");
    //Serial.println(s);
    Alarm.disable(a2);
    a2 =  Alarm.alarmRepeat(h, m, s, light_off);
    Alarm.enable(a2);
    //Serial.println(Alarm.read(a2));
  }

  else if (aNumber == 2)
  {
    h = bNumber / (60 * 60);
    m = (bNumber % (60 * 60)) / 60;
    s = ((bNumber % (60 * 60)) % 60);
    days_to_feed = dNumber;
    //Serial.print(h);
    //Serial.print(" ");
    //Serial.print(m);
    //Serial.print(" ");
    //Serial.println(s);
    Alarm.disable(a3);
    a3 =  Alarm.alarmRepeat(h, m, s, feed_me);
    Alarm.enable(a3);


  }

}

//ligt ON at alarm trigger
void light_on() {
  if (((R == 0) & (G == 0) & (B == 0)) || (I == 0)) {
    becuri(255, 255, 255, 100);
  }
  else {
    becuri(R, G, B, I);
  }
  //Serial.println(R);
  //Serial.println(G);
  //Serial.println(B);
  //Serial.println(I);
  //Serial.println("ON");
}

//ligt OFF at alarm trigger
void light_off() {
  becuri(0, 0, 0, 0);
  //Serial.println("OFF");
}

//feed at alarm trigger
void feed_me() {
  String tmp = "";
  if (weekday() == 1) {
    tmp = "7";
  }
  else
  {
    tmp = String(weekday() - 1);
  }
  if (find_text(tmp, days_to_feed) != -1) {
    feed();
  }
  //Serial.println("FEED");

}
// This gets set as the default handler, and gets called when no other command matches.
void unrecognized(const char *command) {
  Serial.println("What?");
}


//3 helping aid functions
void digitalClockDisplay() {
  // digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.println();
}

void printDigits(int digits) {
  Serial.print(":");
  if (digits < 10)
    Serial.print('0');
  Serial.print(digits);
}


int find_text(String needle, String haystack) {
  int foundpos = -1;
  for (int i = 0; i <= haystack.length() - needle.length(); i++) {
    if (haystack.substring(i, needle.length() + i) == needle) {
      foundpos = i;
    }
  }
  return foundpos;
}
//stop motor
void motorOff() {
  digitalWrite(s1, LOW);
  digitalWrite(s2, LOW);
  digitalWrite(s3, LOW);
  digitalWrite(s4, LOW);
}
//move backward
void backwardsFull() {

  digitalWrite(s1, HIGH);
  digitalWrite(s2, HIGH);
  digitalWrite(s3, LOW);
  digitalWrite(s4, LOW);
  delayMicroseconds(del * 2);

  digitalWrite(s1, LOW);
  digitalWrite(s2, HIGH);
  digitalWrite(s3, HIGH);
  digitalWrite(s4, LOW);
  delayMicroseconds(del * 2);

  digitalWrite(s1, LOW);
  digitalWrite(s2, LOW);
  digitalWrite(s3, HIGH);
  digitalWrite(s4, HIGH);
  delayMicroseconds(del * 2);

  digitalWrite(s1, HIGH);
  digitalWrite(s2, LOW);
  digitalWrite(s3, LOW);
  digitalWrite(s4, HIGH);
  delayMicroseconds(del * 2);

}
//move forward
void forwardsFull() {

  digitalWrite(s1, LOW);
  digitalWrite(s2, LOW);
  digitalWrite(s3, LOW);
  digitalWrite(s4, HIGH);
  delayMicroseconds(del * 2);

  digitalWrite(s1, LOW);
  digitalWrite(s2, LOW);
  digitalWrite(s3, HIGH);
  digitalWrite(s4, LOW);
  delayMicroseconds(del * 2);

  digitalWrite(s1, LOW);
  digitalWrite(s2, HIGH);
  digitalWrite(s3, LOW);
  digitalWrite(s4, LOW);
  delayMicroseconds(del * 2);

  digitalWrite(s1, HIGH);
  digitalWrite(s2, LOW);
  digitalWrite(s3, LOW);
  digitalWrite(s4, LOW);
  delayMicroseconds(del * 2);

}

IOTAquarium.js

Java
 //include moment module for working with time 
var moment = require('moment-timezone'); 
var q = require('moment'); 
//include blynk module to be able to comunicate with our app 
var Blynk = require('blynk-library'); 
var AUTH = '98fd3b118cc84d8e9e54f90ed085449b';//you will get this key from your blynk project in your phone app 
var blynk = new Blynk.Blynk(AUTH);//create the link betwen your app and the IOT device 
//include serial module to be able to facilitate the comunication betwen MT7688 and ATMEGA32U4  
var com = require("serialport"); 
//default serial port for this comunication with the configuration parameters 
var serialPort = new com.SerialPort("/dev/ttyS0", { 
 baudrate: 57600, 
 parser: com.parsers.readline('\r\n') 
}); 
//If everything is ok write on the console that port is open 
serialPort.on('open', function () { 
 console.log('Port open...'); 
}); 
//else write a error message 
serialPort.on('error', function () { 
 console.log('Error...'); 
}); 
var t = "";//variable to keep time 
var f = 0;//variable to know when to feed 
var R = 0, 
 B = 0, 
 G = 0, 
 I = 255;//RGB and Intensity values 
var ziua;//day to feed 
var v0 = new blynk.VirtualPin(0);//light 
var v1 = new blynk.VirtualPin(1);//feeding schedlue 
var v2 = new blynk.VirtualPin(2);//feed buttom 
var v3 = new blynk.VirtualPin(3);//display temperature 
var v4 = new blynk.VirtualPin(4);//brigness 
var v5 = new blynk.VirtualPin(5);//lighting color 
var v6 = new blynk.VirtualPin(6);//status of the food 
//include sntp module to be sync our clock and know exact time from the internet 
var Sntp = require('sntp'); 
// Request server time  
var options = { 
 host: 'pool.ntp.org', // Defaults to pool.ntp.org  
 port: 123, // Defaults to 123 (NTP)  
 resolveReference: true, // Default to false (not resolving)  
 timeout: 1000 // Defaults to zero (no timeout)  
}; 
Sntp.time(options, function (err, time) { 
 if (err) { 
   console.log('Failed: ' + err.message); 
 } 
}); 
function time_su() { 
 //**********************************/ 
 now = Sntp.now(); 
 var time = moment.tz(now, "Europe/Bucharest");//change here if you are not living in ROMANIA 
 var time_string = time.format(); 
 var ta = now + q.parseZone(time_string).utcOffset() * 60 * 1000; 
 serialPort.write("T " + ta + "\r\n"); 
 //**convert time to local timezone and send it to ATMEGA32U4 so that we can use it in our arduino schetc */ 
 var date = new Date(); 
//************make an email alert once per day in case food container is empty */ 
 if( (f>=1000)&&(ziua!=date.getDate())) 
 { 
   blynk.email("IOTAquarium","It's time to replace your fish food container!");//here is the email alert message 
   ziua=date.getDate(); 
 } 
 setTimeout(time_su, 60000);//repeate this every 5 minute 
} 
time_su(); 
//below is the code that decide what to do when interacting with various widgets from our blynk app  
v0.on('write', function (param) { 
 //console.log(param); 
 serialPort.write("A 1 " + param[0] + " " + param[1] + " " + param[3] + "\r\n"); 
}); 
v1.on('write', function (param) { 
 serialPort.write("A 2 " + param[0] + " " + "12" + " " + param[3] + "\r\n"); 
}); 
v2.on('write', function (param) { 
 serialPort.write("FEED\r\n"); 
}); 
v3.on('read', function () { 
 v3.write(t); 
}); 
v6.on('read', function () { 
 if (f > 1000) { 
 v6.write( "EMPTY"); 
 } else { 
  v6.write("OK"); 
 } 
}); 
v4.on('write', function (param) { 
 I = param[0]; 
 serialPort.write("L " + R + " " + G + " " + B + " " + I + "\r\n"); 
 //console.log(R, G, B, I); 
}); 
v5.on('write', function (param) { 
 R = param[0]; 
 G = param[1]; 
 B = param[2]; 
 serialPort.write("L " + R + " " + G + " " + B + " " + I + "\r\n"); 
 //console.log(R, G, B, I); 
}); 
serialPort.on('data', function (data) { 
 var arr = data.split(";"); 
 t = arr[0]; 
 f = arr[1]; 
 parseInt(f, 10); 
}); 

Credits

Pop Gheorghe

Pop Gheorghe

4 projects • 4 followers

Comments