Emulating the Sunset

Published on Sat, 9 November, 2019 | 1000 words
Tags: ikea lighting dioramas transformers python raspberry pi bash hardware

I like to combine my hobbies. I like collecting (and displaying) 1980s Transformers toys. I like building dioramas. I like tinkering with electronics and the Raspberry Pi, and I like programming. So I had to do this.

My Optimus Prime cabinet diorama

I built a Grand Canyon diorama in an Ikea Besta cabinet for my original 1985 Optimus Prime to sit in, and used some Ledberg LED strips to light it.

I’m not going through the process of making the diorama, this isn’t the blog for that, however I’m quite proud of the way that the scene is lit. Because the Ledberg is an RGB LED strip, and it can be controlled exactly the same way as the Dioder (see my previous post on how to do this), you can do some fun things with just a few lines of Python. So I’ve got the light changing colour to emulate the sun in real-time above the actual Grand Canyon.

I’ve never been to the Grand Canyon, and I’ve no real emotional attachment to it. It’s just that I wanted a rocky, south-west-US-style landscape for Optimus to sit on, just like the 80s cartoon series. When making the background, I found a copyright-free photo of the Grand Canyon online that matched the foam rocks I’d built perfectly, so this is just how it ended up. It also just so happens that the Grand Canyon has multiple live cameras all over it, streaming images to the web, meaning I can tell with a bit of Python what colour the sky is over the canyon in real time.

As I wrote the code, I came across another issue. The Grand Canyon is in Arizona, and I’m in the UK. So when the sun is shining on a nice summer morning here in Southampton, the Grand Canyon is in darkness.

Implementation

I won’t re-cover the electronics part because I’ve talked about controlling Ikea lights with a Raspberry Pi before. The only difference this time is that I’m using the Ledberg, which expects 24V as opposed to the Dioder which expects 12V. The rest of the circuit is identical.

The programming is the interesting part. I’ve got a BASH script to download the latest webcam image from the US National Park Service website, and a Python script that actually changes the colour of the LED, handing the time-shifting as we do so. Sadly, the Grand Canyon is west of me, which means that to adjust the time I need to display an image from yesterday rather than earlier the same day, but it’s not like I’m using this script for accurate weather reports, I just need the sky to be roughly the right colour for the time of day.

download.sh

#!/bin/bash
URL="https://www.nps.gov/featurecontent/ard/webcams/images/grca.jpg"
HOUR=$(date +%H)
MINUTE=$(date +%M)
if [ "$MINUTE" -lt "30" ]
then
 FILENAME=/home/pi/sunset/photos/${HOUR}00.jpg
else
 FILENAME=/home/pi/sunset/photos/${HOUR}30.jpg
fi
wget -O /tmp/sunset.jpg $URL
FILESIZE=$(stat -c%s /tmp/sunset.jpg)
if [[ $FILESIZE -gt 100000 ]]
then
 cp /tmp/sunset.jpg $FILENAME
fi
rm /tmp/sunset.jpg

This does a bit of string slicing to round the filename down to the last half-hour, downloads the file, checks it to make sure it’s big enough - the NPS camera occasionally goes down and displays a placeholder image - and saves it in the right place if so.

set_colour.py

#!/usr/bin/python3
from __future__ import print_function
from PIL import Image
import numpy as np
import binascii, struct, scipy, scipy.misc, scipy.cluster, datetime, os, sys, pigpio
RED_PIN = 17
GREEN_PIN = 18
BLUE_PIN = 27
NUM_CLUSTERS = 5
TIMEZONE = 8
now = datetime.datetime.now()
hour = now.hour + TIMEZONE
minute = int(now.minute)
if(hour > 23):
 hour = hour - 24
if(hour < 0):
 hour = hour + 24
if(minute < 30):
 filename = os.path.abspath(os.path.dirname(sys.argv[0]) + '/photos/' + ('00' + str(hour))[-2:] + '00.jpg')
else:
 filename = os.path.abspath(os.path.dirname(sys.argv[0]) + '/photos/' + ('00' + str(hour))[-2:] + '30.jpg')
try:
 im = Image.open(filename)
except:
 sys.stderr.write("File doesn't exist yet.\n")
 sys.exit(1)
print(filename)
im = im.crop((0, 0, 640, 140))
im = im.resize((140, 140)) # squish the image, speeds up the clustering
ar = np.asarray(im)
shape = ar.shape
ar = ar.reshape(scipy.product(shape[:2]), shape[2]).astype(float)
codes, dist = scipy.cluster.vq.kmeans(ar, NUM_CLUSTERS)
vecs, dist = scipy.cluster.vq.vq(ar, codes)
counts, bins = scipy.histogram(vecs, len(codes))
index_max = scipy.argmax(counts)
peak = codes[index_max]
red = int(peak[0])
green = int(peak[1])
blue = int(peak[2])
pi = pigpio.pi()
pi.set_PWM_dutycycle(RED_PIN, red)
pi.set_PWM_dutycycle(GREEN_PIN, green)
pi.set_PWM_dutycycle(BLUE_PIN, blue)
pi.stop()

This is a bit more complex, hence the Python. Obviously you need to set the red, green and blue pins depending on how you’ve wired the lights to the Pi. The rest of the script calls the image for the correct time (controlled by the TIMEZONE variable), and, if it exists, checks the dominant colour of the sky before setting the LEDs to that colour. It gives a bit of info when it quits to say whether it actually did anything or not, which is good for debugging.

Now, all that is needed is to put the scripts into the Pi’s cron…

5,35 * * * * /home/pi/sunset/download.sh ; /usr/bin/python3 /home/pi/sunset/set_colour.py

The NPS image is updated every 30 minutes, so running the script at 5 and 35 minutes past the hour is sensible.

The download script overwrites previous images only if it can, so if the camera is unavailable it uses the last known image for that time of day. There are plenty of other webcams around the Grand Canyon if the NPS one goes down for a long time (as it does sometimes). Check Shodan if you can’t find one on Google.