Introduction: Illuminated Christmas Tree Ornament [WiFi Controlled]

In this Instructable i'll describe how to build a illuminated Christmas tree ornament, which can be controlled over WiFi. Use your favorite browser (computer or smartphone) to connect to your Christmas tree ornament. And select the colors, speed and pattern.

The Christmas tree ornament has it's own web server. And all code runs on a build-in Wemos/ESP8266 board. Everything else it requires is a 5 Volt (USB) power source and a WiFi connection.

This Instructable contains diverse steps. It starts with three code examples. The first example is a simple Arduino sketch, in Autodesk Circuits, using a NeoPixel ring. This example is the base of this build. Next step is a simple webserver using a Wemos board. The third code example explains how to execute functions at specific time intervals.
After these coding examples, it's time to create the design. This is based on a regular icosahedron. This is a completely symmetrical geometry with 20 sides. The design was created in Fusion 360, and then printed using a 3D printer.
After the assembly follows the final code. This is a combination of the three examples from the beginning of the Instructable.

This Instructable is about creating a Christmas Tree ornament. But the web interface is not limited to Christmas ornaments. It can be used for many other projects. In fact, Anything that can be controlled by an Arduino can be controlled over WiFi.

Step 1: Required Materials

Required materials:

  • Wemos D1 Mini Pro ($5) or Wemos D1 Mini ($3)
  • WS2812b LED strip 30/meter IP30 ($3 for 1 meter with 30 Leds)
  • Micro USB cable (local store, about $2)
  • Breadboard wires
  • Transparent glue (hobby store)
  • Glitter dust powder (hobby store)
  • USB power supply (e.g. Ikea Koppla) or other 5 Volt power supply.

Total material costs are about $10 for a single Christmas ornament.

Use a decent USB power supply. The leds use maximum 60 milliAmpere each. 20 LEDs at full power require 1.2 Ampere (6 Watt). I'm using an Ikea Koppla USB power supply. It has 3 ports and can deliver 3.2 Ampere at 5 Volt.

Step 2: Autodesk Circuit: NeoPixel Ring Example

Building something with an Arduino and WS2812 LEDs is really simple. It might look intimidating, if you've never worked with an Arduino before. Some programming and/or electronics experience makes it more easy. But it isn't really that hard.

And it's not necessary to purchase an Arduino if you want to try this out. There are websites where you can simulate an Arduino. One of them is Autodesk Circuits. I've made an Arduino example using a NeoPixel ring. This pixel ring example, is the base of this Christmas ornament.

The code looks simple. But it shows many of the possibilities of coding an Arduino:

It uses an external library (Adafruit NeoPixel). So I don't have to worry about changing the LEDs colors. All I have to do is use the library functions.

The code defines the RGB values of 12 colors in 3 arrays. This are the 12 colors, used by the web interface for controlling the led strip.

There is also a self-defined function. The setColor function can be called from anywhere within the program.

#include <Adafruit_NeoPixel.h>

#define NUM_PIXELS 16
int D2 = 8; 
Adafruit_NeoPixel pixels(NUM_PIXELS, D2, NEO_GRB | NEO_KHZ800);

const int numColors = 12;
int R[numColors] = {255,255,227,255,255,128,075,000,127,000,127,255};
int G[numColors] = {194,165,066,000,000,000,000,000,255,255,255,255};
int B[numColors] = {000,000,052,000,255,128,130,255,212,000,000,000};

void setColor(int R, int G, int B) 
{
   for (int i = 0; i < NUM_PIXELS; i++) 
   {
      pixels.setPixelColor (i, R, G, B);
   }
   pixels.show();
}

void setup() 
{
   pixels.begin();
   pixels.show();
}

void loop() 
{
   for (int j = 0; j < numColors; j++) 
   {
      setColor (R[j], G[j], B[j]);
      delay (1000);
   }
}

This code contains one array with 12 colors (numbered from 0 to 11). I've chosen 12 colors, because the final code contains one button for each color:

color 0: amber (FFC200)
color 1: orange (FFA500)
color 2: vermillion (E34234)
color 3: red (FF0000)
color 4: magenta (FF00FF)
color 5: purple (800080)
color 6: indigo (4B0082)
color 7: blue (0000FF)
color 8: aquamarine (7FFFD4)
color 9: green (00FF00)
color 10: chartreuse (7FFF00)
color 11: yellow (FFFF00)

You can change the colors if you like, by altering the RGB values. More colors codes can be found at Wikipedia.

_______________________________________

For anyone who has never worked with an Arduino, I can recommend the Instructable Arduino Class:

"This class will introduce you to the Arduino world. You'll learn the basics, build your first project, and so much more. Each lesson builds on your skills, infusing new knowledge and techniques along the way.

And most of these classes can be made with Autodesk Circuits.

Step 3: Hello World

After programming an arduino with ws2812 LEDs, it's time to create a simple web server. This requires a Wemos (with an ESP8266) board, containing a WiFi adapter. The Wemos can be connected to your Computer using an USB cable. There is no need for additional USB to serial adapters. This is the advantage of a Wemos above an ESP8266-12.

The Wemos can be programmed using the Arduino software. But it requires adding additional boards with the Boards Manager. This is described in the Wemos documentation.

After these steps it's possible to select a Wemos board in the Arduino programming environment. Select the Wemos board (+ corresponding com-port) and copy the following code:

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>

MDNSResponder mdns;

// network credentials
const char* ssid     = "ssid";
const char* password = "password";

String webPage  = "<h1>Hello World</h1>";                                                                                                                   

ESP8266WebServer server(80);

void setup() 
{
   Serial.begin(115200);

   delay(500); 
   WiFi.begin(ssid, password);

   while (WiFi.status() != WL_CONNECTED) 
   {
      delay(500);
      Serial.println(".");
   }
   Serial.print("IP address: ");
   Serial.println(WiFi.localIP());

   server.on ("/", [](){server.send(200, "text/html", webPage);});
   server.begin();
   Serial.println("HTTP server started");
}

void loop() 
{
   server.handleClient();
}

Alter the network credentials before compiling and uploading the code.

This is a very simple web server. The Wemos will connect to the WiFi network and start a web server with only one page. Use the serial monitor to obtain the IP address of your webserver.

Step 4: Wiring

This instructable requires some soldering. But this is minimized by using a WS2812b led strip.

Solder the male headers on the Wemos board. Only the D2, +5 Volt and ground pins are used. This Means that the headers only have to be soldered on one side of the board.

Solder 3 breadboard wires on the led strip (Ground, signal and +5 Volt).

Remove the plastic from the USB connector. There is no room for such a connector. Add 2 wires to the usb cable: one to the +5 and one to the ground wire. These are used for directly powering the Leds. Don't forget to insulate these wires.

Connect the +5 Volt wires from the USB cable and led strip. The same for the 2 round wires. Connect the signal wire, from the Led strip, onto the D2 pin on the Wemos board. Finally connect the usb connector to the Wemos board.

There is no ground wire connected to the header on the Wemos board. This ground pin is directly connected to the usb connector. Which is connected to the additional ground wire.

Step 5: Timing Example

In my first Arduino neopixel code example (NeoPixel ring), color changes are made within the main loop. This requires a delay in the main loop, or the change of color will go too fast. During this delay the Wemos board just waits, and doesn't execute any other code. Except for background processes, e.g. handle the WiFi network connection.
The final product will run a webserver to give control to the LEDs. Because of this there should be no waits inside the code, because this will give a non-responsive web-interface.

In this example the LEDs are controlled by an internal timer. This osTimer is defined by the os_timer_setfn function, and then armed with the os_timer_arm function. The used value of 1000 is in milliseconds. By using this value, the Wemos timer will execute the timerCallback procedure every second. This procedure increases the color value, and changes the colors of the Leds. And all these actions are done outside the main loop.

Beware that the code inside the osTimer must be very short. The code must be completed before the next timer is fired.

#include <Adafruit_NeoPixel.h>
#include <user_interface.h>

#define NUM_PIXELS 20
Adafruit_NeoPixel pixels(NUM_PIXELS, D2, NEO_GRB | NEO_KHZ800);

os_timer_t osTimer;

const int numColors = 12;
int R[numColors] = {255,255,227,255,255,128,075,000,127,000,127,255};
int G[numColors] = {194,165,066,000,000,000,000,000,255,255,255,255};
int B[numColors] = {000,000,052,000,255,128,130,255,212,000,000,000};
int vColor = 0; // start with color 0 

void setColor(int R, int G, int B) 
{
   for (int i = 0; i < NUM_PIXELS; i++) 
   {
      pixels.setPixelColor (i, R, G, B);
   }
   pixels.show();
}

void timerCallback(void *pArg) // interrupt os-timer<br>{  
   vColor++; if (vColor>=numColors){vColor=0;}
   setColor (R[vColor], G[vColor], B[vColor]);
} 

void setup() 
{
   pixels.begin();
   pixels.setBrightness (16); // reduce power usage
   pixels.show();

   os_timer_setfn (&osTimer, timerCallback, NULL);
   os_timer_arm   (&osTimer, 1000, true);
}

void loop() 
{
   delay (1000); // nothing to do
}

This code also introduces a function called setColor. This function accepts 3 values, these are used to change the color of all the the LEDs.

Step 6: Regular Icosahedron

There are Christmas tree ornaments in many different shapes. While I was looking for a design, i came across some geometric forms. And one type of geometry caught my attention: A regular polyhedron. They are completely symmetrical. This makes it perfect for a Christmas ornament. There are only five known types:

  1. Triangular pyramid (4 sides)
  2. Cube (6 sides)
  3. Octahedron (8 sides)
  4. Dodecahedron (12 sides)
  5. Icosahedron (20 sides)

I've choosen for the icosahedron. It has the highest number of sides. There will be one WS2812 LED on each side, with 20 LEDs in total.

The use of WS2812 LED strips limits the size of the geometry. The distance between the LEDs is 1.3 inch (30 LEDs per meter). This equals the upper limit for the sides of each equilateral triangle. After creating a paper prototype, I came up with a size of about 3 inch (75 mm). This gives just enough room for the Wemos controller and 20 LEDs.

Step 7: Autodesk Fusion 360

Drawing a regular icosahedron starts with 3 rectangles on each axis. These must be golden rectangles. A golden rectangle is a rectangle whose side lengths are in the golden ratio (approximately 1.618). We can calculate the sides for a golden rectangle with a diagonal of 75 mm (3 inch). Making use of the Pythagoras's theorem, gives 65 x 40 millimeter for the sides.

Every single corner of the rectangles is a corner of 5 triangles.

Next step is to create 20 triangles (using a plane through 3 points). And extrude them to the inside. Create a new fourth body with these 20 triangles. Each triangle is adjusted, so that the LEDs can be placed behind.

All Fusion 360 files are available for download on my autodesk drive: Icosahedron, Part 1 and Part 2.

Step 8: Printing Parts

I've printed the design 3 times. Two times with the original Up filament (white), and one time with non regular filament (blue).

Remove all support material. And make sure the LEDs fit in the holes. Use a sharp knife and sand paper to remove all irregularities. It is intended to cover the entire Christmas ornament with glitter. Therefore any irregularities will be visible in the end product.

Step 9: Assembly

Before gluing the LEDS, draw a single line for the path of the LED strip. This line may not cross itself.

Start with gluing the LEDs, inside the large 3D printed part. Begin with the final LED at the end of the strip. I've used a hot melt glue gun. Carefully bend the LED strip while processing.

I used two LED strips in my first version. One with 5 LEDs and one with 15 LEDs. But it is quite possible to use a single LED strip of 20 LEDs. This saves some soldering.

Connect the +5 and ground wires from the LED strip to the USB cable. The signal cable is connected to the D2 pin on the Wemos board. The ground is internally connected. Don't forget to check the LEDs before closing the Christmas ornament.

Put some glue on the connectors, so they don't get loose. Place the Wemos board inside the Large 3D printed part. Make some room for the USB cable throughput, and glue everything together.

Step 10: Webserver

This Arduino sketch file contains all code for a web server with a Wemos board. Alter the "ssid" and the "password" variables before uploading the code.

About the code

Some parts of the code require some explanation:

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <Adafruit_NeoPixel.h>
#include <user_interface.h>

All libraries used in this Arduino sketch.

#define NUM_PIXELS 20
Adafruit_NeoPixel pixels(NUM_PIXELS, D2, NEO_GRB | NEO_KHZ800);

There are 20 LEDs. And these LEDs are connected to pin D2 on the Wemos board.

int R[13] = {255,255,227,255,255,128,075,000,127,000,127,255,000};
int G[13] = {194,165,066,000,000,000,000,000,255,255,255,255,000};
int B[13] = {000,000,052,000,255,128,130,255,212,000,000,000,000};

These are the 12 colors defined (color 0 to 11), these are used for the LEDS. And the corresponding HEX values are used for the buttons. There are 13 values in this array. The last value in the array turns the LED off (#000000 = black). You can change these colors if you want.

String buttonColor [2] = {"white", "black"};
boolean ColorState [13] = {1,1,1,1,1,1,1,1,1,1,1,1,1}; // initial colors

All 12 buttons have a "State". When a button has the value "True", then the corresponding color is displayed by the LEDs. By pressing a button, the state of this button changes. This also changes the text color of this button (e.g. from black [on] to white [off]).

int waitTimes [16]= {50, 100, 150, 200, 250, 500, 750, 1000, 1500, 2000, ...
int waitTime     = 5; // default values

There are two buttons to change the timer value ("faster" and "slower"). The default waitTime value is 5. This value gives a timer interval of 500 milliseconds.

int nextColor (int lastColor)
{
   foundColor = numColors; // nothing found return value
   countColors = 0;        // count number of searches inside the loop

   do
   { 
      currentColor += 1;
      countColors  += 1;
      if (currentColor>numColors)   {currentColor=0;}
      if (ColorState[currentColor]) {foundColor=currentColor;}
   } 
   while (currentColor != lastColor 
       && foundColor == numColors 
       && countColors < numColors+1);
   return (foundColor);
}

This function finds the next color to display using the colorState array. It starts at the array position with the number lastColor. And returns the next index-value in the colorState array with the value 1.

An example: The following array has color 2-7 turned off (white text). Executing this function with the value 0 returns 1. Using this function with the value 1 returns 8. This is the next color which has colorState "True".

color 0: amber (FFC200)
color 1: orange (FFA500)
color 2: vermillion (E34234)
color 3: red (FF0000)
color 4: magenta (FF00FF)
color 5: purple (800080)
color 6: indigo (4B0082)
color 7: blue (0000FF)
color 8: aquamarine (7FFFD4)
color 9: green (00FF00)
color 10: chartreuse (7FFF00)
color 11: yellow (FFFF00)

The colors are always displayed in a fixed order. When all 12 colors are off, all LEDs are turned off (value 000000).

// interrupt os-timer
void timerCallback(void *pArg) 
   if (!buttonSparkle)
   {
      // Sparkle Off = Blink
   }
   else
   {
      // Sparkle
   }

There are 2 modes of operation: Sparkle and Blink. And they each have a different code path inside the OS-timer.

The blink mode has the easiest code. It gets the next color by calling the nextColor function. Then all LED colors are changed to this color.

The sparkle mode differs a lot. It always starts with the first available color in the ColorState array. And then the nextColor function is called for each LED. Increasing the starting LED value, gives a sparkling effect.

void showPage() 
{
   webPage += "<p><a href=\"socketAmb\"><button style=\"background:#FFC200;... 
   webPage += "   <a href=\"socketOra\"><button style=\"background:#FFA500;...
   webPage += "   <a href=\"socketVer\"><button style=\"background:#E34234;...

This function displays/refreshes the web page. There is only one web page in this sketch. The button colors are hard-coded. And these colors match the R-G-B-arrays.

void setup(void){

   server.on ("/",[](){showPage(); });
   server.on ("/socketAmb",[](){ColorState [0] = !ColorState [0]; showPage();});
   server.on ("/socketOra",[](){ColorState [1] = !ColorState [1]; showPage();});
   server.on ("/socketVer",[](){ColorState [2] = !ColorState [2]; showPage();});

The setup part contains the first executed code, after starting the Wemos. It initializes the neopixels, WiFi connection, OS timer and the web server.
Pressing a button on this page results in executing some code. For example: The first button on the web page (amber) has a reference (href) to "socketAmb". Pressing this button executes the code in the server.on part for "socketAmb). This piece of code alters the colorstate in the array for the first color (which is amber).

The LED colors are changed by a timed function. The colors don't change immediate.

void loop(void)
{
   server.handleClient();
}

The only thing in the main loop is to take care of the web server. All other processing is done by the timer function.

Step 11: No WiFi Version

The following Wemos code can be used as an example for a Christmas ornament without WiFi.

This code makes it possible to use an arduino, instead of a Wemos (ESP8266). The arduino code is available at circuits.io (WS2812 Matrix Example). The main difference in the code is the port-name (port 8 instead of D2).

#include <Adafruit_NeoPixel.h>
#define NUM_PIXELS 20
Adafruit_NeoPixel pixels(NUM_PIXELS, D2, NEO_GRB | NEO_KHZ800);

int R[14] = {255,255,227,255,255,128,075,000,127,000,127,255,000,255};
int G[14] = {194,165,066,000,000,000,000,000,255,255,255,255,000,255};
int B[14] = {000,000,052,000,255,128,130,255,212,000,000,000,000,255};

int NUM_ROWS = 20;
// led     01 02 03 04 05 06 07 08 09 10 11 14 13 14 15 16 17 18 19 20 
int ColorMatrix [20][20] = 
          {{13, 8, 7, 8, 7, 7, 7, 7, 7, 7, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7}
          ,{ 7,13, 8, 6, 7, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7}
          ,{ 7, 7,13, 7, 8, 7, 7, 7, 7, 8, 7, 8, 7, 7, 7, 7, 7, 7, 7, 7}
          ,{ 7, 7, 7, 7, 6, 7, 7, 7, 8,13, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7}
          ,{ 7, 7, 7, 7, 7, 8, 7, 7,13, 7, 7, 7, 8, 7, 7, 8, 7, 7, 7, 7}
          ,{ 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 6, 7, 7,13, 8, 7, 7, 7}
          ,{ 7, 7, 7, 7, 7, 7, 8, 7, 7, 7, 7, 7, 7, 8, 7, 7,13, 8, 7, 7}
          ,{ 7, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 6, 7, 7, 7,13, 8, 7}
          ,{ 7, 7, 7, 7, 7, 7, 7, 8, 7, 7, 7, 7, 7, 7, 8, 7, 7, 7,13, 8}
          ,{ 8, 7, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7,13}
          ,{13, 8, 7, 8, 7, 7, 7, 7, 7, 7, 8, 7, 7, 7, 7, 7, 7, 7, 7, 7}
          ,{ 7,13, 8, 6, 7, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7}
          ,{ 7, 7,13, 7, 8, 7, 7, 7, 7, 8, 7, 8, 7, 7, 7, 7, 7, 7, 7, 7}
          ,{ 7, 7, 7, 7, 6, 7, 7, 7, 8,13, 7, 6, 7, 7, 7, 7, 7, 7, 7, 7}
          ,{ 7, 7, 7, 7, 7, 8, 7, 7,13, 7, 7, 7, 8, 7, 7, 8, 7, 7, 7, 7}
          ,{ 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 6, 7, 7,13, 8, 7, 7, 7}
          ,{ 7, 7, 7, 7, 7, 7, 8, 7, 7, 7, 7, 7, 7, 8, 7, 7,13, 8, 7, 7}
          ,{ 7, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 6, 7, 7, 7,13, 8, 7}
          ,{ 7, 7, 7, 7, 7, 7, 7, 8, 7, 7, 7, 7, 7, 7, 8, 7, 7, 7,13, 8}
          ,{ 8, 7, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7, 7, 7, 6, 7, 7, 7, 7,13}};

int brightValue = 10;
int brightMax   = 16; // brightness (1..16)

void setup(void)
{
   pixels.begin();
}

void loop(void)
{
   for (int j = 0; j < NUM_ROWS; j++) 
   {
      for (int i = 0; i < NUM_PIXELS; i++) 
      {
         pixels.setPixelColor (i, R[ColorMatrix [j][i]]*brightValue/brightMax,
                                  G[ColorMatrix [j][i]]*brightValue/brightMax,
                                  B[ColorMatrix [j][i]]*brightValue/brightMax);
      }
   pixels.show();
   delay (50);
   }
}

The order of the LEDs is dependent on the order of gluing. My ornament has the following order:

  • Upper 5 LEDs: 4, 5, 6, 7 , 8 (counterclockwise).
  • Lower 5 LEDs: 11, 12, 13, 14, 15 (counterclockwise)
  • Middle 10 LEDs: 1, 2, 3, 10, 9, 16, 17, 18, 19, 20 (clockwise)

The number in the ColorMatrix corresponds to a R/G/B-color (see the first example for the colors). There are 20 rows with 20 LED colors. The main loop starts with the first row (this is row number zero in the code). And writes the 20 values to the LED strip.The show-function changes the colors.

Then the code waits 50 milliseconds, before to continue with the next row in the matrix. There is no timer in this example. This makes it possible to execute this code with an arduino.

Step 12: Decoration

The last step is decorating the Christmas ornament. First remove any excess glue, and all other irregularities.

It is intended to cover the entire Christmas ornament with glitter. Also, the LEDs. These provide enough light to remain visible.

Cover the 3D printed part entirely with glue. Use transparent glue of good quality. Sprinkle the glitter dust over the whole ornament. Use a box to collect the excessive glitter. This glitter can be reused. Continue until the entire ornament is covered with glitter.

Step 13: Merry Christmas

In this final step, I wish everyone a merry Christmas.

GosseAdema

IoT Builders Contest

Third Prize in the
IoT Builders Contest

Design Now: 3D Design Contest 2016

Fourth Prize in the
Design Now: 3D Design Contest 2016

Make it Glow Contest 2016

Participated in the
Make it Glow Contest 2016