Introduction
We have a 1960's era aluminum Christmas tree. For the time that we've had it we've relied on newer and slightly less dangerous color wheels, then stage lighting. When we moved we didn't bring any of the lighting with us.
I decided to string the poles of the tree with LEDs.
The prior year I made a star topper which used a Bluefruit for lighting. This year I added 24 LEDs for the star that plug into the highest string on the poles.
Design
The current design is written in CircuitPython. For communication it relies on MQTT and Node-Red.
Everything worked great, however, after I worked on a different LED project that used WLED, I think I'll convert this project to WLED. I'm not going to delete this code base, I think it has some interesting implementation features and is worth preserving.
Infrastructure-wise with either implmentation you will need:
- MQTT - you can use AdafruitIO or build your own. In my implementation I set up a broker on a RaspberryPi 4 Model B running Debian Bookworm and Mosquitto.
- Node-Red - my Node-Red journey is chronicled here.
Parts
Code
Tree & Star
The code can be found on Github
As I was coding this project, I found myself thinking I was going to be reusing a lot of it. So I repurposed an old project and replaced what was there with new helper classes.
I did run into an issue where I could not get the LED Animation library to play nicely with MQTT. I tried a few things, including asyncio, and ended up posting on the forums. mikeysklar came to the rescue and you'll see his contribution in the code.
The tree has 196 LEDs and the star has 24.
data = {
'tree_animations': ['multi_chase']
,'star_animations': ['rainbow_sparkle']
,'num_pixels' : 220
,'tree_pixel_subset': [0, 195]
,'star_pixel_subset': [196, 24]
...
}
Libraries Needed
import json
import board
import time
import os
import neopixel
import adafruit_logging
import wifi
import supervisor
import adafruit_connection_manager
import adafruit_minimqtt.adafruit_minimqtt
from adafruit_led_animation.group import AnimationGroup
from adafruit_minimqtt.adafruit_minimqtt import MMQTTException
from adafruit_led_animation.sequence import AnimationSequence
from adafruit_led_animation.helper import PixelSubset
from circuitpy_helpers.led_animations import animationBuilder
from circuitpy_helpers.led_animations import controlLights
from circuitpy_helpers.led_animations import updateAnimationData
from circuitpy_helpers.file_helpers import updateFiles
from circuitpy_helpers.calendar_time_helpers import timeHelper
from circuitpy_helpers.network_helpers import wanChecker
Featured Highlights
The tree subscribes to the following MQTT feeds:
- tree.lights - listens for events from Node-Red to update animation, start time, stop time
- home.time - listens for events from home hub in order to know when to stop or start the lights
- home.sunset - listens for events from home hub in order to know current sunset, uses this for start time
In order to take events from tree.lights and actually change the running animation and colors, I needed to be able to do a couple of things:
- Have a list of supported animations - this is done via the animations.json file
- Be able to override the default values for animations
- Be able to on-the-fly update the data file to implement the new animation/colors choice
When the code first starts it will check if there are overrides for the chosen animation and do an in-line replacement of those values. It will then check to see what the color selection is. Color choice is specified in the animations.json file and can be:
- random - a random color will be selected
- data - in the data file, for each animation is an entry for color. A string representation of the color can be provided or a the name of a color_palette.
Once up and running, about every 30 seconds, the animations will pause long enough for the code to query MQTT for any updates. If a new message arrives the code will update the data file with the appropriate information and perform a supervisor.reload().
You can also change the start and stop times or disable them so that the lights will run continually.
When using start and stop times; when the criteria is met to do either, the code will light sleep until the alarm is met.
Case
I used OpenSCAD and the YAPP Box (Yet Another Parametric Project Box Generator) library from mrWheel for the Feather case. This library is well documented and has a lot of features.
I found a star on Thingiverse years ago and downloaded the SCAD file. I re-mixed it for my own needs. Unfortunatly now I cannot find the Thing anymore. If you recognize the code as yours please let me know so I can give you proper credit.
Node-Red
The Node-Red Dashboard allows anyone to interact with the CircuitPython code and control the tree lights.
Each of the button groups, when selected, will send a JSON string along to the function. It looks like this:
{
"color_search": "custom_sparkle_color",
"new_color": "gold",
"animation_name": "custom_sparkle"
}
From here it will call two functions, the first is to update the color choice and that code looks like this:
var search_string = msg.payload.color_search;
var new_color = msg.payload.new_color;
var newMsg = {
"topic": "update_color"
,"payload": {
"filename": "data.py"
,"search_string": search_string
,"new_value": `'${new_color}'`
}
};
return newMsg;
It will then delay for 10 seconds to allow the update to complete and then update the animation. That code looks like this:
var new_animation = msg.payload.animation_name;
var animationChg = {
"topic": "update animation"
,"payload": {
"filename": "data.py"
,"search_string": "tree_animations"
,"new_value": `'${new_animation}'`
}
}
return animationChg;
The CircuitPython code receives these updates via MQTT. The code will not issue a supervisor.reload() on the color update, only after the animation has been updated.
I opted to implement it this way because it makes it easier to add new animation selections.
The Star is its own segment of 24 lights. Therefore, it has its own Node-Red page with the same options the tree has.
Project Construction
Tree
Because this tree has holes for the branches and is two separate pieces, I couldn't just wrap the tree with one uninterrupted strand of LEDs.
My solution was to cut the LED strip into segments of 7 LEDs and connect them using the clear connectors and stranded wire. This way I could get the lengths I need to wrap the tree without obstructing any of the holes. Also, where the poles split I used the 3 pin male to female connectors to allow unplugging one pole from the other. I used the 3 pin male to female connector to attach the star to the LEDs on the pole.
In order to keep the whole string from sagging, I used Uglu dashes to hold the connectors in place.
At the end of the strand, near the base of the tree, you'll need to attach a jack connector for the power supply, and another male/female connector to connect to the Feather.
I soldered 3 individual headers - power, earth, and data - to the board. Then I soldered a male/female connector to female breadboard jumpers to make detaching easier.
Star
For the star I used 4 strips of 6 LEDs and connected them using the clear connectors and stranded wire. One strip is connected to the male/female connector to connect to the tree.
The star itself is two halves and is held together, around the top most branch with magnets. A traditional topper would likely damage the branch.
Finished Product
This page (Christmas Tree & Star Lights with Node-Red) was last updated on January 08, 2026.
Text editor powered by tinymce.