Automatic Bird Feeder

Our project was an automatic bird feeder.

It uses an IR sensor to determine how much food is in the dish currently. If there isn't enough food in the dish, it will turn the servo (which would be connected to a hopper door) to allow food to fall from the hopper into the dish. When there is enough food, the IR sensor detects this and closes the hopper. If for any reason the hardware fails and leaves the hopper open or there is no more food to fill the dish, then an error is thrown. The hopper is forced closed and an error LED pattern is shown. In order to fix this issue, a hard reset is required.

The bird feeder can also be used over wifi. On its page you can turn the web control function on or off. When web control is on, you can open or close the hopper manually through your network. When you turn web control off, the automatic control as described above takes over. On the page you can also check the state of the feeder. If there is an error, the web page will tell you so you can go fix it.

There is also a manual override switch available. When the manual mode is switched on, web control and autotomatic control features are disabled. The hopper can be controlled with a switch. When the manual override is switched off, the feeder returns to web control or automatic control.

Parts used: Sharp IR Sensor, Adafruit ESP8266 wifi board, Hitec HS-422 Servo

Sharp IR Sensor

mbedIR sensor
+5Vred
GNDblack
p15yellow

Adafruit ESP8266 Board

mbedWifi Board
+5VV+
GNDGND
p28RX
p27TX
p26RST

Hitec HS-422 Servo

mbedServo
+5Vred
GNDblack
p21yellow

Board Picture:

/media/uploads/myu429/img_0994.jpg

Demo:

Main Code:

main.cpp

// ESP8266 Static page WEB server to control Mbed

#include "mbed.h"
#include "uLCD_4DGL.h"
#include "Servo.h"

Serial pc(USBTX, USBRX);
Serial esp(p28, p27); // tx, rx

DigitalIn hardwareEnableSwitch(p30, PullDown);
DigitalIn hardwareValveSwitch(p29, PullDown);

//DigitalOut isBrokenPin(p23);

Timer timer;

volatile float distance = 0;
volatile bool isBroken = false;
volatile bool hardwareControl = false;
volatile bool webControl = false;
volatile float webControlServo = 0;

AnalogIn distanceSensorIR(p15);
uLCD_4DGL uLCD(p9,p10,p8);
Servo servo(p21);

// Standard Mbed LED definitions
DigitalOut  led1(LED1);
DigitalOut  led2(LED2);
DigitalOut  led3(LED3);
DigitalOut  led4(LED4);

// some test values to show on web page
AnalogIn   Ain1(p18);
AnalogIn   Ain2(p19);

/*
char ssid[32] = "hsd";     // enter WiFi router ssid inside the quotes
char pwd [32] = "austin123"; // enter WiFi router password inside the quotes
*/
float temperature, AdcIn, Ht;
float R1=100000, R2=10000; // resistor values to give a 10:1 reduction of measured AnalogIn voltage
char Vcc[10];
char Temp[10];

// things for sending/receiving data over serial
volatile int tx_in=0;
volatile int tx_out=0;
volatile int rx_in=0;
volatile int rx_out=0;
const int buffer_size = 4095;
char tx_buffer[buffer_size+1];
char rx_buffer[buffer_size+1];
void Tx_interrupt();
void Rx_interrupt();
void send_line();
void read_line();

int DataRX;
int update;
int count;
char cmdbuff[1024];
char replybuff[4096];
char webdata[4096]; // This may need to be bigger depending on WEB browser used
char webbuff[4096];     // Currently using 1986 characters, Increase this if more web page data added
char timebuf[30];
void SendCMD(),getreply(),ReadWebData(),startserver();
void gettime(),setRTC(),gettemp(),getbattery();
char rx_line[1024];
int port        =80;  // set server port
int SERVtimeout =5;    // set server timeout in seconds in case link breaks.
struct tm t;
// manual set RTC values
int minute      =00;    // 0-59
int hour        =12;    // 2-23
int dayofmonth  =26;    // 1-31
int month       =8;     // 1-12
int year        =15;    // last 2 digits

int main()
{
    //isBrokenPin = 0;
    float range=0.0005;
    float degrees=0.0;
    servo.calibrate(range, degrees);
    servo = 0;
    pc.baud(9600);
    esp.baud(9600);
    led1=1,led2=0,led3=0, led4=0;
    // Setup a serial interrupt function to receive data
    esp.attach(&Rx_interrupt, Serial::RxIrq);
    // Setup a serial interrupt function to transmit data
    esp.attach(&Tx_interrupt, Serial::TxIrq);
    if (time(NULL) < 1420070400) {
        setRTC();
    }
    startserver();
    DataRX=0;
    count=0;
    wait(1.0);
    while(1) {
        hardwareControl = (hardwareEnableSwitch == 1);
        if (hardwareControl) {
            uLCD.cls();
            uLCD.printf("HARDWARECONTROL\n");
            timer.stop();
            timer.reset();
            servo = hardwareValveSwitch;
        } else if (webControl) {
            uLCD.cls();
            uLCD.printf("WEBCONTROL\n");
            timer.stop();
            timer.reset();
            servo = webControlServo;
        } else if (!hardwareControl && !webControl) {
            uLCD.cls();
            uLCD.printf("AUTOCONTROL\n");
            distance = distanceSensorIR.read();
            if (timer.read() > 10.0) {                      //if feeder has been open too long
                isBroken = true;
            } else {
                if (servo == 0) {                           //if feeder is closed
                    if (distance < 1.65/3.3) {              //if food level > 15 cm away
                        servo = 1;                          //open feeder
                        uLCD.printf("Open valve");
                        timer.start();                      //start timer
                    }
                } else {
                    if (distance > 2.3/3.3) {               //if food level is < 10cm away
                        servo = 0;                          //close feeder
                        uLCD.printf("Close valve");
                        timer.stop();
                        timer.reset();
                    }
                }
            }
        } else {
            isBroken = true;
        }
        if (isBroken) {
            servo = 0;                                  //close feeder
            //isBrokenPin = 1;
            led1 = 1;
            led2 = 0;
            led3 = 0;
            led4 = 1;
            while(isBroken) {
                led1 = !led1;
                led2 = !led2;
                led3 = !led3;
                led4 = !led4;
                wait(0.2);
            }
        }
        
        if(DataRX==1) {
            ReadWebData();
            esp.attach(&Rx_interrupt, Serial::RxIrq);
        }
        if(update==1) // update time, hit count, and analog levels in the HUZZAH chip
        {
            // get new values
            gettime();
            gettemp();
            getbattery();
            count++;
            // send new values
            sprintf(cmdbuff, "count,time,analog1,analog2=%d,\"%s\",\"%s\",\"%s\"\r\n",count,timebuf,Temp,Vcc);
            SendCMD();
            getreply();
            update=0;   
        }
    }
}

// Reads and processes GET and POST web data
void ReadWebData()
{
    wait_ms(200);
    esp.attach(NULL,Serial::RxIrq);
    DataRX=0;
    memset(webdata, '\0', sizeof(webdata));
    strcpy(webdata, rx_buffer);
    memset(rx_buffer, '\0', sizeof(rx_buffer));
    rx_in = 0;
    rx_out = 0;
    // check web data for form information
    if( strstr(webdata, "check=led1v") != NULL ) {
        led1=!led1;
        webControlServo = 1;
    }
    if( strstr(webdata, "check=led2v") != NULL ) {
        led2=!led2;
        webControlServo = 0;
    }
    if( strstr(webdata, "check=led3v") != NULL ) {
        if (!hardwareControl) {
            webControl = !webControl;
        }
    }
    if( strstr(webdata, "check=led4v") != NULL ) {
        led4=!led4;
    }
    if( strstr(webdata, "POST") != NULL ) { // set update flag if POST request
        update=1;
    }
    if( strstr(webdata, "GET") != NULL && strstr(webdata, "favicon") == NULL ) { // set update flag for GET request but do not want to update for favicon requests
        update=1;
    }
}
// Starts webserver
void startserver()
{
    gettime();
    gettemp();
    getbattery();
    pc.printf("++++++++++ Resetting ESP ++++++++++\r\n");
    strcpy(cmdbuff,"node.restart()\r\n");
    SendCMD();
    wait(2);
    getreply();
    
    pc.printf("\n++++++++++ Starting Server ++++++++++\r\n> ");

    // initial values
    sprintf(cmdbuff, "count,time,analog1,analog2=0,\"%s\",\"%s\",\"%s\"\r\n",timebuf,Temp,Vcc);
    SendCMD();
    getreply();
    wait(0.5);

    //create server
    sprintf(cmdbuff, "srv=net.createServer(net.TCP,%d)\r\n",SERVtimeout);
    SendCMD();
    getreply();
    wait(0.5);
    strcpy(cmdbuff,"srv:listen(80,function(conn)\r\n");
    SendCMD();
    getreply();
    wait(0.3);
    strcpy(cmdbuff,"conn:on(\"receive\",function(conn,payload) \r\n");
    SendCMD();
    getreply();
    wait(0.3);
    
    //print data to mbed
    strcpy(cmdbuff,"print(payload)\r\n");
    SendCMD();
    getreply();
    wait(0.2);
   
    //web page data
    strcpy(cmdbuff,"conn:send('<!DOCTYPE html><html><body><h1>ECE4180 IoT Auto BirdFeeder</h1>')\r\n");
    SendCMD();
    getreply();
    wait(0.4);
    if (isBroken) {
        strcpy(cmdbuff,"conn:send('Current Status: Broken')\r\n");
        SendCMD();
        getreply();
    } else {
        strcpy(cmdbuff,"conn:send('Current Status: Working')\r\n");
        SendCMD();
        getreply();
    }
    wait(0.2);
    strcpy(cmdbuff,"conn:send('Food Level: '..analog1..' cm<br>Analog 2: '..analog2..' V<br><hr>')\r\n");
    SendCMD();
    getreply();
    wait(0.3);
    strcpy(cmdbuff,"conn:send('<form method=\"POST\"')\r\n");
    SendCMD();
    getreply();
    wait(0.3);
    strcpy(cmdbuff, "conn:send('<p><input type=\"checkbox\" name=\"check\" value=\"led1v\"> Open Feeder')\r\n");
    SendCMD();
    getreply();
    wait(0.3);
    strcpy(cmdbuff, "conn:send('<p><input type=\"checkbox\" name=\"check\" value=\"led2v\">Close Feeder')\r\n");
    SendCMD();
    getreply();
    wait(0.3);
    strcpy(cmdbuff, "conn:send('<p><input type=\"checkbox\" name=\"check\" value=\"led3v\"> Toggle Webcontrol')\r\n");
    SendCMD();
    getreply();
    wait(0.3);
    strcpy(cmdbuff,"conn:send('<p><input type=\"submit\" value=\"send-refresh\"></form>')\r\n");
    SendCMD();
    getreply();
    wait(0.3);
    strcpy(cmdbuff, "conn:send('<p><h2>How to use:</h2><ul><li>Select a checkbox to flip on/off</li><li>Click Send-Refresh to send data and refresh values</li></ul></body></html>')\r\n");
    SendCMD();
    getreply();
    wait(0.5); 
    // end web page data
    strcpy(cmdbuff, "conn:on(\"sent\",function(conn) conn:close() end)\r\n"); // close current connection
    SendCMD();
    getreply();
    wait(0.3);
    strcpy(cmdbuff, "end)\r\n");
    SendCMD();
    getreply();
    wait(0.2);
    strcpy(cmdbuff, "end)\r\n");
    SendCMD();
    getreply();
    wait(0.2);

    strcpy(cmdbuff, "tmr.alarm(0, 1000, 1, function()\r\n");
    SendCMD();
    getreply();
    wait(0.2);
    strcpy(cmdbuff, "if wifi.sta.getip() == nil then\r\n");
    SendCMD();
    getreply();
    wait(0.2);
    strcpy(cmdbuff, "print(\"Connecting to AP...\\n\")\r\n");
    SendCMD();
    getreply();
    wait(0.2);
    strcpy(cmdbuff, "else\r\n");
    SendCMD();
    getreply();
    wait(0.2);
    strcpy(cmdbuff, "ip, nm, gw=wifi.sta.getip()\r\n");
    SendCMD();
    getreply();
    wait(0.2);
    strcpy(cmdbuff,"print(\"IP Address: \",ip)\r\n");
    SendCMD();
    getreply();
    wait(0.2);
    strcpy(cmdbuff,"tmr.stop(0)\r\n");
    SendCMD();
    getreply();
    wait(0.2);
    strcpy(cmdbuff,"end\r\n");
    SendCMD();
    getreply();
    wait(0.2);
    strcpy(cmdbuff,"end)\r\n");
    SendCMD();
    getreply();
    wait(0.2);
    
    pc.printf("\n\n++++++++++ Ready ++++++++++\r\n\n");
}


// ESP Command data send
void SendCMD()
{
    int i;
    char temp_char;
    bool empty;
    i = 0;
// Start Critical Section - don't interrupt while changing global buffer variables
    NVIC_DisableIRQ(UART1_IRQn);
    empty = (tx_in == tx_out);
    while ((i==0) || (cmdbuff[i-1] != '\n')) {
// Wait if buffer full
        if (((tx_in + 1) % buffer_size) == tx_out) {
// End Critical Section - need to let interrupt routine empty buffer by sending
            NVIC_EnableIRQ(UART1_IRQn);
            while (((tx_in + 1) % buffer_size) == tx_out) {
            }
// Start Critical Section - don't interrupt while changing global buffer variables
            NVIC_DisableIRQ(UART1_IRQn);
        }
        tx_buffer[tx_in] = cmdbuff[i];
        i++;
        tx_in = (tx_in + 1) % buffer_size;
    }
    if (esp.writeable() && (empty)) {
        temp_char = tx_buffer[tx_out];
        tx_out = (tx_out + 1) % buffer_size;
// Send first character to start tx interrupts, if stopped
        esp.putc(temp_char);
    }
// End Critical Section
    NVIC_EnableIRQ(UART1_IRQn);
    return;
}

// Get Command and ESP status replies
void getreply()
{
    read_line();
    sscanf(rx_line,replybuff);
}
 
// Read a line from the large rx buffer from rx interrupt routine
void read_line() {
    int i;
    i = 0;
// Start Critical Section - don't interrupt while changing global buffer variables
    NVIC_DisableIRQ(UART1_IRQn);
// Loop reading rx buffer characters until end of line character
    while ((i==0) || (rx_line[i-1] != '\r')) {
// Wait if buffer empty
        if (rx_in == rx_out) {
// End Critical Section - need to allow rx interrupt to get new characters for buffer
            NVIC_EnableIRQ(UART1_IRQn);
            while (rx_in == rx_out) {
            }
// Start Critical Section - don't interrupt while changing global buffer variables
            NVIC_DisableIRQ(UART1_IRQn);
        }
        rx_line[i] = rx_buffer[rx_out];
        i++;
        rx_out = (rx_out + 1) % buffer_size;
    }
// End Critical Section
    NVIC_EnableIRQ(UART1_IRQn);
    rx_line[i-1] = 0;
    return;
}
 
 
// Interupt Routine to read in data from serial port
void Rx_interrupt() {
    DataRX=1;
    //led3=1;
// Loop just in case more than one character is in UART's receive FIFO buffer
// Stop if buffer full
    while ((esp.readable()) && (((rx_in + 1) % buffer_size) != rx_out)) {
        rx_buffer[rx_in] = esp.getc();
// Uncomment to Echo to USB serial to watch data flow
        pc.putc(rx_buffer[rx_in]);
        rx_in = (rx_in + 1) % buffer_size;
    }
    //led3=0;
    return;
}
 
 
// Interupt Routine to write out data to serial port
void Tx_interrupt() {
    //led2=1;
// Loop to fill more than one character in UART's transmit FIFO buffer
// Stop if buffer empty
    while ((esp.writeable()) && (tx_in != tx_out)) {
        esp.putc(tx_buffer[tx_out]);
        tx_out = (tx_out + 1) % buffer_size;
    }
    //led2=0;
    return;
}

void gettime()
{
    time_t seconds = time(NULL);
    strftime(timebuf,50,"%H:%M:%S %a %d %b %y", localtime(&seconds));
}

void setRTC()
{
    t.tm_sec = (0);             // 0-59
    t.tm_min = (minute);        // 0-59
    t.tm_hour = (hour);         // 0-23
    t.tm_mday = (dayofmonth);   // 1-31
    t.tm_mon = (month-1);       // 0-11  "0" = Jan, -1 added for Mbed RCT clock format
    t.tm_year = ((year)+100);   // year since 1900,  current DCF year + 100 + 1900 = correct year
    set_time(mktime(&t));       // set RTC clock
}
// Analog in example
void getbattery()
{
    AdcIn=Ain1.read();
    Ht = (AdcIn*3.3); // set the numeric to the exact MCU analog reference voltage for greater accuracy
    sprintf(Vcc,"%2.3f",Ht);
}
// Temperature example
void gettemp()
{
 
    AdcIn=Ain2.read();
    Ht = (AdcIn*3.3); // set the numeric to the exact MCU analog reference voltage for greater accuracy  
    sprintf(Temp,"%2.3f",Ht);
}


1 comment on Automatic Bird Feeder:

27 Aug 2017

Great project!!! I would like to try myself, can I kindly ask you to share Servo library :)

Please log in to post comments.