gavd.co.uk

Frail and faltering follower of Jesus

Nibbles Cam - Monitoring Our Hamster with Raspberry Pi + Python

By Gavin Davies

A quick and dirty hamster monitoring solution using 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.

/articles/nibbles-cam/img/nibscam.jpg

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

/articles/nibbles-cam/img/nibs.png


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:

/articles/nibbles-cam/img/plan.jpg

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!

/articles/nibbles-cam/img/blacksquare.png

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:

/articles/nibbles-cam/img/lamp.jpeg

It did the job! I could see her in her favourite tube where she likes to sleep.

/articles/nibbles-cam/img/justenoughlight.png

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.

/articles/nibbles-cam/img/setup.jpg

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!

/articles/nibbles-cam/img/spensive.jpg

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:

  1. No audio needed - remove sudo apt-get purge pulseaudio
  2. I disabled Bluetooth in /boot/config.txt
  3. 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
  4. 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.
  5. Ran sudo raspi-config to set up the Pi to be in headless mode to save 50mb or so RAM
  6. sudo apt-get autoremove