Nibbles Cam - Monitoring Our Hamster with Raspberry Pi + Python
We were due to go on holiday, but my eldest child was loath to leave our beloved family hamster, Nibbles Fluffy the First, behind.
Thankfully, I had anticipated this and bought a Raspberry Pi 3, which is cheap at £29, with the intention of hooking it up to an existing webcam that I wasn’t currently using.
I added a case, heatsinks, and a cooling fan, and got to work writing some software.
Gotta go fast!
I didn’t have long to set up NibblesCam. This would not be an elegant solution; this would have to be m’specialty - a bodge job.
Writing enough Python to grab a screenshot from a USB webcam
Initially, I tried using OpenCV that the Internet At Large recommended.
The problem was, OpenCV took 50 bajillion hours to compile and install on my Raspberry Pi 3. I left it overnight, the installer crashed… So I looked for an alternative.
1.py
- create the screenshot
On Sebastian Wallkoetter’s blog, I found a solution that did not require OpenCV
import imageio as iio
camera = iio.get_reader("<video0>")
screenshot = camera.get_data(0)
camera.close()
iio.imwrite("file.png", screenshot)
2.py
- annotate the screenshot
I found this article on geeksforgeeks showing me how to use Pillow to add text to an image:
from PIL import Image
from PIL import ImageDraw
from datetime import datetime
now = datetime.now()
# Open an Image
img = Image.open('file.png')
# Call draw Method to add 2D graphics in an image
I1 = ImageDraw.Draw(img)
# Add Text to an image
I1.text((10, 10), "NibblesCam " + now.strftime("%Y/%m/%d, %H:%M:%S"), fill=(255, 0, 0))
# Save the edited image
img.save("nibs.png")
Great! That did the job. I now had a pair of scripts to take a screenshot and annotate it.
The results
Now… Where to host it?
I initially thought “I could host it on my Google Photos account”! Great idea!
Except Google’s APIs no longer supports simple tokens; it requires OAuth, setting up a project with Google, a massive SDK that I couldn’t get to work, and they might charge me for using it, and blah blah blah OH NO THIS FEELS LIKE ACTUAL WORK so after an hour with this I nope’d out.
yes, yes, yes, it’s better than token auth, but it’s looooooong to set up and a complete faff
So I thought “how about a simple EC2 setup”. I sketched it up:
But then I sacked that off as well, and simply hosted on S3, so I could scp
files up on every run. I set up a simple AWS user with limited permissions just for the bucket and Cloudfront distro I wanted to use.
Sync script
echo "Generate the image"
python 1.py
echo "Label the image"
python 2.py
echo "Sync the image"
aws s3 cp nibs.png s3://<REDACTED/nibblescam/latest.png
echo "Sync the image backup"
timestamp=$(date '+%Y%m%d%H%M%S')
aws s3 cp nibs.png s3://<REDACTED>/nibblescam/$timestamp.png
echo "Invalidate cache"
aws cloudfront create-invalidation --distribution-id <REDACTED> --paths "/nibblescam/latest.png" "/nibblescam/archive.html"
echo "Add to archive"
echo "<li><a href='${timestamp}.png'>${timestamp}.png</a>" | cat - archive.html > temp && mv temp archive.html
echo "Sync archive"
aws s3 cp archive.html s3://<REDACTED>/nibblescam/archive.html
I configured this on a good ol’ * * * * *
Cron job, so it runs every minute.
Astute AWS sausages may have noticed a problem here!
Wrapper HTML
I made a super simple index file for the site that simply showed the latest image:
<h1>Nibbles cam</h1>
<img src="latest.png">
<p><em>Nibbles Cam updates once per minute. <a href="archive.html">Archive</a></em></p>
Troubleshooting
Trouble 1: night is dark!
The first problem we had was that it took poor photos overnight. I say poor, basically rank scrubberosity - a black skwarr!
So, I added a gentle lamp on a timer. I didn’t want to upset Nibbles with a bright light so there’s nothing pointed directly at the cage, just ambient lighting set back from the cage, just enough to see the lil furball:
It did the job! I could see her in her favourite tube where she likes to sleep.
Trouble 2: Instability
I was out with the kids, the day before we were due to go away, and NibblesCam stopped posting! Oh noes!
We scurried home and I looked at the logs. journald showed a reboot, but bizarrely there were some out of sequence logs appearing after the boot, as if something was stuck.
There were a few suspect utilities knocking around the Pi that we didn’t need, so I did a cleanup - see Appendix B
I then configured an hourly reboot, just to increase confidence.
Ready to rock!
Well, that worked, I guess, on a simple test it seems solid! Eldest child is happy, a simple project done. All I need to do is disconnect it when I get home.
Failure in the field.
It worked perfectly!
For 30 hours. Then it just stopped uploading to S3.
And it’s just as well! The cache invalidations on-the-minute-every-minute were costing my AWS account $15 a day!
I configured the /nibblescam/*
path to no longer be cached by Cloudfront, solving the cost issue.
Debugging
I don’t yet know what the problem was. I suspect it’s the same as the problem I saw before. The hourly reboot didn’t fix it. It seems that the failure, at 05:42 on the second day of our holiday.
Prior to it, I do see:
Mar 25 05:41:05 nibblescam kernel: uvcvideo 1-1.2:1.1: Failed to resubmit video URB (-1).
but I see that error pop up in the logs every so often, so it’s unlikely to be the Big Cause. Judging by the logs, the script keeps on running, but I can’t see what it’s doing, only that Cron ran. So, I configured some additional logging:
* * * * * /home/gavin/go.sh >> /home/gavin/niblog 2>&1
I know my home network wasn’t down as my NAS was moaning to me about packages. I also added set -e
so the script will exit if any command fails.
Speedtest
On cable in my office:
Hosted by Lightning Fibre Ltd (London) [206.54 km]: 12.691 ms
Testing download speed................................................................................
Download: 87.55 Mbit/s
Testing upload speed......................................................................................................
Upload: 32.26 Mbit/s
On wifi by Nibbles:
Retrieving speedtest.net configuration...
Hosted by Lightning Fibre Ltd (London) [206.54 km]: 14.167 ms
Testing download speed................................................................................
Download: 29.99 Mbit/s
Testing upload speed......................................................................................................
Upload: 11.32 Mbit/s
As such, my WiFi seems adequate, provided it is stable, and I have no reason to believe it isn’t.
Theories on the fault
I was using a 2 amp power supply. The stated need of the RPi 3b is 2.5a at 5v so maybe it was underpowered and choked? Seems odd though because it was definitely running, it wasn’t off…
Verdict: Improperly software and all that
Well, I suppose this is what happens without extensive testing. Like I say, I only had a day or so to get this all ready, including packing bags and all the regular mundanities of life.
It would have been better in many way to buy one of those cheap pet cams. They go for less’n 20 quid on Amazon. Still, probably loaded with Spyware, plus they’re only really a loss-leader to get you to pay a subscription.
So I think, overall, this was a noble failure. If we go away again, the house will be full of ninjas with swords to fend off robbers (naturally), but also NibblesCam v2.0…
I’ll update this post if I ever figure out the problem!
Please note that whilst we were away for 4 nights, a friend came over to care for Nibbles. Nibbles is a very well-loved hamster who comes out to play with us every day, and is happy and healthy. She’s an absolute sweetie, very gentle, and a bit of a scamp!
Appendix A: dependencies
I had to install a bunch of dependencies. I’m not sure all of these are actually required, nor am I sure I haven’t missed anything - my notes flip-flopped around a lot as I tried and abandoned various tools!
# Install OS level tooling
sudo apt-get install libopenblas-dev awscli vim
# Install required Python libraries
pip install imageio[ffmpeg]
Appendix B: disabling stuff I don’t need or installed by mistake
Again, I don’t recall everything but:
- No audio needed - remove
sudo apt-get purge pulseaudio
- I disabled Bluetooth in
/boot/config.txt
- I’d installed PHP along the way when I was trying to get Google Photos to work, and it was running in the background, so I removed it
sudo apt-get remove apache2
no idea why that was installed, whether it was dragged in with a package I installed or if it’s stock. Probably came in with PHP.- Ran
sudo raspi-config
to set up the Pi to be in headless mode to save 50mb or so RAM sudo apt-get autoremove