Introduction
With this project I wanted to keep things as simple as as they can be. The instructions that you will find below are usable for any newcomer that joins the IOT world and want to build something. Is not necessarily to build a IOT Aquarium. For example a cat or a dog feeder can be build taking in consideration the same steps but at a different scale.
I wanted to build this project since some time now but due to various reasons I didn't do it. I was looking also on thingverse.com for something already built ones but none that I found was ok for my needs. Finally, winning a free Smart 7688 DUO board at "The Future of Smart Home and Office is Here! " contest has given me the needed motivation to start building this project.
For the moment is complying with the following specifications:
- Scheduled feeding time
- Keep track of temperature inside my aquarium, wich between me and you is very small and during time I had several casualties among the fishes due to temperature issues
- Schedule fish tank light
- Alert if food is empty
Smart 7688 DUO
LinkSmart 7588 DUO is a really nice board. It has an almost complete arduino linked with a linux computer. So what could you ask for more? I compare it with a Arduino Micro and with a home made clone of Adafruit HUZZAH. You can see that in terms of shape there is not much difference, but 7688 DUO is the clear winner here because of it's capabilities.
I know that this is not the best comparison that I could make and I should do a comparison between 7688 DUO, onion omega2, CHIP and so on...but my point is that you can buy the 7688 DUO with 16$ when a arduino micro cost 18$ and adafruit huzzah cost 10$. Ok Omega 2 cost 5$ but you don't have the arduino inside and you are missing a USB power port.
Only thing that I don't like about this board is that I expected it to be a little bit faster, but for a project like the one I'm describing here is more than enough.
Ok enough trying to convince you that 7688 is a really cool board for development and let's move on to our project.
Step 1: Configuring Smart 7688 DUO
To configure your board, you will need to follow the below steps in the mentioned order:
1. First connection and root password setup
2.Configure the board in station mode (connecting to your wifi)
3.Configure Arduino to recognize the 7588 duo board
4.Assure that you are able to connect remotely with SSH. For windows I recommend Putty and WinSCP. In case you are running Linux this should be already integrated in Bash. In windows, if you would like to run ssh from Visual Studio Code terminal you can install OpenSSH
Step 2: 3D printing needed parts
Complete design of the IOT Aquarium controler was done by me in Fusion 360. I won't try to convince you again that Fusion 360 is a really great app for makers. ;-)
Being a completely new design I'm aware that it my have some flaws that I had miss, but if you share them with me I'll be more than happy to apply the needed corrections.
The IOT Aquarium controller is made of 3 3D printed parts:
- main body(with red in the above picture)
- feeding mechanism (brown and yellow)
- neopixel strip holder (gray)
I print everything with a FlashForge Finder 3d printer. Infill of 50%/Layer height 0.18mm. If you want the body to have a better quality than mine I suggest using supports for the wire guiders.
You can find all 3d printed files here http://www.thingiverse.com/thing:2137857 or in the download section of this project. If you don't have a 3D printer you can always use a 3D printing service or ask a friend that have a 3D printer.
Step 3: Schematics & Soldering
Schematic is not that difficult to understand. We have the stepper motor driver board IN1-IN4 connected to D9-D13 from 7688 DUO. Then we have a light barrier, made from a red 5mm LED and a photoresistor. The LDR is connected to A5 on our development board. DS18b20 is connected to pin D3 and neopixel strip is connected to D8 pin.
Everything is powered from the 5v pin. Why I chose this is because when looking at the Smart 7688 DUO schematic I see a direct link to the power micro usb. This means that if you power everything from a microusb phone charger it should work ok.
When soldering please take in consideration the best layout that suits you. My approach you can see in the picture below.
Also what I will suggest is to use a colored pref-board with metallic vias. Soldering is much easier on this types of pref-boards.
Be careful to leave the wires for the LED, LDR and neopixel strip a little bit longer to reach from the board to the fixation holes on the 3dprinted parts.
Step 4: Put everything together
Feeder mechanism
Assemble the feeder mechanism according to below photos
In the end if everything went ok you should be able to do this:
Assembly the stepper motor
Next you need to fix the stepper motor with 2pcs 15x3mm screws and nuts. You will need to use the long plier because one area is a little bit difficult to reach for fixing the nut.
Mounting the brain
Slide in the electronics and fix the LDR together with the red LED in the main body with hot glue.
After this is done the only thing remaining is fixing the loose wires with some cable ties like in the below pictures.
Step 4: Programming the brain
Programming this board is not that hard. We will start with the programing of ATMEGA32U4 done with ArduinoIDE.
Arduino part of the 7688DUO
/*************** 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);
}
Linux part of the 7688DUO
First connect with PuTTY to your board.
Next launch the following commands
opkg update
If you want to go hardcore you can install nano and program evrything directly on the board
opkg install nano
and then we need to install blynk node.js library and a time keeping library called moment.
npm install -g node-gyp
npm install -g blynk-library
npm install -g sntp
npm install moment
npm install moment-timezone
Next is time to write our node.js code. I build everything with Visual Studio Code. You can do the same because is a cross platform editor. Use SCP or WinSCP for transferring files over SSH if you don't want to edit with nano.
Create a new file called IOTAquarium.js with your prefered editor. Here is the code you should write inside:
//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);
});
In the above code you will have to change 2 lines of code:
- here you should put your authentication key given by your blynk project
var AUTH = '98fd3b118cc84d8e9e54f90ed085449b';//you will get this key from your blynk project in your phone app
-in case you are not from romania, here you should put your timezone
var time = moment.tz(now, "Europe/Bucharest");//change here if you are not living in ROMANIA
Next, save everything and you can run a test with the following command:
node --harmony IOTAquarium.js
Remaining now is to make sure that our code is launching every time when our Smart 7688 DUO board starts and that it continues to run in case of unexpected stoppages. For this we will prepare a script that will run every minute. To make this happen we will use cron.
Create a new file with your prefered editor called IOTAquarium. This file will contain below code:
#! /bin/bash
case "$( pgrep -f "node --harmony IOTAquarium.js" | wc -w)" in
0) echo "Restarting IOTAquarium: $(date)" >> /var/log/IOTAquarium.txt
node --harmony IOTAquarium.js &
;;
1) # all ok
;;
esac
This will check if our program is running and if not it will launch it.
Make the script executable:
chomd +x IOTAquarium
Execute the following commands to create a new cron job:
crontab -e
now press i to start editing and add the following line inside
* * * * * sh /root/IOTAquarium
now press ESC and after :w to save and :q to quit vi editor.
It's time to enable cron with the folowing commands:
/etc/init.d/cron start
/etc/init.d/cron enable
Android/iOS app with Blynk
Most easiest to show you how to create the blynk app is by below video:
Step 5: Sit back and relax watching your new IOT Aquarium
Planned future improvements/Improvement ideeas
- increase torque on 28BYJ-48 http://www.jangeox.be/2013/10/change-unipolar-28byj-48-to-bipolar.html
- implement a printed PCB
- add water heater
- automatic food ordering
Comments