WLED custom holiday LED lighting

Off-Topic: Controlling WLED with a Custom Holiday Schedule

Let's introduce a new blog series, I am going to call it "Off-Topic" as that seems most fitting. 3D printing provides some great utility around the house with everyday projects, so this series will focus on some of the ways I've used 3D-printed objects in my own personal home projects.

With that, I will introduce the primary focus for the first installation of this series: WLED. WLED is a lightweight web server utility to control NeoPixel (WS2812B, WS2811, SK6812) LEDs. Many people use WLED to control lights in custom-made lamps, holiday decorations, kitchen cabinet lighting, decorative house lighting, and so much more.

If you're familiar with WLED, you certainly know how powerful it is and how many ways it can be used, but one thing it's missing is a dynamic and highly-customizable scheduling system. I've seen some awesome holiday patterns posted to the WLED subreddit, which inspired me to create some complex presets using segments and such, but the one thing I kept thinking to myself is "wouldn't it be nice if my lights could just enable a preset or playlist automatically depending on the holiday?" WLED is simply a tool to control LEDs and create amazing patterns and effects, and it's a great tool for that job. Expecting it to include a complex scheduler is probably asking too much outside of the scope of the project, so I decided to figure something out.

Luckily, WLED has a pretty simple and intuitive API. To make scheduled API calls, you'll need a home server, Raspberry Pi, or something similar. I am running Ubuntu on my home server, so this guide will be focused on bash scripting, but you should be able to do something similar using Windows and Task Scheduler.

While you can do some very complex things with the API, down to controlling individual pixels, I was more focused on simply creating my presets using the GUI and then calling those presets on a dynamic schedule based on holidays, specifically US holidays for me. Holidays like Labor Day are tough because they don't fall on the same date, and it can be tough using just the date command as you'd need to figure out any dates that final Monday could fall on, but I'd also like the holiday preset to run all weekend for "big" holidays like that. Enter the only prerequisite tool you'll need for this: calendar.

The Calendar utility will create a few default calendars in the default directory /usr/share/calendar. You can even create your own calendars to use with this tool! For my purposes, however, I'm focusing on the US holiday calendar. You can query this specific calendar with the "-f" switch followed by the path to the calendar, "/usr/share/calendar/calendar.usholiday" by default. With no additional arguments, this will compare the current date as well as the next day against that calendar, and return any lines that contain a holiday. A nice additional feature of the calendar utility is if the current date is a Friday, it'll also look ahead to the following Monday for any upcoming holidays.

Now it's time to start a bash script which will run each evening, checking for any holidays. I won't go over the basics, so you'll still need to define your shell and make the script executable when finished. This guide will simply showcase some of the basic logic and functions to get you started so you can customize your own script to run exactly how you'd like!

To start, we'll need to define some variables:

To get the current month number:

month=$(date +%m)

We'll consider FRI-SUN as a weekend, so if the current day number is 5 or greater, set the variable "weekend" to "true":

if [[ $(date +%u) -ge 5 ]]; then weekend=true; fi

Now it's time for the real "meat" of the script - defining our holidays and setting a preset value. You can see all the holidays in a specific calendar by checking the contents of the calendar file:

cat /usr/share/calendar/calendar.usholiday

Take a look at the holidays you'd like to use, and copy/paste the holiday names into a file. Remember, Linux is case-sensitive so be sure to retain uppercase/lowercase letters. You probably don't need to copy the entire holiday name, but grab enough to make it unique so other holidays aren't matched accidentally. Below is a snippet from my script for Memorial Day weekend:

holiday=$(calendar -f /usr/share/calendar/calendar.usholiday | grep "Memorial Day")
holidayWeekend=$(calendar -B 3 -f /usr/share/calendar/calendar.usholiday | grep "Memorial Day")
if [ ! -z "$holiday" ] || ([ $weekend = 'true' ] && [ ! -z "$holidayWeekend" ]); then

ps=24
psBack=11

hue=lwNJxUTAxxCgwhw
fi

Let's break that down. The first line is setting the "holiday" variable. Piping in the grep command with a specific holiday name will allow us to filter any returned lines to only include the holiday we are looking for, since some days have multiple holidays and some days might have a holiday we don't want to turn lights on for.

The second line sets the "holidayWeekend" variable. Since the calendar utility only looks at the current date and ahead, the "-B 3" argument looks backwards three days for those holidays that fall on a Thursday or Friday, so the lights will turn on all weekend. For instance, Veterans' Day is on a Thursday, so the calendar utility won't return that holiday the following Friday-Sunday, so by using the "-B" switch, we can look backwards a few days to account for that.

The third line is a combined "if" statement. The first portion [ ! -z "$holiday" ] looks to see if the "holiday" variable is not empty. If there is a holiday on the current date or next day that matches the "grep" statement, this variable will be set. The second portion ([ $weekend = 'true' ] && [ ! -z "$holidayWeekend" ]) will evaluate to true if the current day is a weekend day (FRI-SUN) AND there was a holiday in the last three days. These two portions are joined with an "or" operator (||), so if the first part OR the second part is true, then the variables in the "then" block will be set as written.

And the penultimate part of the logic is setting the variables if those conditions evaluate to true. I am using "ps" as my preset variable. This number matches a preset I've already created in the WLED interface. You'll notice I am setting a few variables here, as I have lights controlled by WLED in the front of my house as well as the back, in addition to some outdoor Philips Hue lights I am also controlling. Maybe that's another blog post for another day...

Finally, close the "if" loop!

Now our holiday status has been evaluated, and if everything matches up, our "ps" variable will be set to a preset number in WLED. The final part of the script is to tell WLED to turn that preset on:

#Turn on lights if preset variable exists
if [[ $ps ]]; then
curl -s -X POST "http://WLED.IP.ADDRESS.HERE/json/state" -H "Content-Type: application/json" -d '{"on":true,"bri":255,"ps":"'"$ps"'"}'
else
curl -s -X POST "http://WLED.IP.ADDRESS.HERE/json/state" -H "Content-Type: application/json" -d '{"on":false}'
fi

Another "if" block here, this one is basically saying if the "ps" variable contains a value, then run a curl command to tell WLED to enable that preset at full brightness. Otherwise (else), if the variable does NOT contain a value, turn the lights off. That last part isn't completely necessary, but just in case the lights happen to be on for some reason, this is more of a catch-all to turn them off. You'll of course need to enter the IP address of your WLED controller where indicated above.

Now you can save the script, create a cron job to execute it every evening, and you're done! Of course, you'll want to write a second script and cron job to turn the lights off in the morning. I use a utility called heliocron so I can set my cron jobs to run at 2pm each day, but the heliocron utility ensures the scripts don't actually execute until a specified time before/after sunset/sunrise. Because sunset/sunrise times obviously change throughout the year, this ensure my lights always come on an hour before sunset and then turn off an hour after sunrise, no matter what time of year it is.

This post turned out longer than I intended, but my goal is to make sure you can create your own schedule scripts, regardless of your experience and skill level with bash/Linux. If you were able to use this to create your own WLED scheduling scripts, drop a comment below! Or, if you have any suggestions or tips to help others out, feel free to do so!

Back to blog

Leave a comment