An arduino based chicken door

Introduction

Building an Arduino based chicken door is not a very complicated thing to do. There are several providers on the market but if you want to build it by yourself, you can easily do it. I have tried my best to keep everything as simple as possible.

Material

I use a Arduino Nano with an ATMega328P as the “brain” of the chicken door. At first I tried the ESP8266 for wireless connectivity, but that did not have enough digital in/ and outputs for all the sensors and switches.

Arduino Nano: https://amzn.to/2LD6QdN

The Motor driver powers the motor and lets you reverse direction

Motor driver: https://amzn.to/2K45nuX

The reed-switches are used to find out if the door has hit it’s top or bottom end point.

Reed switch: https://amzn.to/34Y1JfM

The mini pushbuttons are used to control and configure the board

Mini Pushbuttons: https://amzn.to/34VJ3NR

The user UI

OLED display: https://amzn.to/2Oglhbc

The light sensor is used to find out if it is dark or not.

Light Sensor: https://amzn.to/2Go7E35

As the Arduino Nano does not have a built in real time clock, we have to attach one so we can also control the door using times

Realtime Clock: https://amzn.to/2YdPeIK

The motor is used to move the door

Motor: https://amzn.to/2OhJs9h

Soldering Breadboard: https://amzn.to/2SDMhjp – use a regular one if you are not experienced.

A 200 Ohm resistor for the voltage divider (big pack here: https://amzn.to/2YrpAnG) but you really only need one!

Terminals to attach the switches: https://amzn.to/2Oqf0tk

H-Profile: https://amzn.to/2JOC8Ob

Rope: https://amzn.to/2SyEKCi

The door: https://amzn.to/2LFpbs8

These are all Amazon Affiliate links. When you buy using this link, I get a small share but your purchasing price does not change.

In addition, you need a couple of wires (for the breadboard and for the switches), tools like screwdrivers, drills and a handful of screws. If you want, you can also 3d-print your own box for that. If you design a nice box for the chicken door, please let me know and I’ll happily include the stl’s in my github repo.

Putting the electronics together

For soldering I use soldering breadboards (half size) as this greatly simplifies the cabelling/soldering.

The mechanics

Here you see pictures of the testing setup. I leave it in the garden for now ti check if it behaves as expected. I don’t want to lock in the chicks 🙂

Make sure you put everything into closed box to avoid that mite sit in there. Do not use hot glue but prefer epoxy glue to put everything together

RTC Library

The library for the real time clock

Installing the RTC library

https://github.com/adafruit/RTClib

https://learn.adafruit.com/ds1307-real-time-clock-breakout-board-kit/overview

SSD1306 ASCII Library

A small library greatly simplifying the display of two lines on our display

https://github.com/greiman/SSD1306Ascii

The arduino code

I have set up a couple of tabs in the Arduino IDE as this keeps the code easy to understand.

Klappe1

the init code and the loop-function. At first I need a function prototype for set_display in order to support a default value for the second line. More on this in the display-section

Then I have set up a small block with the code you are most likely to modify when you don’t want to go too much into detail

// Values for opening/closing the door - configure only this…

int hours_open=07;
int minutes_open=30;

int hours_close=15;
int minutes_close=0;

int bottom_light_threshold=10;
int top_light_threshold=15;

Then I include the RTC and OLED libraries and create respective objects

#include <DS3231.h>
#include <Wire.h>
#include "SSD1306Ascii.h"
#include "SSD1306AsciiWire.h"

// DS3231 RTC
DS3231 Clock;

bool Century=false;
bool h12;
bool PM;
byte ADay, AHour, AMinute, ASecond, ABits;
bool ADy, A12h, Apm;

String date_delimiter=".";
String time_delimiter=":";

// SSD1306 OLED
#define I2C_ADDRESS 0x3C
#define RST_PIN -1
SSD1306AsciiWire oled;

Next I define the input/output pins

// Buttons
int btn_up_pin = 4;
int btn_down_pin = 5;
int btn_ok_pin = 8;
int btn_back_pin = 9;


// Reed switches (Door)
int sw_top = 6;
int sw_btm = 7; 


// Motor
int motor_pin1 = 2;
int motor_pin2 = 3;


// Light sensor
int lightsensor_pin=A0;

Define global variables with respective values

int light_intensity;                                 // Container for the currently measured light intensity
int flattened_light_intensity;
bool bottom_light_threshold_passed=false;            // flag if the threshold has been passed after the n measures
bool top_light_threshold_passed=false;               // flag if the threshold has been passed after the n measures

int number_of_light_measures=3;                      // number of measures
int measure_interval=5;                              // measuere every n seconds
int light_measures[3];
int second_measured;
int light_measure_counter=0;


// Idle Display variables
char* idle_display_content="flattened_brightness";   // brightness, flattened_brightness, datetime or temperature
String buf_line1;
String buf_line2;
int display_delay=1000;                              // delay after each interaction


// Door states
bool door_is_up=false;
bool door_is_down=false;


// Operating states (start in auto-state)
bool is_auto_state=true;

On the last line, the door is set to auto-mode. that means it follows the programme logic. If not in auto-mode, the door ignores times and only follows the up-down buttons.

The loop function

The loop-function controls the display and check if we are in auto-mode or not. If not, look at the buttons, if so, use time and brightness

void loop() {
  
  light_intensity=get_light_intensity();
  
  //idle display
  if(is_auto_state){
    if(idle_display_content=="flattened_brightness"){
      set_display("Brightness (f)",String(flattened_light_intensity));
    }
    if(idle_display_content=="brightness"){
      set_display("Brightness",String(light_intensity));
    }  
  
    else if(idle_display_content=="temperature"){
      set_display("Temperature",String(get_temperature()));
    }

    else if(idle_display_content=="datetime"){
      set_display(get_date_string(), get_time_string());
    }

    else if(idle_display_content=="off"){
      set_display("", "");
    }
    
  } else {
    set_display("Manual Mode!!");
  }
  
  
  //toggle the auto state on the green button. 
  if(buttonstate(btn_ok_pin)){
      set_auto_state(!is_auto_state);
  }



//auto mode - look at time/brightness to move the doors
  if(is_auto_state){  
    set_passed_threshold();
      
    if(door_should_be_open() and door_is_down){
      door_up(); 
    }

    if(!door_should_be_open() and door_is_up){
      door_down(); 
    }


// manual mode - use the buttons
  } else {  
    if(buttonstate(btn_up_pin)){
      door_up();
    }
    
    if(buttonstate(btn_down_pin)){
      door_down();
    }
  }

  
  delay(200);
}

Buttons

The Buttons-Tab contains simple logic to read from a digital input

// returns the state of the buttons

bool buttonstate(int buttonpin){
  int buttonState = digitalRead(buttonpin);
 
  if (buttonState == HIGH) {
    
    return false;
  } else {
    
    return true;
  }
}

Display

The display makes use of the set_display function prototype in the init section. The function controls the display.

  1. Checks if there is new data to be displayed.
  2. If so, replace the data in the buf_line1/2
  3. display the new text
// set the display. Comparing lines to display with lines already displayed to avoid flickering.

void set_display(String line1, String line2){
  if(line1!=buf_line1 or line2!=buf_line2){
    buf_line1=line1;
    buf_line2=line2;
    oled.clear();
    oled.println(buf_line1);
    oled.print(buf_line2);
  }
}

Door

This tab contains code to control the door and check the connected reed-switches

bool is_top(){
  return buttonstate(sw_top);
}

bool is_bottom(){
  return buttonstate(sw_btm);
}

void door_stop(){
  set_display("Stopping door");
  digitalWrite(motor_pin2, LOW);
  digitalWrite(motor_pin1, LOW);
  
}

void door_up(){
  if(is_top()){
    door_is_down=false;
    door_is_up=true;
    return;
  }
  digitalWrite(motor_pin1, HIGH);
  digitalWrite(motor_pin2, LOW);
  set_display("Moving up");
  while(!is_top()){
    if(buttonstate(btn_back_pin)){
      door_stop();
      delay(display_delay);
      set_auto_state(false);
      return;
    }
    delay(50);
  }
  door_stop();
  door_is_down=false;
  door_is_up=true;
  delay(display_delay);
  oled.clear();

}

void door_down(){
  if(is_bottom()){
    door_is_down=true;
    door_is_up=false;
    return;
  }
  digitalWrite(motor_pin2, HIGH);
  digitalWrite(motor_pin1, LOW);
  set_display("Moving down");
  while(!is_bottom()){
    if(buttonstate(btn_back_pin)){
      door_stop();
      delay(display_delay);
      set_auto_state(false);
      return;
    }
    delay(50);
  }
  door_stop();
  door_is_down=true;
  door_is_up=false;
  delay(display_delay);
  oled.clear();

}

bool door_should_be_open(){
  
  int opening_minutes=calc_minutes(hours_open, minutes_open);
  int closing_minutes=calc_minutes(hours_close, minutes_close);
  
  if((opening_minutes<=get_current_minutes() and top_light_threshold_passed) and (closing_minutes>=get_current_minutes() or bottom_light_threshold_passed)){
    return true;
  }
  return false;
}

void set_auto_state(bool state){
  is_auto_state=state;
  if(state){
      set_display("Mode","Auto");
      delay(display_delay);
    } else {
      set_display("Mode","Manual");
      delay(display_delay);
    }
}

the most important one here is door_should_be_open(). Here is a good point to add Serial.println’s to troubleshoot any errors in the doors behaviour.

Light

The light tab does everything that is related to the light – read the value of the light sensor, flattens the read values and sets flags when top or bottom light thresholds have been passed.

int get_light_intensity(){
  return analogRead(lightsensor_pin);
}

// check if the light has passed the threshold. If so, set global var to true. If not, set to false
// The number of measures and the interval is set in the klappe-tab

void set_passed_threshold(){
  if(Clock.getSecond() % measure_interval == 0 and second_measured != Clock.getSecond()){ 
    second_measured=Clock.getSecond();
    light_measures[light_measure_counter]=light_intensity;
    light_measure_counter++;
    if(light_measure_counter==number_of_light_measures){
      int sum=0;
      for (int i = 0; i < number_of_light_measures; i++){
        sum+=light_measures[i];
      }
      light_measure_counter=0;
      flattened_light_intensity=sum/number_of_light_measures;
      
      // top threshold
      if(sum>=top_light_threshold*number_of_light_measures){
        top_light_threshold_passed=true;
      } else {
        top_light_threshold_passed=false;
      }

      //bottom threshold
      if(sum>=bottom_light_threshold*number_of_light_measures){
        bottom_light_threshold_passed=true;
      } else {
        bottom_light_threshold_passed=false;
      }
    }
    
     
  }
}

Setup

The setup-tab does all the initialisations of the hardware and displays a small greeting to the chicken

void setup() {
  Serial.begin(9600);
  Serial.println("Starting Setup");

  
  // set pin modes
  Serial.println("Pinmodes");
  pinMode(btn_up_pin,INPUT_PULLUP);
  pinMode(btn_down_pin,INPUT_PULLUP);
  
  pinMode(btn_ok_pin,INPUT_PULLUP);
  pinMode(btn_back_pin,INPUT_PULLUP);
  
  pinMode(sw_top,INPUT_PULLUP);
  pinMode(sw_btm,INPUT_PULLUP);

  pinMode(motor_pin1,OUTPUT);
  pinMode(motor_pin2,OUTPUT);

  
  // Start the I2C interface
  Serial.println("I2C");
  Wire.begin();
  Wire.setClock(400000L);


  // Start the the OLED
  Serial.println("OLED");
  #if RST_PIN >= 0
  oled.begin(&Adafruit128x32, I2C_ADDRESS, RST_PIN);
  #else // RST_PIN >= 0
  oled.begin(&Adafruit128x32, I2C_ADDRESS);
  #endif // RST_PIN >= 0
  oled.setFont(Arial14);

  // Use true, normal mode, since default for Adafruit display is remap mode.
  oled.displayRemap(true);
  oled.clear();


  // lower door as a start and greet whoever is there.
  set_display("Hello","Ladies");
  delay(3000); 
  door_down();
  
  Serial.println("End of Setup");
}

Temperature

The temperature tab contains a very small function to read from the temperature sensor that is built into the real time clock

//temperature

int get_temperature(){
  return Clock.getTemperature();
}

Time

The time-tab contains a couple of functions to calculate the number of minutes into the day and build the Date/Time strings for the display.

int get_current_minutes(){
  int current_minutes=(Clock.getHour(h12, PM)*60)+Clock.getMinute();
  return current_minutes;
}


// Multiply hours with 60 and add minutes
int calc_minutes(int hour, int minute){
  int calc_minutes=(hour*60)+minute;
  return calc_minutes;
}


// Build the date string
String get_date_string(){
  int clock_day=Clock.getDate();
  int clock_month=Clock.getMonth(Century);
  String day_string=pad_two(clock_day);  
  String month_string=pad_two(clock_month);
     
  return day_string+date_delimiter+month_string+date_delimiter+"20"+String(Clock.getYear());
}


// Build the time string
String get_time_string(){
  int clock_hour=Clock.getHour(h12, PM);
  int clock_minute=Clock.getMinute();
  int clock_second=Clock.getSecond();
  String hour_string=pad_two(clock_hour);
  String minute_string=pad_two(clock_minute);
  String second_string=pad_two(clock_second);

  return hour_string+time_delimiter+minute_string+time_delimiter+second_string;
}


// Function to pad with zero to two characters. 
String pad_two(int nbr){
  String out;
  if(nbr<10){
    out="0"+String(nbr);
  } else {
    out=String(nbr);
  }
  return out;
}

Resources

You will find the full code, stl-meshes for the gears and the fritzing file in my github-repository. If you have any improvements please feel free to adjust the code as needed and send me pull requests for discussion. I am not a C++ pro, so I’d be happy about feedback.

If you build this, please send pictures of it and if you have and problems putting this together, let me know. If you need a better explanation of the code, I will add it here.

Cheers and thanks for reading all this

Andre

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.