Synchronising Christmas Lights Over WiFi
by Wesilg in Circuits > Microcontrollers
1192 Views, 15 Favorites, 0 Comments
Synchronising Christmas Lights Over WiFi
Two Christmas' ago, my wife Jacqueline and I were outside enjoying a glass of bubbly, when she commented "Wouldn't it be nice if all the Christmas lights flashed at the same time."
This project was born.
Having done very little with microcontrollers or motor controllers before, there was lots of learning, from other Instructables authors, the internet in general, and revisiting coding skills not used for many years. There's a fantastic amount of information out there, sifting through to find gems was fun.
Last Christmas, the version deployed outside worked OK, but there were some obvious issues, and it started to undergo lots of refinement, which meant lots of new learning. It's just about at a stage where it's understandable, and works well enough to publish.
This Instructable is not prescriptive. There are many ways to accomplish synchronising the lights, with many more combinations of hardware and variations on the software. As an example, it can be a starting point, and hopefully will inspire others to do something similar for this coming Christmas.
Finally, the variety of Instructables articles and web sites referenced for this project was as vast as it was undocumented. There would be no way to retrace steps to determine the source of all the information, and it would be easy to omit someone. So search, ask, and you'll find what you need.
Thanks to all the authors whose work has been used in getting this far.
Having thought about it for a while, the original Instructable was a little light on detail in some respects, so this is the first update to provide a little more insight into the operation of the lights. There have also been some updates to the data and code, so that the design aligns better with the microcontrollers, and different sets of lights. For example, the original data was based around analogue values up to 1023. Making this up to 256 instead doesn't really change the granularity of the data, as the data is always in discrete steps over the range, but 256 aligns with the default PWM value range (which was overlooked in one version), and with W2812 lights.
This edit has additional information, and the updated code will be added as soon as possible.
Downloads
Supplies
Parts:
- Microcontroller board.
- The Wemos D1 Mini was used, as it was locally available, and comes with WiFi already build in. IoT wasn't part of the original brief, but this processor may lend itself towards that with further research.
- Motor Controller.
- This is a dual motor controller based on the L298, again locally available.
- There are many options online, depending on the current draw of the lights selected.
- When choosing the motor controller, check the inputs needed to run the device. This particular motor controller has separate forward and reverse enables, but only one PWM input (per motor channel). Forward and reverse may need separate PWM specifications for particular patterns, so a motor controller that has separate PWM inputs for forward and reverse will change the way the data is designed and applied.
- Also, each motor channel is labelled En, In1 and In2. The actual application seems to be that the labelled inputs are the enables, and the En is the PWM data input. The data sheet from the vendors web site included code samples that made this clearer. Check if you don't get the results you expect.
- Christmas lights.
- We had several historical sets, all two wire such that DC applied in either direction would light separate sets or strings of LEDs separately.
- Power Supplies capable of driving the lights and electronics.
- USB power supply and cables.
- Miscellaneous wire, ribbon cable, connectors, etc
Tools:
- Computer with:
- Arduino IDE;
- Excel;
- Soldering Iron, solder, etc;
- 3D Printer; We are lucky enough to have a 3D printer, and we built small boxes to house the circuit. These are obviously very specific to the hardware you select, but I've now added some ideas on this front.
General Approach
After doing some reading about microcontrollers, the following approach was adopted:
- WIFI was used to communicate, using the coverage of the house WIFI.
- The system adopts one node as the controller, and it broadcasts to tell the receiving nodes what to do. The receiving nodes don't need to respond.
- Each node hosts a small web page, allowing start, stop, changing the mode or speed, etc from a phone or tablet. It also allows the nodes to opt out of the synchronisation, should that be required (maybe the Christmas Tree wants to do it's own thing). In the event that any node is modified by its web page, other than opting in or out of the synchronisation, it assumes the controlling node function, provided it is still trying to remain synchronised.
- Timers or interrupts should be used rather delays in code.
- In this case, the timing of the code is managed by Timers. PWM output on the Wemos D1 Mini uses the internal interrupts, so software timers had to be used. This probably doesn’t affect the timing that much given they are Christmas lights. Other processors may not have this limitation and interrupts could be used to manage the timing.
- Doing as little processing as possible during the Timer/interrupt routines.
- For this reason, the data passed to the lights is indexed as sets of 256 points, and the index is a byte variable, so incrementing the index loops back to 0 without any conditional processing. Maybe this is overkill, the WEMOS D1 Mini has an 80MHz processor, so calculating values to apply on the fly is probably OK. It's Christmas lights.
- The data is held in arrays, so all the Timer routine needs to do is read the values from the arrays, apply them to the outputs, and increment the index. Faster processors and better code would allow for processing here if you like.
- The data was created in Excel, then transcribed into the code.
- Excel allows fancy computation for the PWM values, or data can be hand designed, and entered easily. One advantage is that the data can be graphed easily to get an idea of how the sequence might look as light intensities. Also, for repeating patterns, Excel allows copy and paste to extend the data.
- Creating generic data for motor driver pins. The electronics of the motor controller will determine how to connect and apply the data.
- Timing for the Timers or interrupts needs to be fast enough to stop the LEDs from appearing to flicker.
- Some patterns if slowed too much will obviously flicker, as the LEDs have no internal persistence.
- In order to get both sets of lights to appear "on" at the same time, the lights need to be flashed quickly enough to fool the eye.
- This is a compromise between the frequency (or timer interval) the data is applied, and the length of the data.
- With a data set of 256 points, the data can only be applied so slowly that the lights won't flicker.
- Longer data sets or calculating the data on the fly would allow higher frequencies (shorter intervals) to be used.
- The timer intervals are built into the data to make it easier to tinker.
- A simple protocol was required to allow the micro controllers to synchronise. The protocol developed over time to the following ideas:
- When the processor wakes up, it starts the default lights sequence.
- It then listens for 20 seconds, if it doesn’t hear a broadcast, if assumes it’s the first awake, and starts to broadcast the synchronisation signal every 5 seconds.
- If it does hear a broadcast, then it changes to the mode broadcast, timing and index that have just been received.
- When it hears a broadcast, it flags that another node is controlling. Over 30 seconds, it checks to see that something has been received, and if not, assumes the controlling node function with the current mode, timing and index.
- UDP was used to support the synchronisation protocol, as multicast is available. If anyone knows how this can be accomplished with TCP, please comment.
Light Sequence Data and Motor Controllers
Most Christmas light sets came preprogrammed with several light sequences, cycled with a single button on the controller. Watching the preprogrammed patterns allows creating similar patterns. Sometimes videoing on the phone and watching in slow motion helps. There's freedom in being able to design your own.
Some background here might assist in understanding why the data looks like it does.
- Two wire lights require DC current to be applied in either direction.
- This is done by providing signals to forward and reverse enable inputs. Both enables LOW and both enables HIGH produces no output current, so should your data supply both enables at the same time, no damage will be done.
- It might seem obvious, but in order to make either one set of LEDs light, or both sets of LEDs to appear to be lit, only one enable can be set at a time.
- Each point in the data can be considered an enable and an analogue value for the intensity. We used a range of 256 data points so the index wrapped around back to 0 without testing, but obviously this isn't really a limitation of the processors. Where a pattern can't be reasonably modelled as 256 points, longer ranges could be used. Some patterns will make this apparent.
- Smooth patterns of intensity can be modelled over the point range, without necessarily considering the enable. For now.
- Some light patterns require that both sets of lights appear to be lit at the same time. Twinkle, for example, worked best as two ascending and descending ramp waveforms, 180 degrees out of phase. That is, as forward increases, reverse decreases. This is accomplished by flashing the forward and reverse light sets quickly enough so that the our eyes can't keep up. This is managed by the enables, and the timing of applying the data points.
- A timer of some sort is needed to determine when to apply each data point.
- The data includes the times (in ms) at which the points are applied.
- The code allows some latitude in changing the timing, with upper and lower limits as well.
- These values need experimentation to determine at which point the timing makes flashing of the forward and reverse lights to become visible.
- The first published code made a distinction between analogue and digital light data. This drove the selection of separate interrupt routines being attached to the timers, based on an additional data set. The digital routine wrote 0 or 1 to the intensity output. We realised that there is really no distinction, as off and on are the 0 and maximum PWM values. The code published now has the digital data either 0 or maximum PWM value, simplifying the code a little.
- With the addition of W2812 lights a separate interrupt routine was introduced. We have only toyed with a small sample string of lights (mainly so the processor and lights could still be powered by a USB port), and a separate interrupt routine applies the same (new) data to odd and even sets of LEDs on the strip. The data works fine. The specification for W2812 lights says the data stream works at 800,000 bits per second (it's also specified in the configuration in the code). For a small sample there's no visible delay. Obviously, only one data pin is required at the processor, and no motor controller is needed in this case.
The approach to the motor controllers also needs some background.
- Where a motor controller has pairs of forward and reverse enable and PWM value, then outputs from the processor can be attached directly to all four inputs. (See the first attached image.) The enables and intensities can all be simply written to the processor outputs. Each data point (or pair) will (or should?) only have one enable active, and this will determine forward or reverse.
- Where a motor controller, like the one we used, has two enables, but only one PWM input, then either, before applying each data point, the enable must be tested so we know which pair of enable and intensity to retrieve and apply, or, the data can be mixed to match the enables, so each point can be considered two enables and one intensity to apply. As either one or no enables should only be written at a time, there should be no ambiguity in the way the data is interpreted and sent in either case.
As mentioned earlier, these patterns were designed and modelled in Excel. This allows complex processing (sine waves, etc) and graphing of the light intensities. Hand drawing (or typing) part of the pattern, then repeating or extending them to the length of your data model is also pretty easy in Excel.
The Twinkle pattern that most light sets provides was always our favourite, and it took a while to model light control that we were happy with.
The timing (in ms) for each mode is specified as well. Modes where both sets of lights appear to be lit needs quite quick refresh for persistence of vision. Some of these modes, if slowed too much, will result in visible flickering of the LEDs.
Because this motor controller has two enables but only one PWM inputs per channel, the PWM values for each string of LEDs are mixed to match the enable data. You could put both PWM streams and enables in the data, but would need to test which enable is enabled in the Timer routine. Mixing them reduces the conditional processing.
Again, Excel makes manipulating the data like this easier.
One point to note here is that if the light patterns don't look correct, check that your data matches the analogue write range for the processor. When this is not correct (ie, the default could be 255 rather than 1023), the lights will saturate before the full range is applied. The patterns will look wrong!
Control Protocol
A simple protocol is used to control the synchronisation of the nodes in the system. The protocol started much more complex, with receiving nodes responding to the controlling node, but eventually it simplified to the controlling node only having to broadcast the following:
- s (start) - directs any listening node to start the timers, send data to the motor controller, etc.
- e (end) - directs any listening node to stop processing the data, stops timers, etc.
- m (mode) followed by a single digit - directs any listening node to change to the mode broadcast. This doesn't change the point in the cycle at which the node is processing.
- r (resynch) followed by a byte integer - directs any listening node to change the offset used to apply the light data to the outputs.
- i (interval) followed by an integer as milliseconds - directs any listening node to set the light control timer to the corresponding interval in milliseconds.
The controlling node sends the mode, resynch and interval instructions at regular timing. The non-controlling nodes listen for instructions, and apply the broadcasts as they are received.
At a longer period (currently 20s), the non-controlling nodes determine if anything has been received, and if not, assume the role of the controlling node, broadcasting the mode, interval and synchronisation instructions for their current configuration.
In this way, the first node activated will eventually assume control, and, should it be removed from the system, another will take over the synchronisation role. If the configuration of any node is modified by it's web page, it assumes the controlling function, and broadcasts the changes. The existing controlling node will receive these broadcasts and becomes a non-controlling node.
While there is a chance that two nodes will assume the controlling role at the same time, the first that broadcasts effectively disables the second, and makes it a non-controlling node.
UDP was adopted as the broadcast mechanism, as I couldn't find references to broadcasting with TCP/IP. This was a first foray into communication protocols, and very much a learning experience. If anyone knows how this can or should be done better, please comment.
UDP had some interesting challenges, in that it appears that there are two modes required, normal UDP to receive, and multicast UDP to broadcast. The code that broadcasts stops the current UPD session, opens a multicast session to broadcast, then closes this and reopens a normal session. Again, comments on the need to do this, or ways in which it might be streamlined would be appreciated.
Code
Coding is a very personal experience. There are no claims that this code is perfect, or necessarily complete.
Some comments about the code.
- The code is separated into logical functions. This has made it easier to find segments to work on.
- Many of the timing intervals are defined as parameters at the start of the code or contained in the data. They can be changed once to affect all the code.
- There are still some code sections that are redundant, commented, but not yet deleted. Occasionally these were revisited, so still left in.
- There are diagnostic traces that write messages to the serial monitor in the IDE. These can be turned on and off at the web page, but once you are happy with your code, the ability for the code to report what it is doing could be removed completely.
- The web page is repeatedly constructed from scratch, which allows dynamic changes if desired.
- File 6 (the web page and routines) needs to have the extension changed to .ino before opening the code in the IDE. As an .ino file containing HTML, it would not upload here.
- Most of the code has been designed to reduce the conditional processing in the Timer routines. Testing once to determine if the analogue or digital Timer handlers are set up means that there's no conditional processing in the handlers. Other approaches are, of course, just as valid.
This was also a first effort at writing HTML, and communications over ethernet, so again, thanks to all the authors who've posted ideas used.
It's a starting point for the concept, so have fun!
Electronics
The particular motor controller used provides separation of the motor voltages from the electronics, plus a 5v regulator which could be used to power the processor as well.
In this case, the WEMOS D1 Mini uses 3.3v, which will drive the inputs to the motor controller fine, but not being sure of the result of powering the motor controller with it's internal 5v mixed with the 3.3v, the processor is powered from it's micro USB port, and the 5v motor controller electronics are powered through the processor board, as the USB 5v is exposed as a pin.
Also, the specification for the LM2805 voltage regulator says 28v maximum, and the lights we are using are powered at 31v. 31v seems common with Christmas lights.
All this was facilitated by powerboards with USB ports. Separate power supplies provided the voltage for the lights, which seem to use 31v reasonably often.
Extending the setup, the powerboards used are Tuya compatible, allowing the lights to be turned on to their default setting remotely. Changing modes currently needs to be done from the home WiFi.
There was also a 3D printed box to keep the electronics neat. The box and lid were created in TinkerCAD with lots of parameters to match the size of the two boards. This would need to fit your selected components.
Web Control
Everyone has a smart phone these days, and it makes sense to be able to change the light's behaviour from the couch rather than getting up to press buttons. Plus, as the lights use the WiFi for communications, being able to access them on the phone is reasonable. The lights are controlled by a simple web page.
In order to find the microcontrollers, IP addresses fixed at the router by MAC address were used. There may be simpler ways to do this. Again, comments would be welcome.
These notes here that may help with understanding it, as it developed and refined over 12 months:
- The entire web page is concatenated into a string. This allows both html and dynamic text to be intermixed in the page. This helps with ensuring selections have been applied, etc.
- Initially buttons in forms were used for the user interactions. Formatting these to appear on one line, together with text, was simple, once the syntax was discovered, but took some time. CSS commands to set "block-inline" or "block" embedded in the form would not work. Eventually a reference was discovered to building a style with "<style>.sameline {display:inline-block}</style>" in the header, with "class=sameline" embedded in the form. This worked.
- Formatting buttons to look the same size required a style as well. "<style>.sizedbutton {height: 24px; width: 80px}</style>" in the header, then "class=sizedbutton" before the input element sized the buttons correctly.
- Initially, the operating mode of the lights was cycled by a button on the web page. While this is the way many Christmas light controllers work, a drop down list of the modes is preferable, given the visual nature of the web page. Finding the code to make a drop down selection act on selection of the list element, rather than on a separate button, was not easy, but was actually out there. Populating the list on the fly allows additional changes to the mode data without recoding the html. The drop down selection required several style definitions, which are again embedded in the html header.
- Another project required a countdown as part of the feedback of the web page, and this can be done in the html. The tag "<meta http-equiv='refresh' content='1'>" in the html header will force the client to refresh every second.
- The buttons and selections effectively send the client to another page, so handlers for each action need to be available, and declared when the server is started.
- The end of each handler needs to redirect the page back to the root page. The two lines used to accomplish this work, contributors better versed in html may be able to comment on why.
What's Next?
This Instructable is mostly about the concept, that Christmas lights could be synchronised. From here, there are many options to expand, and many really bright people on Instructables to take it further.
Updates now include:
- rationalising the data to a 256 analogue range;
- simplifying the code by removing the distinction between analogue and digital light patterns;
- including w2812 lights driven by the same data, but a separate interrupt routine.
Further changes that are planned include:
- synchronising to music (there are several examples already on Instructables);
- storing persistent data so the last mode used will be the default when the system is powered. This can be accomplished on the WEMOS D1 Mini with LittleFS;
- storing the SSID and password as persistent data. Dummy setup and loop routines that collect this data, then write it using LittleFS would remove the need for having to hard code parameters. Once the files are written, the correct setup routine would read and apply the parameters, and the dummy setup could be modified to remove the hard coding, or removed completely.
- adding a configuration web page. Currently, the SSID and password are hard coded. Also, in order to find specific processors, we have matched the MAC addresses to specific IP addresses at our router. An easier way to find the MAC address for this is needed. Another project currently under way is using a small web page on the Wemos D1 Mini, but hosting the WIFI at the processor as well. A small configuration page that captures the SSID and password as persistent data, then reports the MAC address for router configuration is in the pipeline. I'm not sure what facilities for persistent data exist for other processors, but and internet search will provide information around this.
It was fun and an incredible learning exercise. Looking forward to seeing how the ideas might be used. Have fun, and thank you Instructables for the platform for sharing ideas!