I live half a mile above sea level and am SURROUNDED by animals... bears, fox, turkeys, deer, squirrels, birds. Spring has arrived and there are LOADS of squirrels running around. I was in the building mood and, being a nerd, wished to combine a common woodworking project with the connectivity and observability provided by single-board computers (and their camera add-ons). And now I give you Squirrelkam!
Planning and Frame BuildThe first step was sketching out how big I wanted the squirrelhaus to be, where I wanted to place the kam, and whether the haus would be tied to an outlet (and, therefore tethered to the house's grid) or autonomous via some solar panels, lithium battery charging modules, and 18650 batteries...I'm big on autonomy and renewable energy so I opted for the latter.
Below are the haus measurements...I had 1/2" pine sitting around but I'm sure a haus made of something thinner would withstand some panels, a protoboard, some batteries, etc.
The base of the haus was measured out so as to fit atop/hug a deck rail built out of 2" x 6"s. After measuring the size of the panels I had (5.1" x 5.9", 5V 500mAh max 2.5W) I knew how big the roof of the haus had to be to accommodate panels to cover both sides of the roof. I also bypassed an optional feeder square in the center/top due to concerns over sealing and weatherizing. The convenience wasn't worth it, especially when there'd be more than enough room between the base and the lowest point of the roof to put in food.
Below is the finished frame with a few coats of gel stain and a couple coats of shellac.
The next step would be wiring up the panels with enough slack to be able to both attach to the protoboard and later pull away to add liquid nails before affixing to roof and holding in place overnight with a vice. I also wired up the 4 tp4056 lithium charging modules and the 4 18650 battery holders. The positive inputs to the charging circuits (from the solar panels) requires a blocking diode (1N4001) in series so as to not allow current from the batteries to flow through the panels at night when no voltage is present at/on the panels.
IMPORTANT NOTE: I wired the charging module circuits together in parallel so the 3.7-4.2V 2600 mAh batteries would keep at one voltage (4.2V max) but bring a punch of cumulative current/capacity...maxing out at 10400 mAh. Also, as I didn't have a charging module that'd charge several batteries with one input source I opted for the safe "1 panel per module/battery"
I then soldered in the ATtiny85 breadboard socket onto the protoboard (more below on the ATtiny85) along with the blocking diodes. After wiring the panels into the lithium charger inputs I was visually able to test my wiring by placing both sides of the haus into adequate sunlight and observe the red module "charging" LEDs illuminating.
Many sites have tested the Pi Zero W's power consumption. In this case, we'd be streaming video while it's on and connected to wifi so I had to take that into account. Overall the device(s) take around 225-250 mAh. THEORETICALLY, if the Pi were to run 24/7 it'd need 6000 mAh of juice. Since the 4 batteries only supply 10400 mAh you'd get less than 2 days of use out of the haus. Ah, but I added 4 panels, right? Assuming I'll only receive ~4 "great" hours of sunlight a day (my location) and also assuming no more than 250 mAh per panel per hour that'll only replenish around 4000 of the 6000 mAh used for the 24 hour period. And that assumption is for a "great" day of sun. If it were partially sunny or overcast I'd expect ~100 mAh (or less) per panel per hour. How do I reduce the consumption of the Pi Zero/camera? And does it even make sense to stream in the dark? In comes the LiPo SHIM, the BH1750, and the ATtiny85. NOTE: I also included in a python "clean shutdown" script a timekeeping function that shuts off power to the squirrelkam after 8 hours of work. I chose to reduce the workload of the pi vice purchase larger batteries/solar panels since code is "free" and the summer days are LONG so my calculations weren't for 12-14 hours of "work" per day for the squirrelkam.
The purpose of the LiPo SHIM is twofold. First, the Pi Zero operates on 5V BUT my batteries are only pushing 4.15V (when fully charged). The SHIM bumps LiPo inputs up to 5V while maintaining a ~96% efficiency rate! Sure, there are cheaper boost converters out there...But most are only 70-80% efficient, which is not acceptable in this case since I'm already in an energy consumption/production bind. The 2nd purpose of the SHIM is the nifty "enable" pin. When pulled to ground, the SHIM cuts power to the pi. This is where the ATtiny85 and BH1750 shine.
By measuring the outside lux (shooting for 100 lux which is equal to an overcast day) every 5 minutes I'm able to determine IF the environment is light enough to produce decent footage of streaming video or if it's too overcast, early, or late in the day and should instead shut down the pi. But I need to keep the ATtiny85 pin output held either high or low so I won't be able to sleep the ATtiny. No worries, though. When active and either measuring or in the delay portion of the code the ATtiny it (and the BH1750) use no more than 1.05 mAh. Over 24 hours 24 mAh is A LOT LESS than say...2500-3500 mAh from the pi running when it won't be effectively shooting watchable video. I've provided the code I loaded onto the ATtiny [using the Arduino IDE] below.
Below you'll find the Arduino IDE code I wrote and uploaded to the ATtiny85. The ATtiny reads the lux from the BH1750 every 3 minutes (delay in between). If the lux is UNDER 100 the output of pin 4 goes HIGH for 15 seconds. A script (python, function in while loop) checks the input of GPIO 17 on the pi every half second. If it senses a HIGH signal it sends out an email and runs sudo poweroff
. This allows time for a clean shutdown before the LiPo SHIM receivces the LOW signal from the ATtiny85 and cuts all power to the pi/kam. NOTE: If the LiPo SHIM didn't cut power to the pi/kam a constant draw of 45mA on the batteries will occur as the "shutdown" state of a pi isn't a true shutdown. If the lux level is above 100 the output is HIGH and the pi functions normally. The circuit schematic and python script I created is below. is also below. I didn't have time to create the LiPo SHIM part so the note will have to suffice as a placeholder.
After boot the script sends an "awake" email to your configured account (optional). The workday_start()
function checks the /home/pi director for a workday.txt file. If the file exists it then checks the creation date/time. IF the file exists and is less than 8 hours old the script continues on to the while loop functions. IF the file exists BUT is older than 8 hours then the script overwrites the file with the current timestamp (epoch). IF the file doesn't exist it creates the workday.txt file and writes the current timestamp to it. NOTE: This file is needed in a later function. The shutdown_check()
function checks the state of GPIO pin 17 every half second. If the input is HIGH the function sends a shutdown email and runs the sudo poweroff
command. This shuts the pi down before a hard power down occurs with the LiPo SHIM. If the input is LOW the function passes. The workday_over()
function opens the workday.txt file and compares it to the current time. This occurs every 3 minutes. IF the difference in times exceeds 8 hours then the script sends an email and runs sudo poweroff
. If less than 8 hours the function passes.
NOTE: This script, when part of the squirrelkam os, runs 30 seconds after boot due to being installed as a systemd service. If you don't wish to use this script you should run sudo systemctl stop shutdown_workday.service
followed by sudo systemctl disable shutdown_workday.service
.
#include <Wire.h>
#include <BH1750.h>
#include <avr/power.h>
//#include <avr/sleep.h>
//#include <SoftwareSerial.h>
//#define RX 5
//#define TX 1
//SoftwareSerial Seriall(RX, TX);
BH1750 lightMeter;
const int enablePin = 3;
const int shutdownPin = 4;
void setup(){
pinMode(enablePin, OUTPUT);
digitalWrite(enablePin, HIGH);
/*pinMode(0, INPUT);
digitalWrite(0, HIGH);
pinMode(1, INPUT);
digitalWrite(1, HIGH);*/
pinMode(2, INPUT);
digitalWrite(2, HIGH);
pinMode(shutdownPin, OUTPUT);
digitalWrite(shutdownPin, LOW);
//ADCSRA = 0;
//Seriall.begin(9600);
Wire.begin();
delay(50);
lightMeter.begin(BH1750::ONE_TIME_LOW_RES_MODE);
//Seriall.println(F("BH1750 One-Time Test"));
}
void loop() {
delay(50);
uint16_t lux = lightMeter.readLightLevel();
delay(50);
//Seriall.print("Light: ");
//Seriall.print(lux);
//Seriall.println(" lx");
if (lux < 100) {
digitalWrite(shutdownPin, HIGH);
delay(15000);
digitalWrite(enablePin, LOW);
delay(50);
digitalWrite(shutdownPin, LOW);
} else {
digitalWrite(enablePin, HIGH);
}
//delay(60000 * 5);
delay(60000 * 3);
}
#!/usr/bin/env python
import smtplib
from datetime import datetime as t
import RPi.GPIO as GPIO
import os
import time
import subprocess
import sys
from multiprocessing import Process
# listen on pi GPIO 17 for high signal
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN)
# keeps track of system timestamp, meant to hold squirrelkam ops to 8 hours
workday = '/home/pi/workday.txt'
# email info, if you want to receive wakeup and shutdown notifications.
MAIL_USER = 'emailaddress'
MAIL_PASS = 'emailpassword'
SMTP_SERVER = 'emailserver' #usually smtp.xxxx.com
MAIL_RECIPIENT = 'emailrecipient'
SMTP_PORT = 587 # may be a different port, gmail uses 465
def send_email(recipient, subject, text):
"""Sends an email when provided a recipient email address, subject, and text."""
if MAIL_USER != 'emailaddress':
server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
server.ehlo()
server.starttls()
server.ehlo()
server.login(MAIL_USER, MAIL_PASS)
header = 'To:' + recipient + '\n' + 'From: ' + MAIL_USER
header = header + '\n' + 'Subject:' + subject + '\n'
msg = header + '\n' + text + ' \n\n'
# server.set_debuglevel(1)
server.sendmail(MAIL_USER, recipient, msg)
server.close()
else:
pass
def workday_start():
"""Writes system start time to file. If exists and < 8 hours old, passes. If >, overwrites."""
if os.path.isfile(workday):
if (time.time() - os.stat(workday).st_ctime < 28800):
print('#shutdown_workday.py# file exists and less than 8 hours old.')
pass
else:
with open(workday, 'w') as f:
f.write(str(time.time()))
print('#shutdown_workday.py# file existed but was 8 hours old, overwriting.')
else:
with open(workday, 'w') as f2:
print('#shutdown_workday.py# file didn\'t exist so was created.')
f2.write(str(time.time()))
def shutdown_check():
"""Checks for HIGH signal on GPIO 17. This indicates light level has dropped below 100 lux."""
while True:
if GPIO.input(17) == 1:
print('#shutdown_workday.py# squirrelhaus going to sleep: ({:})'.format(t.now().strftime('%d-%b-%y %H:%M')))
send_email(MAIL_RECIPIENT, 'squirrelhaus sleep', 'squirrelhaus is going to sleep. ({:})'.format(t.now().strftime('%d-%b-%y %H:%M')))
with open(os.devnull, 'w') as too_dark:
powerdownpi2 = subprocess.Popen(('sudo', 'poweroff',), stdout=too_dark)
sys.exit(0)
else:
pass
time.sleep(0.50)
def workday_over():
"""Checks to see if difference between current time and time in file > 8 hours. If, shuts down."""
while True:
with open(workday, 'r') as g:
time_card = float(g.read())
if (time.time() - time_card) > 28800:
print('#shutdown_workday.py# squirrelhaus has completed 8 hours of work: ({:})'.format(t.now().strftime('%d-%b-%y %H:%M')))
send_email(MAIL_RECIPIENT, 'squirrelhaus clocking out', 'squirrelhaus has completed 8 hours of work. ({:})'.format(t.now().strftime('%d-%b-%y %H:%M')))
with open(os.devnull, 'w') as day_over:
powerdownpi1 = subprocess.Popen(('sudo', 'poweroff',), stdout=day_over)
os.remove(workday)
sys.exit(0)
else:
pass
time.sleep(180)
if __name__ == "__main__":
send_email(MAIL_RECIPIENT, 'squirrelhaus wake', 'squirrelhaus just woke up. ({:})'.format(t.now().strftime('%d-%b-%y %H:%M')))
print('#shutdown_workday.py# squirrelhaus just woke up: ({:})'.format(t.now().strftime('%d-%b-%y %H:%M')))
workday_start()
process1 = Process(target = shutdown_check)
process1.start()
process2 = Process(target = workday_over)
process2.start()
Camera Software, Homebridge, Sensor Libraries and Finished ProductThe camera software I ended up using was Calin Crisan's motioneye. It's a GREAT frontend to the Motion software program. Additionally, I discovered a NEAT project which proxies network in such a way that the cameras show up in homekit. It's called homebridge-camera-ffmpeg. I also ended up adding some sensor code (python libraries/modules) for reading lux (BH1750), temperature/humidity/pressure (BME280) and the current/voltage-sensing INA219 sensor. NOTE: These sensors will be integrated soon. Below is a list of the raspbian-based image with preinstalled software and usernames/passwords.
squirrelkam os software- based on raspbian-lite (april 2018)
- mid-April build of FFmpeg (with all the fixins') and motion 4.1.1
- nodejs 8.11.1, homebridge, homebridge-camera-ffmpeg
- modifications to /boot/config.txt and /etc/rc.local (save power)
- scripts/code for various sensors (bh1750, ina219, bme280)
- screen, git, python (pyserial, paho-mqtt)
- user = pi, password = raspberry
- hostname = squirrelkam.local
- timezone = US/Eastern
- enabled = ssh, i2c, i2s, spi, camera, serial port
- disabled = HDMI (added /usr/bin/tvservice -o to /etc/rc.local)
- homekit pin # = 123-45-678
- motioneye (kam) = port 80, admin (no password), user (no password)
- Burn squirrelkam.img to an SD card with Etcher.
- open /boot/wpa_supplicant.conf and replace ssid and pass with your router's info.
- BOOT!
NOTE: You can easily find your squirrelkam by going to squirrelkam.local or SSHing into pi@squirrelkam.local. Additionally, when adding your camera to homekit you won't have a barcode so you'll add manually. GREAT THING, however, is that it's found automatically and should be as simple as tapping on the camera and entering the pin #!
Below is the finished product...and a screenshot from my fully-functional squirrelkam!
Comments