6.5’ white tree with 250 WS2811 RGB LEDs, driven by an ESP8266 microcontroller using the FastLED library, controlled with a web app over WiFi and/or an infrared remote control. Currently at 35 procedural animations using a fully 3D mapped layout (X, Y, and Z axes, radius, and angle), allowing scrolling and rotating in any arbitrary direction.

More videos

Features

  • Control via a web app over Wi-Fi
  • Control via an infrared (wireless) remote control
  • On/Off
  • Adjustable brightness
  • Choose animation/pattern
  • Set to any solid color
  • Autoplay with adjustable duration

The whole project, including some tools you may not have, costs about $200. I already had a lot of the tools and parts on hand, and placed a large bulk order for the LEDs (which I’ll use on other projects). I only spent about $105.

Note: This is probably not a great project for people just getting started with Arduino and LEDs. I highly recommend beginners start with a much smaller, simpler project, such as those by Adafruit:

Table of Contents

Parts List

Parts

Price Name
$40 Holiday Time Unlit 6.5’ Jackson Spruce White Artificial Christmas Tree
$68 250 8mm WS2811 RGB LEDs with white wires
$7 Wemos D1 Mini
$2 44 Key IR Remote Control
$1 SN74HCT245N Level Shifter
$20 5V 15A Power Supply
$13 Adafruit Perma-Proto Half-sized Breadboard
$9 3 Pin JST SM Connectors
$19 SN-48B Crimping Tool
$5 4-Way 2.1mm DC Barrel Jack Splitter
$6 4 White 5.5mm x 2.1mm Female Plug
$2 IR Receiver Diode - TSOP38238
$2 1000µF Capacitor
$1 330 Ohm resistor
$5 250+ white 4” zip ties
$5 Lots of white wire
Total ~$200

Not all of these prices include shipping. I’m sure you can find cheaper prices for some of these items, and you may already have some of them.

Controller Board

Here’s the quick perma-proto I built for the Wemos D1 Mini, the SN74HCT245N level shifter, IR receiver, data line resistor, and capacitor. Power is fed to this board through the JST-SM connector that also contains the data line for the first LED string.

I strongly recommend you build and test this circuit on a breadboard first, make sure the ESP8266, LEDs, and IR all work, and then transfer the circuit to a perma-proto before soldering anything.

Here’s a download for the Fritzing Schematic & Diagram

The data out of the first string is connected to the data in of the next. I cut the wires and added JST-SM connectors and extension wires between each section of the tree for easy disassembly, and between each level of branches since the regular wire spacing isn’t long enough.

I’m injecting power at the start and end of the string, and then at two different equidistant points in the middle.

The 5.5 x 2.5mm connectors are not rated for current this high, so I never run at full brightness, solid white. I’ve limited the max power in the firmware, and I’m planning to replace them with XT60 connectors as soon as they come in.

Assembly

I assembled the tree, and then loosely attached the LEDs with twist ties while I worked out the way I wanted to run them. Once I had the positions finalized, I replaced the twist ties with 4” zip ties. I used the zip ties liberally, making sure to reduce wire strain wherever possible.

I ended up using 6 LEDs per branch. The first 6 rows/levels have 6 branches each.

Branch

Layout

These top view diagrams show the order and layout I ended up using, with the approximate distance from the center of the tree.

I started the LED string (LED 0) on the bottom row of branches near the center of the tree. I worked my way way along the branch outward, clockwise to the other side of the branch, and back inwards towards the center. LED 6 starts at the same position as LED 0, on the next branch clockwise.

Level 0:

Level 1:

Level 2:

Level 3:

Level 4:

Level 5. The branches on this level are shorter, so I just ran four LEDs per branch:

The top section of the tree is less organized, and I struggled to attach the LEDs at regular spacing.

Level 6:

Level 7:

Level 8:

Level 9:

Levels 10 & 11:

Levels 12 & 13:

I ran the remaining four LEDs up the left side of the vertical top branch, then back down the right side.

Levels 14 & 15:

Mapping

With the layout finalized, I went about the process of mapping this thing. While I could run animations treating the tree as a single long spiral strand of LEDs, I wanted to be able to scroll vertically, horizontally, radially (inwards and outwards), rotate clockwise, etc.

I used a Google Sheet to keep track of my measurements, and to map them to different numerical spaces. For example, I measured height and radius in centimeters, but then wanted to map them to single byte values (0-255) to save program space and allow easier animation.

Z Axis

To run patterns uniformly up and down the tree, I needed a map of the LEDs in the Z axis. Starting at the bottom branch as zero, I measured the height of each branch. I measured approximately 0, 6, 12, 19, 26, 32, 36, 39, 42, 44, 46, 48, 50, 53, 56 and 59cm. Since I already recorded how many LEDs are on each level, it’s an easy matter to record the Z position of every LED.

Angle

To animate patterns that rotate around the tree, I need the angle of every LED. I started with the first branch as 0 degrees. For simplicity, I treated every LED on the same branch as having the same angle, although technically they differ by a few degrees from one side to the other. I drew a diagram of each level, noting the start and end LED indices and positions. From this, I was able to record the angle of each LED in the sheet. I recorded them in degrees, converted them in the sheet to one byte range values, and to radians (needed for XY mapping). Level 0 branches were at 0, 60, 120, 180, 240, and 300 degrees. Converted to one byte range values, that’s approximately 0, 43, 85, 128, 171, and 213. Level 1 branches are offset by -30 degrees from level 0: 330, 30, 90, 150, 210, and 270 degrees. I repeated this for every level.

Radius

Knowing the radius of every LED will not only allow me to run animations inward and outward, but also allow me to calculate the X and Y position of the LEDs, using the previously recorded angle and a little trigonometry. I measured the radius of each ring of LEDs on each level. Each LED in the ring is fairly equal distance from the center, so I only measured one, or took the average if there was any significant difference. The radii are noted in the diagrams above. I recorded the radii in the sheet.

X and Y Axes

The X coordinate for an LED can be calculated with its radius (r) and angle in radians (a):

x = r * sin(a)

y = r * cos(a)

[[0.000,9.000,0],[0.000,18.000,0],[0.000,28.000,0],[0.000,28.000,0],[0.000,18.000,0],[0.000,9.000,0],[7.831,4.436,0],[15.662,8.872,0],[24.362,13.801,0],[24.362,13.801,0],[15.662,8.872,0],[7.831,4.436,0],[7.831,-4.436,0],[15.662,-8.872,0],[24.362,-13.801,0],[24.362,-13.801,0],[15.662,-8.872,0],[7.831,-4.436,0],[0.000,-9.000,0],[0.000,-18.000,0],[0.000,-28.000,0],[0.000,-28.000,0],[0.000,-18.000,0],[0.000,-9.000,0],[-7.831,-4.436,0],[-15.662,-8.872,0],[-24.362,-13.801,0],[-24.362,-13.801,0],[-15.662,-8.872,0],[-7.831,-4.436,0],[-7.831,4.436,0],[-15.662,8.872,0],[-24.362,13.801,0],[-24.362,13.801,0],[-15.662,8.872,0],[-7.831,4.436,0],[-3.943,6.961,17.04813559],[-8.379,14.791,17.04813559],[-13.308,23.492,17.04813559],[-13.308,23.492,17.04813559],[-8.379,14.791,17.04813559],[-3.943,6.961,17.04813559],[3.943,6.961,17.04813559],[8.379,14.791,17.04813559],[13.308,23.492,17.04813559],[13.308,23.492,17.04813559],[8.379,14.791,17.04813559],[3.943,6.961,17.04813559],[8.000,0.000,17.04813559],[17.000,0.000,17.04813559],[27.000,0.000,17.04813559],[27.000,0.000,17.04813559],[17.000,0.000,17.04813559],[8.000,0.000,17.04813559],[3.943,-6.961,17.04813559],[8.379,-14.791,17.04813559],[13.308,-23.492,17.04813559],[13.308,-23.492,17.04813559],[8.379,-14.791,17.04813559],[3.943,-6.961,17.04813559],[-3.943,-6.961,17.04813559],[-8.379,-14.791,17.04813559],[-13.308,-23.492,17.04813559],[-13.308,-23.492,17.04813559],[-8.379,-14.791,17.04813559],[-3.943,-6.961,17.04813559],[-8.000,0.000,17.04813559],[-17.000,0.000,17.04813559],[-27.000,0.000,17.04813559],[-27.000,0.000,17.04813559],[-17.000,0.000,17.04813559],[-8.000,0.000,17.04813559],[-3.943,6.961,34.09627119],[-8.379,14.791,34.09627119],[-13.308,23.492,34.09627119],[-13.308,23.492,34.09627119],[-8.379,14.791,34.09627119],[-3.943,6.961,34.09627119],[0.000,8.000,34.09627119],[0.000,17.000,34.09627119],[0.000,27.000,34.09627119],[0.000,27.000,34.09627119],[0.000,17.000,34.09627119],[0.000,8.000,34.09627119],[6.961,3.943,34.09627119],[14.791,8.379,34.09627119],[23.492,13.308,34.09627119],[23.492,13.308,34.09627119],[14.791,8.379,34.09627119],[6.961,3.943,34.09627119],[6.961,-3.943,34.09627119],[14.791,-8.379,34.09627119],[23.492,-13.308,34.09627119],[23.492,-13.308,34.09627119],[14.791,-8.379,34.09627119],[6.961,-3.943,34.09627119],[0.000,-8.000,34.09627119],[0.000,-17.000,34.09627119],[0.000,-27.000,34.09627119],[0.000,-27.000,34.09627119],[0.000,-17.000,34.09627119],[0.000,-8.000,34.09627119],[-6.961,-3.943,34.09627119],[-14.791,-8.379,34.09627119],[-23.492,-13.308,34.09627119],[-23.492,-13.308,34.09627119],[-14.791,-8.379,34.09627119],[-6.961,-3.943,34.09627119],[-7.000,0.000,53.98576271],[-16.000,0.000,53.98576271],[-25.000,0.000,53.98576271],[-25.000,0.000,53.98576271],[-16.000,0.000,53.98576271],[-7.000,0.000,53.98576271],[-3.450,6.091,53.98576271],[-7.886,13.921,53.98576271],[-12.322,21.752,53.98576271],[-12.322,21.752,53.98576271],[-7.886,13.921,53.98576271],[-3.450,6.091,53.98576271],[3.450,6.091,53.98576271],[7.886,13.921,53.98576271],[12.322,21.752,53.98576271],[12.322,21.752,53.98576271],[7.886,13.921,53.98576271],[3.450,6.091,53.98576271],[7.000,0.000,53.98576271],[16.000,0.000,53.98576271],[25.000,0.000,53.98576271],[25.000,0.000,53.98576271],[16.000,0.000,53.98576271],[7.000,0.000,53.98576271],[3.450,-6.091,53.98576271],[7.886,-13.921,53.98576271],[12.322,-21.752,53.98576271],[12.322,-21.752,53.98576271],[7.886,-13.921,53.98576271],[3.450,-6.091,53.98576271],[-3.450,-6.091,53.98576271],[-7.886,-13.921,53.98576271],[-12.322,-21.752,53.98576271],[-12.322,-21.752,53.98576271],[-7.886,-13.921,53.98576271],[-3.450,-6.091,53.98576271],[-6.091,-3.450,73.87525424],[-11.311,-6.408,73.87525424],[-16.532,-9.365,73.87525424],[-16.532,-9.365,73.87525424],[-11.311,-6.408,73.87525424],[-6.091,-3.450,73.87525424],[-6.091,3.450,73.87525424],[-11.311,6.408,73.87525424],[-16.532,9.365,73.87525424],[-16.532,9.365,73.87525424],[-11.311,6.408,73.87525424],[-6.091,3.450,73.87525424],[0.000,7.000,73.87525424],[0.000,13.000,73.87525424],[0.000,19.000,73.87525424],[0.000,19.000,73.87525424],[0.000,13.000,73.87525424],[0.000,7.000,73.87525424],[6.091,3.450,73.87525424],[11.311,6.408,73.87525424],[16.532,9.365,73.87525424],[16.532,9.365,73.87525424],[11.311,6.408,73.87525424],[6.091,3.450,73.87525424],[6.091,-3.450,73.87525424],[11.311,-6.408,73.87525424],[16.532,-9.365,73.87525424],[16.532,-9.365,73.87525424],[11.311,-6.408,73.87525424],[6.091,-3.450,73.87525424],[0.000,-7.000,73.87525424],[0.000,-13.000,73.87525424],[0.000,-19.000,73.87525424],[0.000,-19.000,73.87525424],[0.000,-13.000,73.87525424],[0.000,-7.000,73.87525424],[-2.957,-5.221,90.92338983],[-6.901,-12.181,90.92338983],[-6.901,-12.181,90.92338983],[-2.957,-5.221,90.92338983],[-6.000,0.000,90.92338983],[-14.000,0.000,90.92338983],[-14.000,0.000,90.92338983],[-6.000,0.000,90.92338983],[-2.957,5.221,90.92338983],[-6.901,12.181,90.92338983],[-6.901,12.181,90.92338983],[-2.957,5.221,90.92338983],[2.957,5.221,90.92338983],[6.901,12.181,90.92338983],[6.901,12.181,90.92338983],[2.957,5.221,90.92338983],[6.000,0.000,90.92338983],[14.000,0.000,90.92338983],[14.000,0.000,90.92338983],[6.000,0.000,90.92338983],[2.957,-5.221,90.92338983],[6.901,-12.181,90.92338983],[6.901,-12.181,90.92338983],[2.957,-5.221,90.92338983],[-9.192,-9.192,102.2888136],[-13.000,0.000,102.2888136],[-9.192,9.192,102.2888136],[0.000,13.000,102.2888136],[9.192,9.192,102.2888136],[13.000,0.000,102.2888136],[9.192,-9.192,102.2888136],[0.000,-13.000,102.2888136],[0.000,-11.000,110.8128814],[-8.672,-6.768,110.8128814],[-10.733,2.410,110.8128814],[-4.703,9.944,110.8128814],[4.703,9.944,110.8128814],[10.733,2.410,110.8128814],[8.672,-6.768,110.8128814],[0.000,-9.000,119.3369492],[-7.095,-5.537,119.3369492],[-8.781,1.972,119.3369492],[-3.848,8.136,119.3369492],[3.848,8.136,119.3369492],[8.781,1.972,119.3369492],[7.095,-5.537,119.3369492],[0.000,-7.000,125.019661],[-6.091,-3.450,125.019661],[-6.091,3.450,125.019661],[0.000,7.000,125.019661],[6.091,3.450,125.019661],[6.091,-3.450,125.019661],[0.000,-6.000,130.7023729],[-6.000,0.000,130.7023729],[0.000,6.000,130.7023729],[6.000,0.000,130.7023729],[4.243,-4.243,136.3850847],[-4.243,-4.243,136.3850847],[-4.243,4.243,136.3850847],[4.243,4.243,136.3850847],[5.221,2.957,142.0677966],[0.000,-6.000,142.0677966],[-5.221,2.957,142.0677966],[0.000,6.000,150.5918644],[5.221,-2.957,150.5918644],[-5.221,-2.957,150.5918644],[-1.000,0.000,159.1159322],[-1.000,0.000,167.64],[1.000,0.000,167.64],[1.000,0.000,159.1159322]]
        

Web app

Patterns are requested by the app from the ESP8266, so as new patterns are added, they’re automatically listed in the app.

The web app is stored in SPIFFS (on-board flash memory).

The web app is a single page app with separate files for js and css, using jQuery and Bootstrap. It has buttons for On/Off, a slider for brightness, a pattern selector, and a color picker (using jQuery MiniColors). Event handlers for the controls are wired up, so you don’t have to click a ‘Send’ button after making changes. The brightness slider and the color picker use a delayed event handler, to prevent from flooding the ESP8266 web server with too many requests too quickly.

Compiling

Follow the instructions on the following pages to install the required software for your OS (Windows, Linux, Mac):

Download and install the following libraries, using the instructions here: https://www.arduino.cc/en/Guide/Libraries

Download the source code here: https://github.com/evilgeniuslabs/tree-v2

In Arduino, choose the following options from the Tools menu:

  • Board: WeMos D1 R2 & mini
  • Flash Size: 4M (3M SPIFFS)
  • CPU Frequency: 160MHz
  • Upload Speed: 921600

Select the correct port for your board (it’s easiest to unplug any other USB serial devices).

Finally, click the Upload button. Please report any errors, problems, questions, etc to the Issue Tracker.

SPIFFS (SPI Flash File System)

The web app needs to be uploaded to the ESP8266’s SPIFFS. You can do this within the Arduino IDE after installing the Arduino ESP8266 filesystem uploader.

With ESP8266FS installed run the sketch and then upload the web app using the ESP8266 Sketch Data Upload command in the Arduino Tools menu.

Compression

The web app files can be gzip compressed before uploading to SPIFFS by running recompress.sh or the following command:

gzip -r data/

The ESP8266WebServer will automatically serve any .gz file. The file index.htm.gz will get served as index.htm, with the content-encoding header set to gzip, so the browser knows to decompress it. The ESP8266WebServer doesn’t seem to like the Glyphicon fonts gzipped, though, so I decompress them with this command:

gunzip -r data/fonts/

REST Web services

The firmware implements basic RESTful web services using the ESP8266WebServer library. Current values are requested with HTTP GETs, and values are set with POSTs using query string parameters. It can run in connected or standalone access point modes.

Support

Please report any errors, problems, questions, etc to the Issue Tracker.