Saturday 21 September 2019

Bird box activity counter v3: Build, detect & log activity

Update Dec 2019:  see part 2 of this series that deals with logging entrance hole activity to a postgres database and text file:

I've built entrance hole detectors into two existing bird boxes, lets call those counter versions 1 + 2.
This year, the v.2 counter box had two consecutive Great Tit nesting sessions, 8 chicks fledgling in total (7 and 1).  With the data it records, it can generate informative visuals like this one that shows daily counts of 'in events' for nesting sessions 1 and 2.  Can you work out which was the more successful 2019 brood?

Nest box activity for two consecutive nesting sessions in Spring 2019


I re-visited this from an earlier version (v1), this post describes version 3.  It uses the same principle as a commercial product from Schwegler that displays a local count - their product doesn't do anything else and will set you back approx £65.

Commercial counter displayed on the of the box front

My detector uses a pair of infrared (IR) beams & detectors that are offset from each other giving an 'outer' and an 'inner' beam.  A bird coming in will break the outer beam before the inner one and vice versa for a bird exiting the box.  Beam disruption events are logged to a text file on the integral  Raspberry pi Zero W which also runs video motion capture software, controls the lighting etc.

By having two detectors, its possible to differentiate between 'in' vs 'out' events.  Sensor noise such as a bird popping its head in from the outside or a spider jumping up and down on one IR LED can be ignored.  This is better than a 'one beam' approach that would produce noisy data that would be impossible to clean.

My design is an evolved version of a project in the 'Raspberry Pi projects' book by Robinson & Cook.  Don't buy the book for this project as it uses obsolete hardware (something called a 'PiFace', I used that in my v1 counter from 2014), the python code is chock full of mistakes, the code also isn't available to download on the publisher's website.

I reckon my v3 version is simpler to do and I've fixed the code side of things too 😏

Parts / equipment
2x 5mm IR LEDs buy here
2x IR phototransistor QSE113 buy here
Some 0.25 watt resistors: 1x 100ohm, 2x 1k ohm, 2x 10k ohm
Small piece of stripboard (grandly(!) referred to as the 'interface board' below)
1.6mm / 2.4mm heat shrink tubing
Wire.. I buy one roll of black 2 core firework shooting wire every few years, and a box of ethernet cable provides virtually endless colour-coded twisted pairs of low gauge wire.
Raspberry pi Zero W + power source

For the box:
Plywood (front of bird box): 1x 18mm piece and 2x 3mm pieces
30mm flat drill bit for the entrance hole
Router with a narrow cutter for the cable runs, e.g. this 3.2mm one.

The idea is to create a plywood sandwich for the v3 counter, with the 18mm ply between two 3mm pieces.  In contrast, v1 and v2 used more layers of plywood and was more fiddly to make.

How to make this....
Start off by clamping the three plywood pieces together.  Drill the entrance hole through all three ( I use a 30mm hole) then put the inner and outer 3mm plywood pieces to one side.
Drill 4 small 'guide holes' through the inner 18mm piece only, in approx a square (ish) arrangement around the entrance hole.  Use these as guide to route a diagonal channel on either side.  The channels need to intersect the guide holes   You want an 'X' shape  with one / and \ channel cut on each side, some of these guide holes will also double up as cable conduits.  Cut a couple of cable channels for the LED and phototransducer as shown below.  Its important that the cable channels are not full thickness.


Cut a recessed chamber on the 'outer' face of the 18mm ply piece that is deep enough to fit a small piece of stripboard (no smaller than 8 holes along the top, 10 on the side).  Drill another small hole through the ply in the recess that connects to a channel that runs up to the top of the inner face.  Power and GPIO connections to the Raspberry Pi come out via this route to the Pi in the top of the bird box.

Routing channels for the phototransistor and LEDs is a bit fiddly, here's a closeup of my efforts.  Any over cut/gappy bit around the detector can be filled with blu-tac or wood filler.


Before making the 'Interface board' I prototyped it out to make sure it worked


Breadboard fiddling
The resistors are as follows:
1) 100 ohm for the in-parallel LED circuit (220 ohm may also be okay)
2) 10k ohm pull up resistor to 5V for the transistor collector
3) 1k ohm resistor for the transistor collector GPIO connection

A handy hint for making stuff with IR LEDs: You cant see when they're on, however if you point a digital camera (with a screen on it) at them, then the camera can see it.

The breadboard prototype evolved into this shrunk down version on stripboard:

'Interface' resistor board

I use 2 core cable to wire the phototransistor emitter and collector legs as shown above, and a couple of twisted pair wires from a piece of ethernet cable for the LEDs, using different colour twisted pair wires for each LEDs - this make is easier to tell them apart.  I'm in the habit of using the stripy one of the pair for the + and the solid colour for the - pole (helps avoid soldering stuff in-situ the wrong way around).

Wire the transistors and LEDs cables first, then slot them into their routed channels and solder in-situ to the prepared interface board as shown above.

There are 4 connections to be made coming off the interface board: (1)5v and (2) ground [GND] (3) GPIO_1 for inner beam, (4) GPIO_2 for outer beam connections.  For the 5V and GND I use two core cable again, and coloured twisted pair for the GPIOs - as before, having a stripy one and solid coloured one for the GPIO connections helps you tell which is which.

How you inerface this with your raspberry pi is up to you, these 4 wires can be connected directly to the appropriate GPIO pins.  In my boxes, the 5V and GND will connect direct to the recom switching regulator (R-78B5.0-1.5) that drops the box's 12v feed to 5v. 

In this setup the IR LEDs are 'always on'.  I cant forsee a situation where I may need to turn them off but you could alternatively wire the LEDs via a second GPIO for power and power them on/off programatically.

The holesensor.py python script writes a single line to a log file if one of the beams transitions from broken to whole or vice versa.

The logfile logic is as follows:  BEAM,STATE,event time
Outer beam = 1; Inner beam = 2
Whole = 1; Broken =0

In this excerpt from the log, a bird has come into the box:
Outer beam broken, event_time
Inner beam broken, event_time
Outer beam whole, event_time
Inner beam whole, event_time

### recordBird_v3 starting up at:21 Sep 2019 09:47:35.990
1,0,21 Sep 2019 09:48:04.100
2,0,21 Sep 2019 09:48:04.446
1,1,21 Sep 2019 09:48:05.180
2,1,21 Sep 2019 09:48:05.228

The python script 'holesensor.py' that records this is detailed below.
On my 'to do' list is to modify this to record to a database rather than a static text file. 
Having this recorded to a database opens up the possibility of connecting remotely to this box and directly querying its on-board database.

The 'log to file' version is below.  I'll write up the graphing functions in another post...

import RPi.GPIO as GPIO
from time import sleep
import time
import datetime

# Change log
# 16/09/19 updated for zerocam 7

GPIO.setmode(GPIO.BCM)          #use BCM pin numbering system
GPIO.setwarnings(False)

whichBirdcam = 'zerocam7'

#file used to log entrance actions
EntrancelogFile='/home/pi/testpizero/logs/zerocam7_birdlog.txt'
OpenEntrancelogFile= open(EntrancelogFile,  'a', 0)

#function to return the current time, formatted as
# e.g. 13 Jun 2013 :: 572
def getFormattedTime():
    now = datetime.datetime.now()
    return now.strftime("%d %b %Y %H:%M:%S.") + str(int(round(now.microsecond/1000.0)))

#generate and record an event to file
def logEntranceEvent(sensor, state):
    OpenEntrancelogFile.write(str(sensor) + "," + str(state) + "," + getFormattedTime() + "\n")


#setup GPIOs

detect_OUTER = 22 #6    #set GPIO pin for Outer photransducer (input)
detect_INNER = 23 #12    #set GPIO pin for Inner photransducer (input)

print 'detect_OUTER = ' + str(detect_OUTER)
print 'detect_INNER = ' + str(detect_INNER)

#Constants
#OUTER_BEAM = 1
#INNER_BEAM = 2

#WHOLE = 1
#BROKEN = 0

# setup GPIO pins:
GPIO.setup(detect_OUTER, GPIO.IN)   #set Outer GPIO Phototransducer as input
GPIO.setup(detect_INNER, GPIO.IN)   #set Inner GPIO Phototransducer as input


#indicate the point the program started in the log
OpenEntrancelogFile.write("### recordBird_v3 starting up at:" + getFormattedTime() + "\n")
print "============================================"
print whichBirdcam.upper() + ": Starting up entrance hole counter script..."
sleep (0.5)

#Set initial state of WasBroken for both beams:
OUTER_WasBroken = False
INNER_WasBroken = False

# LED status check
# LEDstate= GPIO.input(detect_INNER)

print ""
print "detect_OUTER status = " + str(GPIO.input(detect_OUTER))
print "detect_INNER status = " + str(GPIO.input(detect_INNER))
print ""


#When the detector          'sees' IR led, the detector pin is 0/LOW/False
#When the detector does not 'see ' IR led, the detector pin is 1/HIGH/True

def checkStatus():
    if GPIO.input(detect_OUTER):  #if OUTER detector does not see IR led, print error, GPIO.input = HIGH
        print "OUTER beam detect failure!, status = " +str(GPIO.input(detect_OUTER))
        #quit()
    else:
        print "OUTER beam detect - passed :) | Status = "+str(GPIO.input(detect_OUTER))

    if GPIO.input(detect_INNER):  #if INNER detector does not see IR led, print error, GPIO.input = HIGH
        print "INNER beam detect failure!, status = " +str(GPIO.input(detect_INNER))
        #quit()
    else:
        print "INNER beam detect - passed :) | Status = "+str(GPIO.input(detect_INNER))
        print "============================================"
        print ""

def status2():

    print "============================================"
    print "OUTER_IsWhole = "+str(OUTER_IsWhole)
    print "OUTER_WasBroken = "+str(OUTER_WasBroken)
    print ""
    print "INNER_IsWhole = "+str(INNER_IsWhole)
    print "INNER_WasBroken = "+str(INNER_WasBroken)
    print "============================================"
    print ""

checkStatus()

# (x,y)
#  x=beam   (1=Outer,2=inner)
#  y=state  (1=Whole,0=Broken)

while (True):
    OUTER_IsWhole = (GPIO.input(detect_OUTER) == 0)  #read current state of beam
    INNER_IsWhole = (GPIO.input(detect_INNER) == 0)  #read current state of beam
    
    sleep(0.01)

    if (not OUTER_IsWhole and not OUTER_WasBroken): #if OUTER beam is broken [FALSE], and OUTER_WasBroken=FALSE (ie default value)
        OUTER_WasBroken = True
        print "(OUTER,Broken)"+ getFormattedTime()
        status2()
        logEntranceEvent(1,0)


    if (OUTER_IsWhole and OUTER_WasBroken): #if Outer beam is whole [TRUE] and OUTER_WasBroken=TRUE
        OUTER_WasBroken = False
        print "(OUTER,Whole)"+ getFormattedTime()
        status2()
        logEntranceEvent(1,1)


    if (not INNER_IsWhole and not INNER_WasBroken): #if INNER beam is broken [FALSE], and INNER_WasBroken=FALSE (ie default value)
        INNER_WasBroken = True
        print "(INNER,Broken)"+ getFormattedTime()
        status2()
        logEntranceEvent(2,0)


    if (INNER_IsWhole and INNER_WasBroken): #if INNER beam is whole [TRUE] and INNER_WasBroken=TRUE
        INNER_WasBroken = False
        print "(INNER,Whole)"+ getFormattedTime()
        status2()
        logEntranceEvent(2,1)

GPIO.cleanup()

Here is a 'production version' of this being setup.  I swapped out the camera unit from an existing box.  You can see the IR leds in this camera - the're invisible to humans/birds


2x entrance hole IR beams - Digital camera view only!



4 comments:

  1. Excellent! Very interesting.

    I'd like to see a graph showing the difference between "in" and "out" counts. I've always suspected these will be a little wild during the period when birds are checking out the box (lots of looking in the box without getting fully in). And during the feeding stage, I'd expect the "in" and "out" counts to be a better match.

    ReplyDelete
    Replies
    1. Easy to do Steve, I also want to see if I can work out some sort of 'time spent on station' metric - this is probably not easy when both parents are present, but in nesting season #2 the male went AWOL, so I have a handy control dataset

      Delete
  2. Fantastic Chris, this is brilliant and just shows you what you can do with a few components and script. This has a lot more scope for more metrics as you can measure more over time than standard loggers. Also, you could probably cross correlate with other metrics like temp, light etc. Well done Chris, this is really good and would be perfect area for research. Cheers Mike and team handykam.com

    ReplyDelete
    Replies
    1. Thanks Mike, glad you like it. That box also monitors (internal) temperature and humidity every minute... I havent looked for a luminosity (?) sensor but did build a basic light monitor into it with an LDR and capacitor - basically this tutorial: https://pimylifeup.com/raspberry-pi-light-sensor/ it isnt especially accurate, and would rather get a dedicated module for future applications

      Delete