Sense Hat Fireworks
It's November the 5th, everyone is heading out to watch the fireworks and play with sparklers, but this year we fancied making our own little home display.
The Raspberry Pi Sense HAT has a gorgeous 8 by 8 array of LEDs which make the perfect platform to build our own firework display.
Setting up our script
To start off with, we need to import the Sense HAT library. And because we're going to need to delay our fireworks, we should import sleep too.
from sense_hat import SenseHat
from time import sleep
Now we've got some libraries to start with we also want to initialise our Sense HAT, so just below this we should add:
sense = SenseHat()
Making Fireworks
So we've got a Sense HAT to use, the first step is to build a firework that will explode on our display. The best bit of every firework is the explode-y bit once the rocket has launched, so lets start with that.
The LEDs are numbered from 0-7 starting and the top left hand corner is (0, 0) and the bottom right is (7, 7). To test our firework, we should start roughly in the middle at (3, 3)
The function below will turn on the LED starting at (3,3) and then move up, down, left and right away from (3, 3) turning on and off the LEDs as it moves. Our fireworks will be red because we've chosen (255, 0, 0):
def firework_head():
for x in range(4):
sense.set_pixel(3-x, 3, 255, 0, 0)
sense.set_pixel(3, 3-x, 255, 0, 0)
sense.set_pixel(3+x, 3, 255, 0, 0)
sense.set_pixel(3, 3+x, 255, 0, 0)
sleep(0.5)
sense.clear()
while True:
firework_head()
Next we need the rocket flames as it launches up from the ground! This is another routine which starts at (7,3) which is roughly in the middle and moves up to the middle before stopping. Fire from rockets is kinda orange so lets go for (255, 100, 0) as a pixel colour. And fire leaves a trail in the sky, so we can leave our LED on after each step.
def firework_tail():
for x in range(4):
sense.set_pixel(3, 7-x, 255, 100, 0)
sleep(0.5)
while True:
firework_tail()
sense.clear()
So now we've got a tail and a head we can combine both of these functions to build a firework function.
def firework():
firework_tail()
firework_head()
sense.clear()
sleep(0.5)
while True:
firework_tail()
firework_head()
sense.clear()
sleep(0.5)
Randomly Placing Fireworks
Unless you're at a super professional planned display with lots of fireworks, they don't always end up in the same bit of sky, so whilst our fireworks are nice, it would be great if they were a bit more random. So lets put some randomness into our fireworks.
We need an extra bit of code adding to the import statement to allow us to use random numbers:
from random import randint
And now we're going to modify our firework,firework_head and firework_tail functions to include random positioning.
def firework_head(i, j):
for x in range(4):
sense.set_pixel(i-x, j, 255, 0, 0)
sense.set_pixel(i, j-x, 255, 0, 0)
sense.set_pixel(i+x, j, 255, 0, 0)
sense.set_pixel(i, j+x, 255, 0, 0)
sleep(0.5)
sense.clear()
def firework_tail(i, j):
for x in range (7, j, -1):
sense.set_pixel(i, x, r, g, b)
sleep(0.5)
def firework():
i = randint(0, 7)
j = randint(0, 4) # Explode our fireworks in the sky, we'd hate to hit the crowd between 5-7.
firework_tail(i, j)
firework_head(i, j)
sleep(0.5)
while True:
firework()
sense.clear()
sleep(0.5)
If we run this script for a little bit, it might work fine, but all of a sudden we'll get an error from the Pi and everything will stop.
ValueError('Y position must be between 0 and 7')
Our fireworks can't explode beyond where the crowd can see the fireworks, so we need to make sure we check that our explosion is within our LED grid each time we go to explode. Each of the sense.set_pixel commands in firework_head needs an if statement to check that we're asking for a good explosion location.
def firework_head(i, j):
for x in range(4):
if(i-x >= 0): sense.set_pixel(i-x, j, 255, 0, 0)
if(j-x >= 0): sense.set_pixel(i, j-x, 255, 0, 0)
if(i+x <= 7): sense.set_pixel(i+x, j, 255, 0, 0)
if(j+x <= 7): sense.set_pixel(i, j+x, 255, 0, 0)
sleep(0.5)
sense.clear()
Now if we run our script we get a beautiful red set of fireworks all across the sky.
Random Coloured Fireworks
The firework displays we all go to have lots of different coloured fireworks, so lets make our fireworks a random colour too. We we can add random number generators to each of the firework_head colours going into sense.set_pixel:
def firework_head(i, j, r, g, b):
for x in range(4):
if(i-x >= 0): sense.set_pixel(i-x, j, 255, 0, 0)
if(j-x >= 0): sense.set_pixel(i, j-x, 255, 0, 0)
if(i+x <= 7): sense.set_pixel(i+x, j, 255, 0, 0)
if(j+x <= 7): sense.set_pixel(i, j+x, 255, 0, 0)
sleep(0.5)
sense.clear()
def firework_tail(i, j):
for x in range (7, j, -1):
sense.set_pixel(i, x, 255, 100, 0)
sleep(0.5)
def firework():
i = randint(0, 7)
j = randint(0, 4) # Explode our fireworks in the sky, we'd hate to hit the crowd between 5-7.
r = randint(1, 255) # 0 is off so we don't want blank fireworks, lets start at 1.
g = randint(1, 255)
b = randint(1, 255)
firework_tail(i, j)
firework_head(i, j, r, g, b)
sleep(0.5)
while True:
firework()
sense.clear()
sleep(0.5)
Finishing our Display
The last thing to do is to get our display to launch more than one firework at a time. For this we're going to use threading.
At the moment we're setting off one firework and then waiting until our firework has exploded and disappeared before we set off the next firework. This is ok, but normally fireworks go off all at different times, or in clumps to fill the sky with pretty colours.
Threading allows us to set one firework off in a thread and immediately set off another firework in another thread before waiting for the first one to finish. For this, we need another library. So add to the import statement at the top of the file:
import threading
And now we can't use sense.clear() any more in our firework_head, firework_tail and main while loop as this will wipe any fireworks that happen to be exploding whilst we set off another one. So we need to add some extra tidy routines to our script which just tidy up the firework that is set off in our thread.
def firework_head(i, j, r, g, b):
for x in range(4):
if(i-x >= 0): sense.set_pixel(i-x, j, 255, 0, 0)
if(j-x >= 0): sense.set_pixel(i, j-x, 255, 0, 0)
if(i+x <= 7): sense.set_pixel(i+x, j, 255, 0, 0)
if(j+x <= 7): sense.set_pixel(i, j+x, 255, 0, 0)
sleep(0.5)
tidy_head(i, j, x)
def firework_tail(i, j):
for x in range (7, j, -1):
sense.set_pixel(i, x, 255, 100, 0)
sleep(0.5)
tidy_tail(i, j)
def tidy_tail(i, j):
for x in range(7, j, -1):
sense.set_pixel(i, x, 0, 0, 0)
def tidy_head(i, j, x):
if(i-x >= 0): sense.set_pixel(i-x, j, 0, 0, 0)
if(j-x >= 0): sense.set_pixel(i, j-x, 0, 0, 0)
if(i+x <= 7): sense.set_pixel(i+x, j, 0, 0, 0)
if(j+x <= 7): sense.set_pixel(i, j+x, 0, 0, 0)
def firework():
i = randint(0, 7)
j = randint(0, 4) # Explode our fireworks in the sky, we'd hate to hit the crowd between 5-7.
r = randint(1, 255) # 0 is off so we don't want blank fireworks, lets start at 1.
g = randint(1, 255)
b = randint(1, 255)
firework_tail(i, j)
firework_head(i, j, r, g, b)
sleep(0.5)
while True:
thread = threading.Thread(target=firework)
thread.start()
sleep(2)
The last bit inside the while loop is the important bit. Now we have a thread which is told "make me a firework please", there's a pause and then another thread is started with another firework in it.
You should be able to see 3 or 4 fireworks sometimes going at once! Reducing the 2 second sleep at the end of the while True loop will make more fireworks appear in the sky!
Extras
We've got a full script over at our GitHub with some extra bits and pieces such as colour fade for the fireworks, speed controls for the display and additional library commands for running this on the Sense Hat Emulator.
I hope you all have a fabulous Bonfire Night, stay safe if you're going out to a big display, and let us know if you're building any indoor electronic fireworks displays like ours!