A hat covered in 3d-mapped NeoPixels

I glued 100 WS2812 NeoPixels to a hat then mapped them in 3D space


You will need:

I hot-glued the lights to the hat, turning in a random(ish) direction after I'd glued-down each one, attempting to move towards empty space each time. I also managed to stick 2 or 3 of them on backwards because I'm an idiot.

I cable-tied the Pi to the power-bank and wired everything up, producing something you absolutely would not want to try to take through an airport:

not a bomb

The data line is connected to pin 21 (physical pin 40).

Installing the software

From a box-fresh install of Raspberry Pi Bullseye:

Set hostname and enable serial port and i2c

sudo raspi-config nonint do_hostname hatlights
sudo raspi-config nonint do_serial 1
sudo raspi-config nonint do_i2c 0
sudo reboot

Install git

sudo apt -y update && sudo apt install -y git

Get this repo

git clone

Install everything

cd hatlights

Mapping the lights

Once everything is assembled, you need to take lots of photos, and do a load of analysis.


The five buttons are mapped in conf/buttons.yaml:

button press to cycle hold to cycle
A modes
B colours colour-sets
C colour-sources
D axes invert
E display modes


A mode needs to inherit from Mode, and should expose a run method, which loops forever and does something with the lights, and a reconfigure method, which resets the hat to the mode's preferences.

The ordering of the modes is defined in this list, and the modes' preferences are defined in conf/modes.yaml.

Some of the modes involve some pretty intense maths which, if done in real-time, will slow everything down to a crawl. In these cases, it's sensible to pre-render the set of states. A mode named foo will attempt to deserialise a file at renders/foo.pickle into an instance attribute called frame_sets.


Colour sets are defined in conf/colour-sets.yaml - pressing the colours button will step through the colours in a set, while holding the same button will step through the sets themselves. It's up to a mode if it actually makes use of these settings.

Colour sources

There are three kinds of colour-source:

  • Redis: this (poorly-named) source means "use whatever colour is currently defined at hat:colour in Redis". This is the colour that gets set by the colour button
  • Wheel: the ColourWheel is running as a daemon, and populating the hat:hue key in Redis with a constantly-rotating value between 0 and 1, using a step-size and interval defined in conf/conf.yaml. Calling get("colour") on the Custodian (which is mostly a wrapper around Redis) will, if the colour-source is wheel, return an RGB triple based on that hue value
  • Random: uses a random hue between 0 and 1 to generate an RGB triple


If you're looking at the hat square-on from the front, then

  • x goes from -1 on the left to +1 on the right
  • y goes from 0 at the bottom to +1 at the top (this makes sense if you think about the hat as the top half of a sphere)
  • z goes from -1 at the back to +1 at the front

For most of the modes, it makes sense for them to orient themselves with respect to an axis, and pressing the axis button will cycle through x, y and z. Holding this button will invert the direction of movement along or around that axis.

Display modes

Pressing the display modes button cycles through the three modes for the OLED screen:

  • Hat settings: shows the hat mode, and if relevant, the axis/inversion state and the colour and colour-set
  • Button config: shows the abbreviation fields from the button-mappings. They don't quite line up, but it's good enough
  • IP address: shows the hat's current IP address
Hatlights on Github