Chrimbus is a repository for controlling the grid of Christmas lights set up in my window. The lights are controlled by a Raspberry Pi using neopixels. The lights are mapped to a 2D grid based on their physical location in the window.
Write patterns. Run them on the Pi (or the visualizer). Enjoy blinking lights.
Assuming you do not have a Raspberry Pi set up with lights in the same configuration as mine, you can test patterns using the visualizer Python module (thanks to @joshua-lawrence). The visualizer module is programmed to display programs which are defined in pattern_definition.py. If you wish to test your pattern using the visualizer, ensure your pattern is imported and listed in the PATTERNS dict.
In order to control the lights, the neopixels library must be installed in the system Python of the Raspberry Pi. For this reason, this project supports only Python 3.7. Please ensure your submitted patterns are compatible with this version.
- ✅ OK:
- f-strings
dataclasses(builtin in 3.7)typingmodule (List,Dict, etc.)
- ❌ Avoid:
match/case(Python 3.10+)- Walrus operator
:=(Python 3.8+) - Builtin generics like
list[int],dict[str, int](Python 3.9+) typing.Annotated,typing.TypedDictas builtins syntax
Please do not include any patterns which are offensive or otherwise inappropriate as the lights are displayed publicly.
To submit a pattern, simply fork this repository and (optionally) open a pull request. Your pattern should live in the patterns/ directory.
There are many example patterns in the patterns/ directory. You should model your pattern after these examples. Namely, the pattern should use the @with_neopixel decorator to ensure compatibility with the visualizer.
The Chrimbus display has gone dark. The reindeer are grounded, the storm sprites are confused, and the confectionery production line is behind schedule for the first time in centuries. To prevent a global cheer shortage, you must complete a series of tasks inscribed on the Workshop Ledger.
c01_circle.py-- Nose So Brightc02_triangle.py-- Fir-tual Treec03_candy_cane_stripes.py-- Candy Cane Alignmentc04_star.py-- Celestial Recalibrationc05_falling_snow.py-- Silent Snowfallc06_radial_sweep.py-- Polar Radarc07_snow_wisps.py-- The Wandering Wind (Advanced)c08_spiral.py-- Spiral of the Evergreens (Advanced)
Greater mysteries lie beyond the eighth chapter, but they remain sealed for now. May your patterns be bright, your loops terminate, and your LEDs never segfault.
The North Pole Electrical and Computational Illumination Society respectfully requests that visiting wizards refrain from outsourcing their patterns to Languid Lingual Magic.
True Chrimbus spirit comes from mittened hands and warm print()s.
Using the @with_neopixels decorator allows you to set the color of each light (or pixel). The simplest example is white.py.
@with_neopixel
def white(pixels, time_limit=TIME_LIMIT):
start = time.time()
value = MAX_COLOR_VAL
while True:
pixels[:] = [(value, value, value)] * len(pixels)
pixels.show()
elapsed = time.time() - start
if elapsed > time_limit * 60:
breakTwo subtle details are of note.
- Though we set each pixel, we do not reassign the
pixelsvariable. Rather, we reassign the slice of thepixelslist. - The tuple
(value, value, value)is a tuple of three integers corresponding to (green, red, blue) values. The tuples are NOT RGB; they are GRB.
Setting individual pixels is also possible.
from with_neopixel import with_neopixel
@with_neopixel
def strobe(pixels, time_limit=TIME_LIMIT):
start = time.time()
pixels.fill(COLORS["green"])
while True:
for color in COLORS.values():
for i, _ in enumerate(pixels):
pixels[i] = color
time.sleep(0.03)
elapsed = time.time() - start
if elapsed > time_limit * 60:
breakThe LEDMatrix class is a convenience class for mapping the physical location of the lights to their index in the pixels list. The primary usage of this class involves its .mapping dictionary. This dictionary uses the key as the light index and the value as a tuple of the approximate
import colorsys
from led_matrix import LEDMatrix
@with_neopixel
def linear_gradient(pixels, time_limit=TIME_LIMIT):
start = time.time()
matrix = LEDMatrix(pixels=pixels)
while True:
for i, (x, _) in matrix.mapping.items():
hue = (x + time.time() * 0.5) % 1
saturation = 1
value = 1.0
rgb = colorsys.hsv_to_rgb(hue, saturation, value)
red, green, blue = [int(c * MAX_COLOR_VAL) for c in rgb]
pixels[i] = (green, red, blue)
pixels.show()
elapsed = time.time() - start
if elapsed > time_limit * 60:
break