After having the scales, thermometer and a coffeemaker connected to the internet, it is finally time for Christmas Tree to become smarter.
Hardware
- 6ft Christmas Tree
- An addressable WS2811 LED strip (3x50 was good enough for this tree size)
- Raspberry Pi Zero W
- Voltage level shifter (3.3V to 5V)
- Power Supply
Software
- used rpi_ws281x Python samples as a base library for controlling the LEDs
- Flask app served at a specific port which is forwarded to the internet (to control via IFTTT)
webcolors
library to map between color names and RGB values
Currently, I've only implemented a few actions/patterns:
wipe with a specific color (given as a color name)
"rainbow" pattern
"crazy" pattern (also called "theater chase" in the
rpi_ws281x
samples)"stop" - turn off - all LEDs black
The Code
Flask part
from flask import Flask
from libs import LEDStrip
app = Flask(__name__)
led_strip = LEDStrip(count=150)
@app.route('/wipe/<color>')
def wipe_color(color):
led_strip.color_wipe(color)
return "Wipe with {color} - success!".format(color=color)
@app.route('/rainbow')
def rainbow():
led_strip.rainbow()
led_strip.rainbow_cycle()
return "Rainbow animation executed successfully!"
@app.route('/crazy')
def crazy():
led_strip.theater_chase_rainbow()
return "Crazy christmas tree animation executed successfully!"
@app.route('/stop')
def stop():
led_strip.clear()
return "Christmas tree was turned off!"
libs.py
import time
from webcolors import name_to_rgb
from neopixel import *
from exceptions import ColorNotFoundException
def wheel(pos):
"""Generate rainbow colors across 0-255 positions."""
if pos < 85:
return Color(pos * 3, 255 - pos * 3, 0)
elif pos < 170:
pos -= 85
return Color(255 - pos * 3, 0, pos * 3)
else:
pos -= 170
return Color(0, pos * 3, 255 - pos * 3)
class LEDStrip:
def __init__(self, count,
pin=18, frequency=800000,
dma=5, brightness=255,
invert=False, channel=0,
strip_type=ws.WS2811_STRIP_RGB):
"""
LED strip abstraction class.
:param count: number of LED pixels
:param pin: GPIO pin connected to the pixels (18 uses PWM!)
:param frequency: LED signal frequency in hertz (usually 800khz)
:param dma: DMA channel to use for generating signal
:param brightness: set to 0 for darkest and 255 for brightest
:param invert: True to invert the signal (when using NPN transistor level shift)
:param channel: set to '1' for GPIOs 13, 19, 41, 45 or 53
:param strip_type: strip type and colour ordering
"""
self.led_count = count
self.strip = Adafruit_NeoPixel(count, pin, frequency, dma, invert, brightness, channel, strip_type)
self.strip.begin()
def clear(self):
self.color_wipe("black")
def color_wipe(self, color, wait_ms=30):
"""Wipe color across display a pixel at a time."""
try:
color_value = Color(*name_to_rgb(color))
except ValueError:
raise ColorNotFoundException("Color {color} not found.".format(color=color))
for led_number in range(self.led_count):
self.strip.setPixelColor(led_number, color_value)
self.strip.show()
time.sleep(wait_ms / 1000.0)
def theater_chase(self, color, wait_ms=50, iterations=10):
"""Movie theater light style chaser animation."""
for j in range(iterations):
for q in range(3):
for i in range(0, self.led_count, 3):
self.strip.setPixelColor(i + q, color)
self.strip.show()
time.sleep(wait_ms / 1000.0)
for led_number in range(0, self.led_count, 3):
self.strip.setPixelColor(led_number + q, 0)
def rainbow(self, wait_ms=20, iterations=1):
"""Draw rainbow that fades across all pixels at once."""
for j in range(256 * iterations):
for led_number in range(self.led_count):
self.strip.setPixelColor(led_number, wheel((led_number + j) & 255))
self.strip.show()
time.sleep(wait_ms / 1000.0)
def rainbow_cycle(self, wait_ms=20, iterations=5):
"""Draw rainbow that uniformly distributes itself across all pixels."""
for j in range(256 * iterations):
for led_number in range(self.led_count):
self.strip.setPixelColor(led_number, wheel((int(led_number * 256 / self.led_count) + j) & 255))
self.strip.show()
time.sleep(wait_ms / 1000.0)
def theater_chase_rainbow(self, wait_ms=50):
"""Rainbow movie theater light style chaser animation."""
for j in range(256):
for q in range(3):
for led_number in range(0, self.led_count, 3):
self.strip.setPixelColor(led_number + q, wheel((led_number + j) % 255))
self.strip.show()
time.sleep(wait_ms / 1000.0)
for led_number in range(0, self.led_count, 3):
self.strip.setPixelColor(led_number + q, 0)
exceptions.py
class ChristmasError(BaseException):
pass
class ColorNotFoundException(ChristmasError):
pass
Aside from Google Home & Alexa voice control abilities like "Okay Google - Turn the Christmas Tree green" and "Okay Google - Make a rainbow", it is a lot of fun connecting that with motion sensors and other smart devices.
What do you think about the overall design of the LEDStrip
class and the flask app? What do you think I can improve? I would also appreciate any ideas about the ways I can continue making it more fun this Christmas.
Note that there is already a plenty of things to address security-wise.
ColourNotFoundException
needs to callChristmas Error
. Can it not be just set up asclass ColourNotFoundException(BaseException): pass
. P.S Sorry if the question is stupid, I didn't even know you could do that. \$\endgroup\$ColourNotFoundException = Exception("")
\$\endgroup\$