The Python Code

What I have going on here is once a day a 2am I update the sunrise and sunset times. Once a minute I update the door and light if they need to be changed from there present state. I also make web page that I can view anywhere on my LAN. The web page has a photo taken inside the coop from a USB webcam and the current status. I also have an OLED connected to the RPi3 that shows status. Next will be a temperature and humidity sensor for the coop.

#!/usr/bin/env python

# GPIO.VERSION '0.6.3'
# Raspberry Pi 3 Model B Rev 1.2

import RPi.GPIO as GPIO
import schedule
import astral
import datetime
from pytz import timezone
import time
import os
import sys
from luma.core.interface.serial import i2c
from luma.core.render import canvas
from luma.oled.device import sh1106
import logging
import logging.handlers

my_logger = logging.getLogger('MyLogger')
my_logger.setLevel(logging.DEBUG)

handler = logging.handlers.SysLogHandler(address = '/dev/log')

my_logger.addHandler(handler)
# Example debug and critical messages
# my_logger.debug('this is chicken debug')
# my_logger.critical('this is chicken critical')

# setup the OLED
serial = i2c(port=1, address=0x3C)
device = sh1106(serial)

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)

# Motor Orange/White and White Orange
# Motor FWD Physical Pin 7
GPIO.setup(4, GPIO.OUT)
GPIO.output(4, False)

# Motor REV Physical Pin 29
GPIO.setup(5, GPIO.OUT)
GPIO.output(5, False)

# Door Lock Blue/White and White/Blue Physical Pin 31
GPIO.setup(6, GPIO.OUT)
GPIO.output(6, False)

# Lights Physical Pin 26
GPIO.setup(7, GPIO.OUT)
GPIO.output(7, False)

# Manual Up Door Switch Blue Wire Physical Pin 15
GPIO.setup(22, GPIO.IN,pull_up_down=GPIO.PUD_DOWN)

# Manual Down Door Switch Brown Wire Physical Pin 16
GPIO.setup(23, GPIO.IN,pull_up_down=GPIO.PUD_DOWN)

# Manual Light Switch Black Wire Physical Pin 18
GPIO.setup(24, GPIO.IN,pull_up_down=GPIO.PUD_DOWN)

# Door Up Switch Physical Pin 37
GPIO.setup(26, GPIO.IN,pull_up_down=GPIO.PUD_DOWN)

# Door Down Switch Physical Pin 13
GPIO.setup(27, GPIO.IN,pull_up_down=GPIO.PUD_DOWN)

"""
# test for an internet connection so NTP time is verified
hostname = "google.com"
response = os.system("ping -c 1 " + hostname)

#and then check the response...
if response == 0:
        print hostname, 'is up!'
        GPIO.output(7, True)
        time.sleep(5)
        GPIO.output(7, False)
        time.sleep(2)
        GPIO.output(7, True)
        time.sleep(2)
        GPIO.output(7, False)
else:
        print hostname, 'is down!'
        exit()
"""
# Construct our location.  Longitude west and latitude south are negative
coordinates = ["Poplar Bluff", "USA", 36.763084, -90.413871, "US/Central", 110]
pbmo = astral.Location(info=(coordinates))
pbmo.solar_depression = "civil"

egglight = 840 # minutes of daylight desired
timeformat = "%I:%M %p"

# create global variables
dawn = timezone('US/Central').localize(datetime.datetime.now())
sunrise = timezone('US/Central').localize(datetime.datetime.now())
sunset = timezone('US/Central').localize(datetime.datetime.now())
dusk = timezone('US/Central').localize(datetime.datetime.now())
lighton = timezone('US/Central').localize(datetime.datetime.now())
now = timezone('US/Central').localize(datetime.datetime.now())
lights = False
runtime = 0
mode = "Unknown"
door_status = "Unknown"
tempC = 0.0
tempF = 0.0

htmlcontents = ''' <!DOCTYPE html>
<html lang="en">
        <head>
                <meta charset="utf-8">
                <meta http-equiv="refresh" content="15" >
                <title>Chicken</title>
        </head>
        <body>
                <p>{}</p>
                <table>
                        <tr>
                                <th align="left" style="width:130px">Dawn</th>
                                <th align="left" style="width:130px">Sunrise</th>
                                <th align="left" style="width:130px">Sunset</th>
                                <th align="left" style="width:130px">Dusk</th>
                        </tr>
                        <tr>
                                <td>{}  </td>
                                <td>{}  </td>
                                <td>{}  </td>
                                <td>{}  </td>
                        </tr>
                </table>
                <p>Extra Light {}</p>
                <p>Door Opens at {}</p>
                <p>Door Closes at {}</p>
                <p>Door is {}, Lights are {}, Mode is {}</p>
                <p>CPU Temperature is {}F {}C</p>
        </body>
</html>
'''

def update():
        global dawn
        global sunrise
        global sunset
        global lighton
        global dusk
        global lights
        global runtime
        dawn = pbmo.dawn(datetime.date.today())
        sunrise = pbmo.sunrise(datetime.date.today())
        sunset = pbmo.sunset(datetime.date.today())
        dusk = pbmo.dusk(datetime.date.today())

        # amount of daylight in HH:MM:SS
        daylight = sunset - sunrise
        print "Daylight {}".format(daylight)

        lightmin = daylight.seconds / 60
        if egglight > lightmin:
                extralightminutes = egglight - lightmin
                lighton = sunrise - datetime.timedelta(minutes=extralightminutes)
                lights = True
                print "Lights On {} and Door Opens".format(lighton.strftime(timeformat))
                print "Sunrise {}".format(sunrise.strftime(timeformat))
        else:
                lights = False
                print "Sunrise {} and Door Opens".format(sunrise.strftime(timeformat))

        print "Sunset {}".format(sunset.strftime(timeformat))
        print "Dusk {} and Door Closes".format(dusk.strftime(timeformat))

def status():
        global dawn
        global sunrise
        global sunset
        global lighton
        global dusk
        global lights
        global runtime
        global htmlcontents
        global mode
        global door_status
        global tempC
        global tempF
        light = "Off"
        now = timezone('US/Central').localize(datetime.datetime.now())
        #print now.tzinfo
        #print lighton.tzinfo
        #print(datetime.datetime.now().strftime("%H:%M"))
        #print lighton < now
        #print now < sunrise
        #print now > sunrise
        #print now < lighton
        #print now > sunset

        if lights and lighton < now and now < sunrise:
                GPIO.output(7, True) # Lights
                light = "On"

        if now > sunrise and not GPIO.input(24):
                GPIO.output(7, False) # Lights

        if lights:
                if lighton < now and now < dusk:
                        if not GPIO.input(26) and runtime < 60: # Up Door Switch
                                print "The door is opening"
                                GPIO.output(4, True) # Motor FWD
                                GPIO.output(6, True) # Door Lock

                if now < lighton or now > dusk:
                        if not GPIO.input(27) and runtime < 60: # Down Door Switch
                                print "The door is closing"
                                GPIO.output(5, True) # Motor REV
                                GPIO.output(6, True) # Door Lock

        if not lights:
                if sunrise < now and now < dusk:
                        if not GPIO.input(26) and runtime < 60: # Up Door Switch
                                print "The door is opening"
                                GPIO.output(4, True) # Motor FWD
                                GPIO.output(6, True) # Door Lock

                if now < sunrise or now > dusk:
                        if not GPIO.input(27) and runtime < 60: # Down Door Switch
                                print "The door is closing"
                                GPIO.output(5, True) # Motor REV
                                GPIO.output(6, True) # Door Lock

        if GPIO.input(26): # Up Door Switch
                door_status = "Open"
        elif GPIO.input(27): # Down Door Switch
                door_status = "Closed"

        temp = os.popen("vcgencmd measure_temp").readline()
        tempC = float(temp.replace("temp=","")[0:4])
        tempF = round(9.0/5.0 * tempC + 32)
        if sys.stdin.isatty(): # running in a terminal so print messages
                print"{} Light is {}, Door is {}, Run Time {} CPU {}F {}C".format(now.strftime("%H:%M"),
                        light, door_status, runtime, tempF, tempC)
        else: # log the info to syslog
                my_logger.debug("{} Light is {}, Door is {}, Run Time {} CPU {}F {}C".format(now.strftime("%H:%M"),
                        light, door_status, runtime, tempF, tempC))

        # Update the web page need to know if lighton is before sunrise
        output = open("/var/www/html/chicken.html","w")
        output.write(htmlcontents.format(now.strftime("%b %d %Y %I:%M %p"),
                dawn.strftime(timeformat),
                sunrise.strftime(timeformat),
                sunset.strftime(timeformat),
                dusk.strftime(timeformat),
                lights,
                lighton.strftime(timeformat),
                dusk.strftime(timeformat),
                door_status,
                light,
                mode,
                tempF,
                tempC))
        output.close()

        """ the camera seems to cause some issues with the RPi3
        if not sys.stdin.isatty(): # this only works when running as a service
                # Update the webcam Video Capture 1280 x 1024 @ 30 fps
                # 1024 x 800
                os.system("fswebcam -r 800x600 /var/www/html/coop.jpg")
        """

def motor(): # monitor the motor when moving
        pass

update()

if not GPIO.input(22) and not GPIO.input(23):
        print "First Update"
        status()

schedule.every(1).minutes.do(status)
#schedule.every().hour.do(update)
schedule.every().day.at("02:00").do(update)

try:
        while True:
                if GPIO.input(22) or GPIO.input(23): # Manual Door Mode
                        manual = True
                        mode = "Manual"
                        runtime = 0
                else: # Auto Door Mode
                        manual = False
                        mode = "Auto"

                if GPIO.input(24):
                        GPIO.output(7, True) # Manual Lights

                if not manual:
                        schedule.run_pending()

                if manual:
                        if GPIO.input(22) and not GPIO.input(26):
                                GPIO.output(4, True) # Motor FWD
                                GPIO.output(6, True) # Door Lock
                        elif GPIO.input(22) and GPIO.input(26):
                                GPIO.output(4, False) # Motor FWD
                                GPIO.output(6, False) # Door Lock
                                print "Manual Door Up"

                        if GPIO.input(23) and not GPIO.input(27):
                                GPIO.output(5, True) # Motor REV
                                GPIO.output(6, True) # Door Lock
                        elif GPIO.input(23) and GPIO.input(27):
                                GPIO.output(5, False) # Motor REV
                                GPIO.output(6, False) # Door Lock
                                print "Manual Door Down"

                 #the door lock is on meaning the door is moving in Auto Mode
                if GPIO.input(6) and manual == False:
                        runtime += 1

                if runtime >= 75: # Something went wrong so shut down the motor and lock
                        GPIO.output(4, False) # Motor FWD
                        GPIO.output(5, False) # Motor REV
                        GPIO.output(6, False) # Door Lock
                        door_status = "Failed"

                if GPIO.input(4) and GPIO.input(26): # Motor FWD and Up Door Switch
                        GPIO.output(4, False) # Motor FWD
                        GPIO.output(6, False) # Door Lock
                        runtime = 0

                if GPIO.input(5) and GPIO.input(27): # Motor REV and Down Door Switch
                        GPIO.output(5, False) # Motor REV
                        GPIO.output(6, False) # Door Lock
                        runtime = 0

                if not GPIO.input(4) and not GPIO.input(5) and not GPIO.input(6):
                        if GPIO.input(26) or GPIO.input(27):
                                runtime = 0

                if GPIO.input(26):
                        door_status = "Open"
                if GPIO.input(27):
                        door_status = "Closed"
                if not GPIO.input(26) and not GPIO.input(27):
                        door_status = "Unknown"

                if GPIO.input(7): # Lights
                        light_status = "On"
                if not GPIO.input(7):
                        light_status = "Off"

                now = timezone('US/Central').localize(datetime.datetime.now())

                with canvas(device) as draw:
                        draw.rectangle(device.bounding_box, outline="white", fill="black")
                        draw.text((10, 5), "Door Status {}".format(door_status), fill="white")
                        draw.text((10, 15), "Lights are {}".format(light_status), fill="white")
                        draw.text((10, 25), "{}".format(now.strftime("%b %d %y %H:%M:%S")), fill="white")
                        draw.text((10, 35), "Mode is {}".format(mode), fill="white")
                        draw.text((10, 45), "CPU {}F {}C".format(tempF, tempC), fill="white")

                time.sleep(1)

except KeyboardInterrupt:
        # here you put any code you want to run before the program
        # exits when you press CTRL+C
        print "\nKeyBoard Interrupt"

except Exception,e:
        # this covers all other exceptions
        print str(e)

finally:
        GPIO.cleanup() # this ensures a clean exit