Be Still, My Beating Heart

title

It's that festival of red and pink and hearts and all that gubbins. Part craft and part coding, this Valentine's project might get you into a geek's heart. Failing that, you've got a nice pulsing light.

Here's what it will look like:

beating heart

Making the Pibow top

First off, a Unicorn HAT HD was hidden inside a custom pibow. You can customise your own pibow top into a glittery diffuser with the helpful aid of some silicone bathroom sealant and sequins or gems. To make this one, we took a Pibow top and smeared an even layer of silicone sealant over it.

silicone sealant

A box of gems was sorted by colour and size, and the gems were pressed into the sealant in a pattern. You need to leave the sealant to set for 24 hours, but after that it is reasonably hard-wearing.

shiny top

Foolishly, I used white sealant, which works well as a diffuser, but I'd use clear next time to allow a little more light to shine through.

making the top

Other ideas - push broken/old components into the sealant, or nuts and bolts for a more rugged version.

Coding the heart

Rather than just having a pulsing light behind the top of the Pibow, I wanted to have an actual heart shape. Handily, Sandy made a heartbeat example for the non-HD Unicorn HAT, so I modified his code to apply to the Unicorn HAT HD, and also simplified the code.

designing the heart

The Unicorn HAT HD is 16 x 16 pixels, so I drew a heart on squared paper that would be translated into a list. To do this, everywhere you see a heart pixel, you type a 1, and everywhere it should be dark/background, type a 0.

heart = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
         [0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0],
         [0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0],
         [0,1,1,1,1,1,1,0,0,1,1,1,1,1,1,0],
         [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
         [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
         [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
         [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
         [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
         [0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0],
         [0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0],
         [0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0],
         [0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0],
         [0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]

NumPy is a library of useful mathematical functions in Python, and this is what we use to handle our little heart picture.

heart = numpy.array(heart)

Making the heart flash

This is not quite as easy as it should be; making something in RGB colours flash is harder than making something in HSV colours flash.

while True:
   for i in 2*(range(1,11)[::-1]+range(1,10)):

The maths to make the flashing pattern is easiest to show in a picture:

range maths

To make the ba-BUMP of a heartbeat, you need to dial the brightness up to full and back again, twice. The easiest bit to explain is the times two at the start - two bumps. The value for the brightness is 1 divided by whatever number is next in the list, so it starts off being divided by 10, and goes through to full brightness, and then back to 1/10th when the pattern repeats.

By far the largest chunk of code is changing the HSV into RGB so we can send the information to the Unicorn HAT HD to actually show the heart! Hopefully the comments amongst the code explain this.

A full copy of the code with or without comments is at the bottom of this tutorial.

Now you can display a beating heart, maybe you could change the shape, or colour, or even make it so that the heart beats when you send the Pi a certain message (Tweepy is a good place to start)?

heart working

Happy Valentine's Day!

The code

import unicornhathhd as unicorn
import time, colorsys
import numpy

unicorn.brightness(1)
unicorn.rotation(90)

heart = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
         [0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0],
         [0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0],
         [0,1,1,1,1,1,1,0,0,1,1,1,1,1,1,0],
         [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
         [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
         [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
         [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
         [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
         [0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0],
         [0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0],
         [0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0],
         [0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0],
         [0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]  

heart = numpy.array(heart)

while True:
  for i in 2*(range(1,11)[::-1]+range(1,10)):
    for y in range(16):
      for x in range(16):
        h = 0.0
        s = 1.0
        v = heart[x,y] / float(i)
        rgb = colorsys.hsv_to_rgb(h, s, v)
        r = int(rgb[0]*255.0)
        g = int(rgb[1]*255.0)
        b = int(rgb[2]*255.0)
        unicorn.set_pixel(x, y, r, g, b)
    unicorn.show()
    time.sleep(0.005)
  time.sleep(0.8)

The commented code

import unicornhathhd as unicorn
import time, colorsys
import numpy

unicorn.brightness(1)
# need to rotate the image to have the heart the right way up
unicorn.rotation(90)

heart = [[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
         [0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0],
         [0,0,1,1,1,1,0,0,0,0,1,1,1,1,0,0],
         [0,1,1,1,1,1,1,0,0,1,1,1,1,1,1,0],
         [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
         [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
         [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
         [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
         [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0],
         [0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0],
         [0,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0],
         [0,0,0,0,1,1,1,1,1,1,1,1,0,0,0,0],
         [0,0,0,0,0,1,1,1,1,1,1,0,0,0,0,0],
         [0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0],
         [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]]  

heart = numpy.array(heart)

while True:
# go through the range 1-10 backwards then back up
# the 2* makes a ba-BUMP for the heart
  for i in 2*(range(1,11)[::-1]+range(1,10)):
# the x and y ranges are the size of the Unicorn Hat HD - 16 x 16 pixels
    for y in range(16):
      for x in range(16):
        h = 0.0 # red
        s = 1.0 # saturation at the top of the red scale
        v = heart[x,y] / float(i) # the brightness of the heart depeds where it is in the range
        rgb = colorsys.hsv_to_rgb(h, s, v) # convert the hsv back to RGB
        r = int(rgb[0]*255.0) # makes a 0-1 range into a 0-255 range and rounds it to a whole number
        g = int(rgb[1]*255.0)
        b = int(rgb[2]*255.0)
        unicorn.set_pixel(x, y, r, g, b) # sets the pixels on the unicorn hat
    unicorn.show() # show the pixels
    time.sleep(0.005) # tiny gap, sets the frames of the heart animation to 200 a second so it looks smooth
  time.sleep(0.8) # waiting time between heartbeats
That's all folks!

Search above to find more great tutorials and guides.

Plasma 2040

Swathe everything in rainbows with this all-in-one, USB-C powered controller for WS2812/Neopixel and APA102/Dotstar addressable LED strip.