CC Blog Projects Research & Design Hub

Lighting a Miniature Holiday Village

Written by Jeff Bachiochi

Part 2: It Takes Custom Lighting to Make a Village

In Part 1 of this two-part series, Jeff explained how to replace the incandescent bulbs in the buildings of a miniature holiday village with DotStar RGB LEDs controlled by an ESP8266. In Part 2, he shows how to enable an HTTP server in each ESP8266 to allow users to change any command or change the color of individual LEDs—without reprogramming the module—by using a smartphone, tablet, or computer.

As my wife, Beverly, was packing up all the Christmas decorations, I already saw “red,” the familiar color of Valentine’s Day, begin to emerge. There seemed to be a steady stream of packing boxes either coming down from the attic or returning to the storage space. After 50 years of integrating holidays into our home, we now have an (over) abundance of tchotchkes. The annual Christmas villages that get scattered all over every horizontal surface got an illumination upgrade this year.

Last month, in Part 1 of this article series [1], I presented a project to eliminate the standard 6W incandescent bulbs in the Village houses and shops. In the new setup, Adafruit DotStar LEDs, controlled by an ESP8266, replaced the standard bulb and wiring.

The Arduino IDE project uses an arbitrary number of Commands that are executed upon a time match. Each COMMAND contains an ON time, OFF time, LED#, RGB values, and a fade time. Since the ESP8266 has built-in Wi-Fi, I used the Arduino IDE to program an application, to allow linking to a local WAN. Once a connection to the Internet has been made, NTP (Network Time Protocol) is contacted to synchronize the application to the real time. The real time of day is used to check each COMMAND for a match of the ON or OFF times. When the time matches an ON or OFF time, a processEvent() turns the associated LED ON (using the RGB color values) or OFF if the color is Black (RGB=#000000). When the fade >0, the color values are fractionally changed every 100ms, until the fade value decreases to zero.

You can have any number of COMMANDs, but I used 10. That’s when you can program a change to one of the DotStar LEDs to occur 10 times during a day. Although you can buy DotStar LEDs as a chip, I purchased a 1m strip of DotStars. They are connected serially, and can be updated using an SPI port to bang out a clock and LED data.

Each DotStar requires 4 bytes of data—a brightness byte, followed by 1 byte each of Blue, Green, and Red data. The LEDs are updated by sending out a complete set of data that pushes itself through the string of LEDs. I am using only three DotStar LEDs in my string connected to my microcontroller. I bent the small strip at right angles between each LED (Figure 1) such that they point in different directions and light up different portions of the village structures that they inhabit, simulating lights in different rooms.

FIGURE 1
The Wemos mini (ESP8266) module I designed with for last month's build is shown here with 3 DotStar LEDs cut from a strip and mounted to the board. When bent at right angles to each other, the LEDs will shine in three different directions.
FIGURE 1
The Wemos mini (ESP8266) module I designed with for last month’s build is shown here with 3 DotStar LEDs cut from a strip and mounted to the board. When bent at right angles to each other, the LEDs will shine in three different directions.

These commands are fixed at the time of programming. This means that unless you change the COMMANDs for each module, all the lights will change at the same time in each building.

I wanted to make these more random, so each light in every structure would be independent. We already have a connection to the local WAN, so why not use it? This month’s project enables your smartphone, tablet, or any computer to permanently change any COMMAND or change the color of any LED in any building. We are going to accomplish this by enabling an HTTP server in each ESP8266.

PERMANENT

First off, the word “permanent” is so, uh, permanent. Presently the COMMANDs are in array[]. This is in volatile RAM, so it gets overwritten each time the system is powered up or reset. The EEPROM command can access a non-volatile area of EEPROM/FLASH when necessary. We can use this original definition of array[] as the source, and make a copy in the non-volatile area. We want this to happen only if it has never been done before. To do this, we will use the last location in this non-volatile area as a check. If the non-volatile area has been erased (as in programming the device), all locations will have the value of 0xFF. Once we save our COMMAND strings into the lower portion of this area, we can write a value other than 0xFF into this last location. If the program finds this location with another value, we will not copy array[] into the non-volatile area.

Note: Although this area can be erased and written thousands of times, a user must be careful not to allow this to happen. If this were in a loop of some kind, it could quickly kill off the area, and the data would be corrupted. That being said, we will rarely make any changes to these COMMANDs, so it should not be an issue.

Add the EPROM library and variable to EEPROM data.

//————————————–

// start EEPROM initialization

//————————————–

#include <EEPROM.h>

byte dataEE;

//————————————–

// end EEPROM initialization

//————————————–

Then we can define the area to consist of 512 bytes of non-volatile memory. The last byte, 511, will be used for checking if the area has already been initialized. If location 511 = 0xFF, then we need to call the function storeDataToEEPROM() to save array[]. This will save all the COMMANDs, 10 commands x 9 values = 90 bytes, starting at location 0. Also, we write 0x55 to location 511, and then commit the data, which does the actual writing.

If location 511 = 0x55, then we read the non-volatile data into array[]. Note that once saved, if any locations in the non-volatile area are changed, these will be stored permanently and will get loaded into array[] upon a reset or power up (Listing 1).

LISTING 1
Using non-volatile storage allows user changes to a COMMAND in array[][] to remain after a reset.

//----------------  // start EEPROM setup  //----------------  EEPROM.begin(512);  dataEE = EEPROM.read(511);  if(dataEE == 255)  {    Serial.println(“EEPROM is empty, saving Commands to EEPROM”);    storeDataToEEPROM();  }  if(dataEE == 0x55)  {    Serial.println(“EEPROM has save Commands, Retieving”);    loadDataFromEEPROM();    }  else  {    Serial.println(“Bad Data in EEPROM”);    while(1);  }  //----------------  // end EEPROM setup  //----------------
WEBPAGES

We want a user to access the COMMANDs from a wireless device (computer, tablet or smartphone) using any web browser. We already have access to the local WAN, because, as described in Part 1 of this article series [1], we accessed the NTP (Network Time Protocol) to get in sync with the “real time of day.” Now, we need to add support for our module to interface with a browser. The most common document is a webpage formatted in Hypertext Markup Language (HTML). This markup language is the standard for documents designed to be displayed in a web browser. It supports plain text, images, embedded video and audio contents, and scripts (short programs) that implement complex user interaction. The information is transferred across the Internet using the Hypertext Transfer Protocol (HTTP).

For a client (browser) to contact a server, it needs to know the server’s web address. We already have that. Our last application got a local WAN IP from the DNS server that assigns IPs to devices that try to connect to our local WAN. That IP was used by the NTP service to send back the local date and time to our module. If we were to open a browser and enter that IP address, we would get a message saying, “This site can’t be reached,” because we have not yet set up the server.

The IP address (Internet Protocol address) is the key to communications. It can establish a path between the browser and the server. Once the path has been established, communication is much like any other medium that uses a request/response model, with your browser as the requesting client, and the server responding with a response. The initial request from a browser—in my case a Mozilla Firefox browser—might look like this:

GET / HTTP/1.1

Host: 192.168.0.20

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate

Connection: keep-alive

Upgrade-Insecure-Requests: 1

<LF>

Note that the whole message is ASC and is readable. It ends with an empty line or two-line feeds in a row. I added the ending <LF> here to make this more apparent. This is an HTML GET command, but there is no payload data here. It is not requesting anything specific, and is expecting a response like this:

HTTP/1.1 200 OK

Content-Type: text/plain

Content-Length: 21

Connection: keep-alive

Keep-Alive: timeout=2000

<LF>

I’ll call this the serverResponse() function. The server may want to transmit some actual data along with this response. I’ll call this the displayHTMLWebPage() function. In this case, we’ll send a simple text message, so we can see a response from the server in the browser.

client.println(“I’m here!”);

client.println();

Figure 2 shows the project’s IP address (192.168.0.20) entered into my browser, and the short string of text being displayed in the browser window. To achieve this, we need to add some more code to our program. The server library is added to the other libraries previously included. We’ll call our Wi-Fi Server “server,” and the Wi-Fi Client the “client.” Then, in the loop() function, we add a test for a client and branch to serviceClient(), when we get a request from the browser (Listing 2).

FIGURE 2 
A browser was designed to retrieves information and display it on your desktop or mobile device. The information needs to be shared and displayed in a consistent format, so that people using any browser, anywhere in the world, can see the information. In its simplest form plain text will be displayed.
FIGURE 2
A browser was designed to retrieves information and display it on your desktop or mobile device. The information needs to be shared and displayed in a consistent format, so that people using any browser, anywhere in the world, can see the information. In its simplest form plain text will be displayed.

Now, we need to add the code for the serviceClient() function, which will include the functions previously mentioned, serverResponse() and displayHTMLWebPage() (Listing 3).

— ADVERTISMENT—

Advertise Here

LISTING 2
The web server remains idle until a client makes contact with the server's IP address.

initialize...#include <ESP8266WebServer.h>// Set web server port number to 80WiFiServer server(80);// Variable to store the HTTP requestString header;WiFiClient client;loop()...  client = server.available(); // Listen for incoming clients  if (client)   {                    // If a new client connects,    serviceClient();  }
LISTING 3
When a client makes contact, we need to dissect the request and then respond properly to it. There are two distinct actions here: serverResponse(), and displayHTMLWebPage().

//--------------------------------------// start serviceClient() function//--------------------------------------void serviceClient(){  Serial.println(“New Client.”);    // print a message out in the serial port  String currentLine = “”;          // make a String to hold incoming data from the client  while (client.connected())   {                                 // loop while the client’s connected    if (client.available())     {                               // if there’s bytes to read from the client,      char c = client.read();       // read a byte, then      Serial.write(c);              // print it out the serial monitor      header += c;      if (c == ‘\n’)                // if the byte is a newline character      {            // if the current line is blank, you got two newline characters in a row.        if (currentLine.length() == 0)		// that’s the end of the client HTTP request, so send a response:        {             serverResponse();                                      //          displayHTMLWebPage();          //          break;        }         else         { // if you got a newline, then clear currentLine          currentLine = “”;        }      }       else if (c != ‘\r’)       {  // if you got anything else but a carriage return character,        currentLine += c;      		// add it to the end of the currentLine      }    }  }  header = “”;                          // Clear the header variable  client.stop();                        // Close the connection  Serial.println(“Client disconnected.”);}//--------------------------------------// end serviceClient() function//--------------------------------------//--------------------------------------// start serverResponse() function//-------------------------------------- void serverResponse()     {                         // content-type so the client knows what’s coming, then a blank line:  client.println(“HTTP/1.1 200 OK”);  client.println(“Content-type:text/html”);  client.println(“Connection: close”);  client.println();}//--------------------------------------// end serverResponse() function//--------------------------------------          //--------------------------------------// start displayHTMLWebPage() function//--------------------------------------void displayHTMLWebPage(){  client.println(“I’m here!”);  client.println();}//--------------------------------------// end displayHTMLWebPage() function//--------------------------------------

Although the browser was smart enough to display our text message, it was designed to do so much more! It uses the HyperText Transfer Protocol to request the webpage contents from an IP address and to handle that formatted response. Notice that the response began with the response line, “HTTP/1.1 200 OK.” This informs the browser that the response will conform to the HTTP protocol. All requests/responses headers may contain colon-separated field Names/Values pairs that transfer information about this packet. The “Content-type:text/html” header indicates that the following section will be text or formatted HTML. Up to this point, none of this information is actually displayed by the browser. The header section ends with a blank line ending in <CR><LF>.

A response message may continue with a body that contains text in HTML. It defines the content and appearance and is often assisted by technologies such as Cascading Style Sheets (CSS) and scripting languages such as JavaScript.

HTML provides a means to create a structured document (display) via structural discipline for text such as headings, paragraphs, lists, links, quotes, and other items. HTML elements are delineated by “tags,” written using angle brackets. Browsers do not display the HTML tags, but use them to interpret the content of the page. Following the HTML rules, our minimum message should be formatted as follows:

<!DOCTYPE html>

<html>

<body>

<div>

<p>I’m here!</p>

</div>

</body>

</html>

The text between <html> and </html> describes the webpage, and the text between <body> and </body> is the visible page content. The tag <div> defines a division of the page, and the tag <p> defines a paragraph both used for easy styling. I recommend W3Schools, a learning and training resource for website developers, for learning and trying out HTMl commands [2]. It is my go-to place for learning new elements that I may want to use. You can try the above “I’m here!” code and see how it is displayed.

I use Microsoft Expression Web 4 [3] for doing any website work. It is easy to use and gives a real-time display of what your code will produce. You may already own a similar application. When I place the above code into MEW4, I can see both code and output on the same screen (Figure 3).

FIGURE 3
Web designers use an application to help them code a webpage using HTML code. I use Microsoft Expression Web 4 [3] to help write HTML code and display what it will look like. In Figure 2, the browser just put my text in the browser window. If you follow HTML rules for compatibility, that text should be formatted like this.
FIGURE 3
Web designers use an application to help them code a webpage using HTML code. I use Microsoft Expression Web 4 [3] to help write HTML code and display what it will look like. In Figure 2, the browser just put my text in the browser window. If you follow HTML rules for compatibility, that text should be formatted like this.
USER WEB INTERFACE

Now we can design a webpage to display all the information we have in each of the COMMANDs. These are in the multi-dimensional array[][], which holds each of the 10 command arrays of 9 byte wide data values assigned to these variables: nextHourOn, nextMinuteOn, nextHourOff, nextMinuteOff, nextLED, nextRed, nextGreen, nextBlue, and nextDuration. I want to be able to display one of the 10 COMMANDs 0-9, in my browser, so the first element I use is an input. This is a “number” type input, where the user can enter a number 0-9 into a box. It has the text string “Command (0-9):”, displayed to the left of the box. Just below this, I added a second input. This time it is of the type “radio,” which is a clickable button. Only one radio button can be set at a time; clicking on a different button resets other buttons in a group. You can see the HTML code and the browser presentation in Figure 4.

FIGURE 4 
By following the HTML rules, you can control how the webpage will be displayed. Non-displayed elements describe placement, font, size, and color of text. Displayed elements might be text, input collection objects, or audio and video. This page contains labels, numerical input, radio button selection, color picker, time inputs, and a Submit button, which sends the present values of all inputs to the server.
FIGURE 4
By following the HTML rules, you can control how the webpage will be displayed. Non-displayed elements describe placement, font, size, and color of text. Displayed elements might be text, input collection objects, or audio and video. This page contains labels, numerical input, radio button selection, color picker, time inputs, and a Submit button, which sends the present values of all inputs to the server.

Additional “fieldset” elements are used to separate the inputs into sections. The second is for an LED, which includes position, color, and fade duration for that LED. Note that the color is a single value, but is actually three RGB color byte values. The user will be presented with a color chart from which a color can be assigned with just a click. The third section is for choosing ON and OFF times. Each choice presents the user with an input for time, which is chosen with a click.

The three radio buttons allow a user to choose one of three functions. The first button, “Get Command,” asks the server to update the display with one of the COMMANDs. The second button, “Update Color Only,” asks the server to change the color of chosen LED immediately. The third radio button, “Update Command,” forces the server to write a new command string to the chosen COMMAND. This string reflects any changed data presently being displayed and is written to EEPROM to replace the present COMMAND in that position.

TABLE 1
The input object ID and data value pairs along with the associated fields.
TABLE 1
The input object ID and data value pairs along with the associated fields.

This brings us to the “Submit” button at the bottom of the page. You use it to send your choice to the server. This will send a string of data to the server. The header contains all your input objects, where each is identified with the “id=value” format shown below. The input object IDs and their data are given in Table 1.

GET /?positionCMD=0 &positionLED=0 &colorLED=%23ff0000 &fadeLED=0 &modeCMD=UpdateColorOnly &timeON=07%3A00 &timeOFF=07%3A00 &finalfield=0 HTTP/1.1

Host: 192.168.0.20

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:122.0) Gecko/20100101 Firefox/122.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8

— ADVERTISMENT—

Advertise Here

Accept-Language: en-US,en;q=0.5

These IDs pass data to and from the server. Data from the server can update the default values of an object. Previously, in the displayHTMLWebPage() routine we had just two lines. Let’s replace these with new HTML code (Listing 4). Before the actual HTML code, some housekeeping is done to prep the variables with the latest data, based on the request. Note that we place our HTML text in a client.print(“”) statement. Remember: to embed quotes in a string, substitute all double quotes with single quotes. In those places in the HTML code where we want to substitute a variable for a constant, as in the first HTML code for the positionCMD:

”<label for=’positionCMD’>Command (0-9):</label><input type=’number’ id=’positionCMD’ name=’positionCMD’ value=’0’ min=’0’ max=’9’>

In place of the value:constant, value=’0’, we want to remove the zero, break the string with double quotes in place of the zero, and concatenate the variable using a String( positionCMD) statement with the (now) two text strings (see Listing 4).

That takes care of sending our webpage. Now we turn to the header that gets sent by the Client.

Immediately following the serverResponse() and displayHTMLWebPage() functions in the serviceClient() function, add another routine, checkClientRequest(). This routine will search through the received header for each of the expected id=value pairs to extract the value and update our local variables with that value. An example of one of the searches is shown in Listing 5.

If the string “positionCMD” is found, then look after the equal sign for an integer value. PositionCMD is updated with this value. After all the strings of interest have been found, we look for the modeCMD. The modeCMD input comes as a string reflecting the last radio button to be clicked; only the selected radio button in the group gets sent by the submit button. We can use this to determine what to do with the data transmitted when the “Submit” button is clicked in our web browser (Listing 6).

LISTING 4
This routine replaces the "I'm here!" text with a data form that displays the parameters of one COMMAND from the array[][] and allows the user to input new data. Some variables are initialized based on modeCMD and positionCMD; these reflect the present state of things in our application. Then, clientprintln() statements transfer the actual HTML code (from figure 4)

//--------------------------------------// start displayHTMLWebPage() function//--------------------------------------void displayHTMLWebPage(){  String GC = “”;  String UCO = “”;  String UC = “”;  if(modeCMD == “GetCommand”)  {    GC = “checked”;  }  if(modeCMD == “UpdateColorOnly”)  {    UCO = “checked”;      }  if(modeCMD == “UpdateCommand”)  {    UC = “checked”;      }  //  loadFromArray(positionCMD);  //  colorLED = “#” + byteToHEX(redLED) + byteToHEX(greenLED) + byteToHEX(blueLED);  //  timeON = “”;  if(onHour < 10)  {    timeON = “0”;  }  timeON = timeON + String(onHour);  timeON = timeON + “:”;  if(onMinute < 10)  {    timeON = timeON + “0”;  }  timeON = timeON + String(onMinute);  //  timeOFF = “”;  if(offHour < 10)  {    timeOFF = “0”;  }  timeOFF = timeOFF + String(offHour);    timeOFF = timeOFF + “:”;  if(offMinute < 10)  {    timeOFF = timeOFF + “0”;  }  timeOFF = timeOFF + String(offMinute);  // Display the HTML web page  client.println(“<!DOCTYPE html><html><script>console.clear();</script><head><title>ftb405 Village Color</title></head><body style=’background-color:lightgray;’><form>”);  client.println(“<fieldset><label for=’positionCMD’>Command (0-9):</label><input type=’number’ id=’positionCMD’ name=’positionCMD’ value=’” + String(positionCMD) + “’ min=’0’ max=’9’><br>”);  client.println(“<input type=’radio’ id=’GetCommand’” + GC + “ name=’modeCMD’ value=’GetCommand’><label for=’GetCommand’>Get Command </label><br’></fieldset>”);  client.println(“<fieldset><label for=’positionLED’>LED Position (0-9):</label><input type=’number’ id=’positionLED’ name=’positionLED’ value=’” + String(positionLED) + “’ min=’0’ max=’9’><br>”);  client.println(“<label for=’colorLED’>Select LED color:</label><input type=’color’ id=’colorLED’ name=’colorLED’ value=’” + String(colorLED) + “’><br><label for=’fadeLED’>LED Fade tenths(0-255):</label>”);  client.println(“<input type=’number’ id=’fadeLED’ name=’fadeLED’ value=’” + String(fadeLED) + “’ min=’0’ max=’255’><br><input type=’radio’ id=’UpdateColorOnly’” + UCO + “ name=’modeCMD’ value=’UpdateColorOnly’>”);  client.println(“<label for=’UpdateColorOnly’>Update Color Only</label></fieldset>”);  client.println(“<fieldset><label for=’timeON’>LED ON time:</label><input type=’time’ id=’timeON’ name=’timeON’ value=’” + String(timeON) + “’><br>”);  client.println(“<label for=’timeOFF’>LED OFF time:</label><input type=’time’ id=’timeOFF’ name=’timeOFF’ value=’” + String(timeOFF) + “’><br>”);  client.println(“<input type=’radio’ id=’UpdateCommand’ “ + UC + “ name=’modeCMD’ value=’UpdateCommand’><label for=’UpdateCommand’>Update Command</label></fieldset>”);  client.println(“<input type=’hidden’ id=’finalfield’ name=’finalfield’ value=’0’><input type=’submit’ value=’submit’></form></body></html>”);  client.println();}//--------------------------------------// end displayHTMLWebPage() function//--------------------------------------
LISTING 5 
The new checkClientRequest() routine searches the received header for the items in Table 1.  This is an example of one search for the "positionCMD=" string. This data value associated will change our positionCMD variable asking for a new COMMAND from array[][].

// Request sample: Get /? positionCMD=0 &positionLED=0 &colorLED=%23ff0000 &fadeLED=0 &modeCMD=UpdateColorOnly &timeON=07%3A00 &timeOFF=07%3A00 &finalfield=0  Serial.println(“Request header: “ + header);  int p1 = header.indexOf(“positionCMD=”);  if(p1 != -1)  {    positionCMD = header.substring(p1 + 12).toInt();    Serial.println(“positionCMD=” + String(positionCMD));  }
LISTING 6 
Referring to Listing 5, another search in the checkClientRequest() routine is for the "modeCMD=" string, where the important stuff happens. Depending on the radio button selected, several routines are called to carry out the client's request

if(modeCMD == “GetCommand”)  {    // change Command    loadFromArray(positionCMD);    Serial.println(“New Command Displayed”);  }  if(modeCMD == “UpdateCommand”)  {    // save Command    storeToArray(positionCMD);    storeDataToEEPROM();    Serial.println(“Command Changed”);  }  if(modeCMD == “UpdateColorOnly”)  {    // set Color    RedColor = redLED;    GreenColor = greenLED;    BlueColor = blueLED;    storeAtLED(positionLED);    updateDotStar();    Serial.println(“Color updated”);  }

Our webpage now has three functions. Perhaps you wish to see what parameters are assigned to COMMAND 2. By changing the positionCMD input and clicking the “Get Command” button, the modeCMD will request that the displayed data be updated using the function loadFromArray(positionCMD). There are 10 commands in array[][], 0-9, and the positionCMD is a pointer into that array. The command’s nine elements will be loaded such that the webpage will now display the requested set of elements.

You might want to change just the color of one of the LEDs in the string. The “update Color Only” button can be used for this. You can choose any LED and color and the modeCMD will branch to the routines to accomplish that task without affecting anything else.

The whole idea of this second part of this project was to allow those static COMMANDs to be permanently changed by the user, without reprogramming the module. We started off with the EEPROM routines necessary to permanently change one or more COMMANDs, at least until the module gets reprogrammed. With the “Update Command” button clicked, the modeCMD directs us to the routine to store new data to the EEPROM using the storeToArray(positionCMD) and storeDataToEEPROM() routines.

CONCLUSION: ADVANTAGE BROWSER

If you’ve never written any HTML code, I hope this project reduces any apprehension you may have about trying it. I find a real advantage in using a tool that is available on every computer and phone. The power of the browser is often overlooked. Browsers are actually becoming more powerful with each iteration of the HTML framework. Figure 5 shows the webpage produced by my project, in four different browsers.

FIGURE 5
This project's webpage, which is nothing fancy but, just contains the basic elements necessary to get the job done. It gets translated pretty well by different browsers. From top left clockwise, Firefox, Chrome, my Motorola phone, and Edge browsers are shown.
FIGURE 5
This project’s webpage, which is nothing fancy but, just contains the basic elements necessary to get the job done. It gets translated pretty well by different browsers. From top left clockwise, Firefox, Chrome, my Motorola phone, and Edge browsers are shown.

All modern browsers, including Google Chrome, Mozilla Firefox, Opera Mini, Microsoft Edge, and Apple Safari, support HTML 5 and all their features. How HTML has evolved is discussed in W3C Wiki [4].

If you look at what happens when you use the input type=color, for instance, you can easily imagine all the background programming that is involved to make this HTML element run on a browser. All the power comes to you through a couple of lines of HTML code. Pretty remarkable. So, why not tap into that power and save a lot of programming time. Yeah, some of this requires putting in some effort to learn. However, there is nothing like developing a new skill. You don’t need to be a pro to put it to good use. It gives you a new appreciation for web designers, every time you view a new website. Too much to learn, too little time. 

RESOURCES
Espressif Systems | www.espressif.com

REFERENCES
[1]  Jeff Bachiochi, “FROM THE BENCH: Lighting a Miniature Holiday Village: Part 1: Using DotStar RGB LEDs with an ESP8266 & WEMOS-D1 Mini.” Circuit Cellar 405, April 2024, p. 50.
[2] – World’s largest web developer site:  www.w3schools.com
[3]  Microsoft Expression Web 4 is no longer supported, but still great: www.expression-web-tutorials.com/ew4/installing-ew4.html
[4] How HTML has evolved is discussed in W3C Wiki:  www.w3.org/wiki/HTML/Specifications.

— ADVERTISMENT—

Advertise Here

SOURCES
Wemos D1 mini ESP8266: www.wemos.cc
DotStar LEDs:  www.adafruit.com/search?q=dotstar

Code and Supporting Files

PUBLISHED IN CIRCUIT CELLAR MAGAZINE • MAY 2024 #406 – Get a PDF of the issue

Keep up-to-date with our FREE Weekly Newsletter!

Don't miss out on upcoming issues of Circuit Cellar.


Note: We’ve made the Dec 2022 issue of Circuit Cellar available as a free sample issue. In it, you’ll find a rich variety of the kinds of articles and information that exemplify a typical issue of the current magazine.

Would you like to write for Circuit Cellar? We are always accepting articles/posts from the technical community. Get in touch with us and let's discuss your ideas.

Sponsor this Article
Website |  + posts

Jeff Bachiochi (pronounced BAH-key-AH-key) has been writing for Circuit Cellar since 1988. His background includes product design and manufacturing. You can reach him at: jeff.bachiochi@imaginethatnow.com or at: www.imaginethatnow.com.

Supporting Companies

Upcoming Events


Copyright © KCK Media Corp.
All Rights Reserved

Copyright © 2026 KCK Media Corp.

Lighting a Miniature Holiday Village

by Jeff Bachiochi time to read: 18 min