Introduction: EAL - Coral Feeder Connected

This instructable is a description of my Arduino controlled coral feeder which has taken a step further. It is now able to connect to a network drive (NAS) via wifi and log data to a database. I also made a Windows application making the user able to retrieve the data, and have them represented in an easy readable manner.

As it is now it is only able to log the feedings, but potentially it will log temperature, salinity, and many many more parmeters which is normally measured manually in a reef tank.

When writing this instructable, i assume the reader has looked at my other instructable, and has the basic knowledge of what the coral feeder is.

Step 1: Parts List

  1. Coral Feeder (see my other instructable).
  2. 1k Ohm resistor.
  3. 2k Ohm resistors.
  4. ESP8266 Wifi Module.
  5. 3.3V voltage limiter/regulator.
  6. NAS drive (or computer) with MYSQL database or similar.
  7. Google (not really a part for the build, but as it's a fairly complex build with somewhat complex software relations, you will run into problems at some point)

Step 2: New Coral Feeder Features

The coral feeder module got a few new features along with the upgrade. I have added some more choices in the menu. You can now get a reading showing your coral feeder IP-address, and have the option to reboot the WiFi module should the coral feeder loose connection to your network for some reason.

During booting and when transmitting data to the NAS-drive, the display will show various informative messages concerning the connectivity. It will tell that it is transmitting and if TCP connection succeeds or errors occur. It will also tell if the feeder has success connecting to your network etc. All display messages can be found in the Arduino code voids: Wifi_Setup() and Transmit().

Note: Depending on your home network IP address class (my coral feeder IP is 10.0.0.48(static)), you will probably need to change som small bits in the Arduino code to make the IP reading right. As i take out a substring with the length of my own IP you should change it to whatever your IP length is. So if your IP is 192.168.0.200 you should change the last number to 88 as your IP is 4 digits longer than mine.

        //get connection information from ESP8266
        //module and take IP-substring address out of string
        if (ipRead == true)
        {
          IP = (wifi.getLocalIP().c_str());
          ipRead = false;
        }
        lcd.print("IP: ");
        lcd.print(IP.substring(75, 84));        

Step 3: The Arduino Code

Below you will find the code for the Coral Feeder.

Four libraries is necessary for the code to work and must be downloaded to the Arduino IDE software.

  • LiquidCrystal for the LCD
  • TimerOne for the timer interrupt making the clock work
  • Servo for the servomotor
  • ESP8266 for the Wifimodule.
//Prototyping
void Clock();
void Feed();
void Wifi_Setup();
void Transmit();
void No_Menu_Default();


//Include library codes:
#include LiquidCrystal.h
#include TimerOne.h
#include Servo.h
#include "ESP8266.h"




//Definitions for wifi and SQL host
#define SSID        "MyHomeNetworkName"
#define PASSWORD    "MyHomeNetworkPassword"
#define HOST_NAME   "MyNASdriveIPAddress"
#define HOST_PORT   (80) //NASdrive webserverport --NOT-- SQL port, SQL port goes into PHP script.


//Variables declaration
int S = 1;
int M;
int H;
int TBF = 0;
int Menu_number = 2;
int pos_in = 76;
int pos_out = 118;
int H_feed = 0;
unsigned int FPD = 0;
bool Feeding_allowed = HIGH;
const bool Click = 1;
long IdleCounter = 0;
long IdleStatic = 120000;
String IP;
bool ipRead;
bool reboot_confirm;




char *transmit_data = "GET /feeding.php?value=Booting HTTP/1.1\r\nHost: MyNASdriveIPAddress\r\nConnection: close\r\n\r\n";




//Inputs/Outputs
const int Feed_on_off = 33;
const int Menu = 32;
const int Up = 31;
const int Down = 30;
const int Shaker = 10;


//Define serial ports for ESP8266 Wifi module and set baud communication speed
ESP8266 wifi(Serial2);
long baud = 115200;


//Create servo motor "Feeder"
Servo Feeder;


//Initialize the LCD library with the numbers of the interface pins
LiquidCrystal lcd(28, 26, 5, 4, 3, 2);




void setup()
{
  //Open serial comm with "baud" speed
  Serial2.begin(baud);




  // set up the LCD's number of columns and rows:
  lcd.begin(16, 2);


  // Initialize timer1, and set a ~1 second period, corrected for program read time
  // and attach the timer service routine interrupt to seperate void (Clock)
  Timer1.initialize(992275);
  Timer1.attachInterrupt(Clock);


  //Buttons declaration
  pinMode(Feed_on_off, INPUT_PULLUP);
  pinMode(Menu, INPUT_PULLUP);
  pinMode(Up, INPUT_PULLUP);
  pinMode(Down, INPUT_PULLUP);


  //Shaker motor declaration
  pinMode(Shaker, OUTPUT);


  //Servomotor declaration & initialization
  Feeder.attach(24);
  Feeder.write(pos_in);


  //ESP8266 Wifi module startup by jump to void
  Wifi_Setup();




}
void loop() //Main program
{
  //Back to default at 2 min idle
  if (digitalRead(Menu) == LOW || digitalRead(Up) == LOW || digitalRead(Down) == LOW && Menu_number != 0 )
  {
    IdleStatic = millis() + 120000;
  }
  IdleCounter = millis();
  if (IdleStatic < IdleCounter && IdleCounter < IdleStatic + 50)
  {
    Menu_number = 0;
    lcd.clear();
  }


  //Enter Menu
  if (digitalRead(Menu) == LOW)
  {
    delay(200);
    Menu_number = Menu_number + 1;
    lcd.clear();
  }


  //Reset Menu_number
  if (Menu_number == 6)
  {
    Menu_number = 0;
  }


  //Menu switch case
  switch (Menu_number)
  {
    case 1: //Forcefeed
      {
        //LCD info
        lcd.setCursor(0, 0);
        lcd.print("Menu: Forcefeed ");
        lcd.setCursor(0, 1);
        lcd.print("Press Feed      ");


        //Forcefeed
        if (digitalRead(Feed_on_off) == LOW)


        {
          delay(200);
          transmit_data = "GET /feeding.php?value=Manual_feed HTTP/1.1\r\nHost: MyNASdriveIPAddress\r\nConnection: close\r\n\r\n";
          Feed();
        }


      }
      break;


    case 2: //Set clock
      {
        //LCD info
        lcd.setCursor(0, 0);
        lcd.print("Menu: Set clock");


        //LCD CLOCK




        if (H < 10)
        {


          lcd.setCursor(6, 1);
          lcd.print("0");
          lcd.setCursor(7, 1);
          lcd.print(H);
        }
        else
        {
          lcd.setCursor(6, 1);
          lcd.print(H);
        }
        if (M < 10)
        {
          lcd.setCursor(8, 1);
          lcd.print(":");
          lcd.print("0");
          lcd.setCursor(10, 1);
          lcd.print(M);
        }
        else
        {
          lcd.setCursor(8, 1);
          lcd.print(":");
          lcd.print(M);
        }


        //Set clock
        //Hours
        if (digitalRead(Up) == LOW)
        {
          delay(200);
          H = H + 1;
        }
        //Minutes
        if (digitalRead(Down) == LOW)
        {
          delay(200);
          M = M + 1;


        }
        if (H > 23)
        {
          H = 0;
        }
        if (M > 59)
        {
          M = 0;
        }


      }
      break;
    case 3: //Feedings per day (FPD)
      {
        //LCD info
        lcd.setCursor(0, 0);
        lcd.print("Menu: Set FPD");
        lcd.setCursor(0, 1);
        lcd.print("Daily Feeds");


        if (FPD < 10)
        {
          lcd.setCursor(15, 1);
          lcd.print(FPD);
          lcd.setCursor(14, 1);
          lcd.print(" ");


        }
        if (FPD > 9)
        {
          lcd.setCursor(14, 1);
          lcd.print(FPD);


        }


        //Adjust FPD up
        if (digitalRead(Up) == LOW)
        {
          delay(200);
          FPD = FPD + 2;
          if (FPD == 10)
          {
            FPD = 12;
          }
          if (FPD == 14)
          {
            FPD = 24;
          }
          TBF = 24 / FPD;
          H_feed = H + TBF;


          if (H_feed >= 24)
          {
            H_feed = H_feed - 24;
          }
        }


        //Adjust FPD down
        if (digitalRead(Down) == LOW)
        {
          delay(200);
          FPD = FPD - 2;
          if (FPD == 22)
          {
            FPD = 12;
          }
          if (FPD == 10)
          {
            FPD = 8;
          }
          TBF = 24 / FPD;
          H_feed = H + TBF;


          if (H_feed >= 24)
          {
            H_feed = H_feed - 24;
          }
        }


        //Prevent FPD > 24
        if (FPD > 24)
        {
          FPD = 0;
        }
        ipRead = true;
      }
      break;
    case 4: //IP address
      {
        //LCD info
        lcd.setCursor(0, 0);
        lcd.print("Menu: IP-address");
        lcd.setCursor(0, 1);


        //get connection information from ESP8266
        //module and take IP-substring address out of string
        if (ipRead == true)
        {
          IP = (wifi.getLocalIP().c_str());
          ipRead = false;
        }
        lcd.print("IP: ");
        lcd.print(IP.substring(75, 84));


      }
      break;
    case 5: //Reinitialize wifi connection
      {


        //LCD info
        lcd.setCursor(0, 0);
        lcd.print("Menu: Wifi      ");
        lcd.setCursor(0, 1);
        lcd.print("Reboot: Press UP");


        //Reboot ESP8266 wifi module
        if (digitalRead(Up) == LOW)
        {
          delay(200);
          lcd.clear();
          lcd.setCursor(0, 0);
          lcd.print(" Confirm Reboot ");
          lcd.setCursor(0, 1);
          lcd.print(" Up=Yes Down=No ");
          reboot_confirm = true;


        }
        while (reboot_confirm == true)
        {
          if (digitalRead(Up) == LOW)
          {
            delay(200);
            transmit_data = "GET /feeding.php?value=Booting HTTP/1.1\r\nHost: MyNASdriveIPAddress\r\nConnection: close\r\n\r\n";
            reboot_confirm = false;
            Wifi_Setup();
          }
          else if (digitalRead(Down) == LOW)
          {
            delay(200);
            reboot_confirm = false;
            return;
          }
        }
      }
      break;


    default:


      void No_Menu_Default(); //Daily operation out of menu
      {




        //LCD CLOCK


        lcd.setCursor(0, 0);
        lcd.print("    ");
        lcd.setCursor(12, 0);
        lcd.print("     ");


        if (H < 10)
        {


          lcd.setCursor(4, 0);
          lcd.print("0");
          lcd.setCursor(5, 0);
          lcd.print(H);
        }
        else
        {
          lcd.setCursor(4, 0);
          lcd.print(H);
        }
        if (M < 10)
        {
          lcd.setCursor(6, 0);
          lcd.print(":");
          lcd.print("0");
          lcd.setCursor(8, 0);
          lcd.print(M);
        }
        else
        {
          lcd.setCursor(6, 0);
          lcd.print(":");
          lcd.print(M);
        }
        if (S < 10)
        {
          lcd.setCursor(9, 0);
          lcd.print(":");
          lcd.print("0");
          lcd.setCursor(11, 0);
          lcd.print(S);
        }
        else
        {
          lcd.setCursor(9, 0);
          lcd.print(":");
          lcd.print(S);
        }


        //Feeding on/off toggle
        if (digitalRead(Feed_on_off) == LOW)
        {
          delay(200);
          Feeding_allowed = !Feeding_allowed;
          lcd.clear();
        }


        //LCD info
        if (Feeding_allowed == LOW)
        {


          lcd.setCursor(2, 1);
          lcd.print("Feeding  Off");


        }
        else if (FPD == 0)
        {
          lcd.setCursor(1, 1);
          lcd.print("Please set FPD");


        }
        else
        {
          lcd.setCursor(0, 1);
          lcd.print("Feeding at");
          if (H_feed < 10)
          {


            lcd.setCursor(11, 1);
            lcd.print("0");
            lcd.setCursor(12, 1);
            lcd.print(H_feed);
          }
          else
          {
            lcd.setCursor(11, 1);
            lcd.print(H_feed);
          }
          lcd.setCursor(13, 1);
          lcd.print(":");
          lcd.print("00");


        }


        //Feeding conditionals
        //First feeding cycle after FPD change is reset at H == 10 to bind the cycle to aquarium light schedule


        //Normal feeding
        if (H_feed == H && M == 0 && S == 0 && Feeding_allowed == HIGH && FPD > 0
            || H == 10 && M == 0 && S == 0 && Feeding_allowed == HIGH && FPD > 0)
        {
          transmit_data =  "GET /feeding.php?value=Autofeed HTTP/1.1\r\nHost: MyNASdriveIPAddress\r\nConnection: close\r\n\r\n";
          Feed();
          H_feed = H + TBF;
        }


        //Next feed adjustment at feeding off
        else if (H_feed == H && M == 0 && S == 0 && Feeding_allowed == LOW && FPD > 0
                 || H == 10 && M == 0 && S == 0 && Feeding_allowed == LOW && FPD > 0)
        {
          H_feed = H + TBF;
        }


        else
        {
          digitalWrite(Shaker, LOW);
          Feeder.write(pos_in);
        }


        if (H_feed >= 24)
        {
          H_feed = H_feed - 24;
        }


      }
      break;
  }


}
void Wifi_Setup() //Initialization of wifi module
{
  //LCD info
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("  Initializing  ");


  //Set wifi module to softAP mode
  if (wifi.setOprToStationSoftAP()) {
    lcd.setCursor(0, 1);
    lcd.print("Set to SAP:   OK");
  } else {
    lcd.clear();
    lcd.setCursor(0, 1);
    lcd.print("Set to SAP:  ERR");
  }


  //Join wifi network
  if (wifi.joinAP(SSID, PASSWORD)) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Join AP success ");
    lcd.setCursor(0, 1);
    String IP = String(wifi.getLocalIP().c_str());
    lcd.print("IP: ");
    lcd.setCursor(4, 1);
    lcd.print(IP.substring(75, 84));
  } else {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Join AP failure ");
  }
  delay(2000);






  //Set client to single connection
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("  Initializing  ");
  if (wifi.disableMUX()) {
    lcd.setCursor(0, 1);
    lcd.print("Set Single:   OK");
  } else {
    lcd.clear();
    lcd.setCursor(0, 1);
    lcd.print("Set Single:  ERR");
  }
  delay(2000);
  if (transmit_data == "GET /feeding.php?value=Booting HTTP/1.1\r\nHost: MyNASdriveIPAddress\r\nConnection: close\r\n\r\n") {
    Transmit();


  }


  lcd.clear();






}
void Transmit() //Transmit data to NASdrive
{


  //LCD info
  lcd.setCursor(0, 0);
  lcd.print("Communicating  ");
  lcd.setCursor(0, 1);
  lcd.print("with server     ");
  delay(1000);
  //Create TCP connection with NASdrive
  if (wifi.createTCP(HOST_NAME, HOST_PORT)) {
    lcd.setCursor(0, 0);
    lcd.print("Create TCP:   OK\r\n");
    lcd.setCursor(0, 1);
    lcd.print("Transmitting    ");
  }
  else {
    lcd.setCursor(0, 1);
    lcd.print("Create TCP:  ERR\r\n");
  }




  //Write data to NASdrive
  //get template  "GET /feeding.php?value=XXXXX HTTP/1.1\r\nHost: MyNASdriveIPAddress\r\nConnection: close\r\n\r\n"
  wifi.send((const uint8_t*)transmit_data, strlen(transmit_data));
  wifi.releaseTCP();
  delay(1000);
  lcd.clear();


}
void Feed() //Feeding
{
  digitalWrite(Shaker, HIGH);
  delay(40);
  digitalWrite(Shaker, LOW);
  delay(200);
  digitalWrite(Shaker, HIGH);
  delay(40);
  digitalWrite(Shaker, LOW);
  delay(200);
  Feeder.write(pos_out);
  delay(200);
  Feeder.write(pos_in);
  delay(200);
  lcd.begin(16, 2);
  Transmit();


}
void Clock()  //System clock
{


  //Clock
  S = S + Click;


  if (S > 59)
  {
    S = 0;
  }


  if (S == 0)
  {
    M = M + 1;
  }


  if (M > 59)
  {
    M = 0;
  }


  if (M == 0 && S == 0)
  {
    H = H + 1;
  }


  if (H > 23)
  {
    H = 0;
  }
}





















Step 4: The Database

My database is running on a Synology NAS drive which as standard has MariaDB in its package center. MariaDB is a variety of MySQL which will work in this project as well. No other types of databases will work with the windows app or arduino code as there has been implemented specific libraries for MySQL into the codes.

The database can be managed and built in PHPMyAdmin, a free software for this purpose. I have added one database with two tables, one for the actual feeding data, and then a table with dummy temperature data to show how the windows app would be able to read from different tables.

The tables has 3 rows each; Id, Time (time/data stamp) and then a value. When making a table you can either do it manually in PHPMyAdmin or you can write a SQL command which then produces the table. To make it work with the php script, arduino and windows code the table name should be called Feedings (and Temperature for the dummy table).

When making your DB you should remember to check the A.I. (auto increment) in the ID structure editor. You should also make a user for your applications and grant it full access to the database, as otherwise you will not be able to connect from external sources.

Depending on which database and database edition (im using MariaDB 10.0.32) you are using, there will be small variations in the SQL codes necessairy. Please consult the documentation available on the specific database.

Step 5: The PHP Schript

To be able to transmit data from the arduino to the database you will need to make a PHP-script file and paste it into the web-folder on your NAS drive. The arduino then connects to the webserver with GET commands which the webserver running your database then picks up and processes. Attached you will find a textfile with the PHPcode. For making it work with your DB and arduino; fill in your own data in the [variables] (remove the brackets as well), rename the file to feedings.php and copy it into your webfolder on your NASdrive.

Step 6: The Windows Application

The windows application is programmed in C# and is made as a windows application form in Microsoft Visual Studio. The full program is attached as ZIP folder.

Besides showing the amount of data you want, the application will scan the data looking for "Booting" which the coral feeder sends to the database when starting up or reconnecting to your network. So if your coral feeder has been disconnected or restarted, the application till notify you, as the data could be incomplete.

The application will also tell you if you have typed the wrong password or username etc, and can read and show all errorcodes sent from your MySQL database if something goes wrong.

Step 7: Security Issues

When making a project like this you will need to take some security risks into considerations. In my case the database password, username and other credentials are hardcoded into the PHP-script. This makes the database very vulnerable to SQL injections which can destroy your database in a worst case scenario. So unless you are sure noone other than you and your closes family and friends has access to your network, you should make the arduino pass the credentials to the script instead, making it more safe to use in public(this dosen't make it bulletproof though). When setting up your webstation on your NAS-drive you should make rules that only allow certain IP adresses access to the database. If you do this remember to log onto your router and make sure your arduino has a static IP address.

There are other measures to you can take if afraid of malicious attacks on your database. If you think more preventive measures are necessairy there's plenty of reading material out there on the www.

Step 8: The Wiring

On the picture you will find the wiring schematic. For your specific ESP8266 module please consult the documentation as there are many variations of these.

It is very important that you never supply the ESP8266 with more than 3.3 volt (3.6 volt max) as you will fry it if so. The RX pin should never recieve more than 3.6 volt as well. This is why i had to make a voltage divider, in my case with 1k Ohm resistor and a 2k Ohm resistor on each side and then connect the RX(esp) in the middle. The TX(arduino) should be connected to the low (1k) end and GND to the high (2k)end of the divider. This way the RX pin on the esp will recieve a 3.3v signal as it is supposed to.