Turn your Flickerstrip into a binary clock

The Lightwork editor works great for a number of different patterns, there are, however, a few limitations. The biggest of these limitations is that the Lightworks you create with the editor will always be static patterns. You're recording a series of pixels that will get played over and over again, exactly the same way. The programmers among you will probably notice this limitation sooner than most and you eventually want a way to generate your frames on the fly. In this post, I'm going to show you how you can circumvent this limitation by using a script to upload frames to your Flickerstrip.

To illustrate this, we're going to use an idea that user neotarlax submitted. Neotarlax has submitted a few Lightworks to the repository and has been an active member of the forums. Below are a sample of his lightworks "Inverted Hearts", "Bouncing Sun" and "Smooth Rainbow + White Stripe". One of the advanced techniques that Neotarlax has been making use of is the image import/export functionality. This allows him to design these lightworks in a program like Photoshop and then reimport them as Lightworks.

To illustrate this, we'll use a simple Python script to create a binary clock that will display on your Flickerstrip and continually update. Our binary clock will update once per second and display the time of day in 24hr format down to the second precision. If we wanted to design this in the Lightwork editor, it would take 86,400 frames! (24 hrs * 60 minutes * 60 seconds). This would be too large to fit into Flickerstrip's Lightwork storage, even if we took the time to create it using the designer. Not to mention that it may be difficult to get the timing to work out right as Flickerstrip wouldn't know which frame to play at what time.

Before we get started, you can find the full code listing under the "BinaryClock" repository on our Flickerstrip GitHub page.

Here's a demo of the result:

Get the time in binary

Without further ado, let's jump right in. First up, we're going to have to generate the time in binary. To do this, we'll use the built-in datetime module and the format function:

#!/usr/bin/env python

from datetime import datetime

now = datetime.now();

# We're going to use some array cleverness to make this a bit more extensible
# In the future, we could easily add the date or maybe even milliseconds
timeSlots = [5,6,6]; #This represents the number of binary digits we want to allocate to each segment hours. minutes and seconds respectively
currentTime = [now.hour,now.minute,now.second]; #This array is parallel to timeSlots and holds the current time in hours, minutes, and seconds

for i in range(len(timeSlots)): #This will iterate through each timeSlot using the index, i
    #This line is a bit of magic. To convert an integer to a binary string, we use something like.. format(value,'08b') which would
    # return the binary string format of the provided value padded with 8 zeros
    #To get what we're looking for, we concatenate the zero (to pad with zeroes) with the length of the binary slot (as a string) and the "b" for binary
    binarySlot = format(currentTime[i],"0"+str(timeSlots[i])+"b");

    #for now, we'll just print this
    print(i,timeSlots[i],binarySlot);

When we run this, we'll get a printed output that looks something like this:

0 5 01010
1 6 001111
2 6 101100

The first field contains the index of the time slot, the second field is the number of binary digits in that time slot, and the last is the binary representation of the time slot for the current time.

Generating pixel data

Now that we have the time, we want to decide how our clock will display on Flickerstrip. In the next section, we'll learn how to upload the pattern, but for now, suffice to say that Flickerstrip will want the pattern as a string of bytes ordered RGB. To make the next step easier, we'll assemble this as a Python list

pixels = [];
for i in range(len(timeSlots)):
    binarySlot = format(currentTime[i],"0"+str(timeSlots[i])+"b"); #Format the value for this slot as a binary string with the length from timeSlots

    if (i != 0): pixels += [255,0,0]; #We'll start with a green pixel to make the clock more readable except for the first slot

    for b in binarySlot: #Now we'll iterate through the binary representation character by character and output a "byte" array for the pixels
        if (b == "1"):
            pixels += [255,255,255]; # Full white for the 1s
        else:
            pixels += [0,0,0]; # Off for the 0s

There's not a lot going on here, all we're doing is expanding our binary representation into 3-byte pixel values. You should take some time to monkey with this piece of code and personalize your clock. Perhaps you want blue pixels to be the 1s and red pixels to be the 0s. Use an online RGB color picker to help you select colors.

Uploading to Flickerstrip

Now, we're ready to upload the frame to Flickerstrip. To make things simple, we're going to hard-code our Flickerstrip's IP address. To find the IP address of your Flickerstrip, tap on a visible Flickerstrip in the app and select "info." This will give you a bunch of information on your strip, but all we'll need for now is the IP address.

Note: Depending on your network topology, this IP address may change periodically. You can, however, configure a static IP address for your Flickerstrip which will prevent this from happening. Exactly how to do this will depend on your router.

Now we'll use the Python requests module (you may need to install this with pip install requests) to upload the frame to our strip, but first we have to put it in the right format. We'll use the /pattern/create endpoint to upload our pattern which takes a few URL parameters: name, fps, frames, pixels, and preview. In addition, the body of the request will contain the binary representation of our pattern. Fortunately for us, converting this array to binary is fairly easy in Python:

binaryString = ''.join([chr(item) for item in pixels])

Now to upload the pattern to Flickerstrip. Fortunately, the requests library makes this a breeze:

import requests

flickerstripIp = "http://192.168.1.1";
url = "%s/pattern/create?name=Clock&fps=1&frames=1&pixels=%d&preview" % (flickerstripIp,len(pixels)/3);
requests.post(url,data=binaryString);

Notice that we're setting the pixels GET parameter based on the number of LEDs that we're addressing. Since each pixel is three bytes (RGB), we divide the length by three.

Putting it all together

The finishing touches are having the script approximately once per second. We're doing this in a fairly crude way and just delaying for a second after each upload which means that we're more likely than not to occasionally skip a frame because the POST request will take a non-zero amount of time. I'll leave it as an exercise for the reader to make this code a bit more precise

#!/usr/bin/env python

from datetime import datetime
import requests
import time

flickerstripIp = "http://192.168.1.1"; #Adjust this to the correct IP address of your Flickerstrip

timeSlots = [5,6,6]; #This represents the number of binary digits we want to allocate to each segment hours. minutes and seconds respectively

while True:
    now = datetime.now();
    currentTime = [now.hour,now.minute,now.second]; #This array is parallel to timeSlots and holds the current time in hours, minutes, and seconds
    pixels = [];
    for i in range(len(timeSlots)):
        binarySlot = format(currentTime[i],"0"+str(timeSlots[i])+"b"); #Format the value for this slot as a binary string with the length from timeSlots

        if (i != 0): pixels += [255,0,0]; #We'll start with a green pixel to make the clock more readable except for the first slot

        for b in binarySlot: #Now we'll iterate through the binary representation character by character and output a "byte" array for the pixels
            if (b == "1"):
                pixels += [255,255,255]; # Full white for the 1s
            else:
                pixels += [0,0,0]; # Off for the 0s

    binaryString = ''.join([chr(item) for item in pixels]) #Convert the pixel array into a binary string

    #POST the pixeldata to the strip, note that we're using the preview option which prevents the pattern from being saved permanently
    url = "%s/pattern/create?name=Clock&fps=1&frames=1&pixels=%d&preview" % (flickerstripIp,len(pixels));
    requests.post(url,data=binaryString);

    time.sleep(1) #Delay a second before running the loop again, this could be improved by waiting until the second rolls over

Taking it further

This is just about the simplest possible implementation of a binary clock running on Flickerstrip and there are a number of improvements that can be made. One of the big things with this example is that we're uploading only a single frame at a time. That just happens to be the most conveniet and straightforward way to get the clock that we're looking for, but you can programmatically upload any pattern you want using the same endpoint. For multiple frames, simply change the GET parameter accordingly and append the frames one after another in the body of the request. Don't forget that you can adjust the FPS as well using the GET parameter. In our clock example, this could be used to fade the seconds or perform an animation when a new hour rolls around.

For something a bit more simple, you may just want to customize the colors a bit. The opportunity for this is in the lines that manipulate the "pixels" array. Perhaps you want to color the pixels in the hour, minute, and second differently? Perhaps you want to leave more space between the "digits" or highlight pixels that have recently changed. This is the place to do it, you can pretty much make any customization you want here.

Another nice enhancement that you could make to this is to make it so that only Flickerstrips that are displaying a pattern with a certain name will be interacted with. To do this, you'll want to use the /status endpoint which will return a JSON object with information about the strip. Among the info you get back is the selectedPattern and the patterns array. Between the two of these, you can figure out the name of the currently running pattern and run a test to see if the name matches. Once you've coded this up, you'll need to make a dummy pattern called something like "Clock" that will sit on your Flickerstrip and act as a placeholder to activate the external control.

Note: You may also need to check for a selectedPattern value of -1 which means that the Flickerstrip is currently previewing a pattern. Even more challenging still would be coming up with a mechanism that will still allow you to preview patterns on your strip without them being instantly clobbered. I'd do this by turning the clock loop on when it sees that the strip is running a pattern named "Clock" and off when it sees the Flickerstrip running a pattern by any other name (except the preview pattern).

One final enhancement would be to discover the Flickerstrip automatically, regardless of the IP address. Flickerstrip uses SSDP announce it's presence. You can use this little Python snippet to discover all Flickerstrips on your current network. To completely polish this feature, it's very possible to have the code automatically discover and manage multiple Flickerstrips, running the clock on any of them that are available. (or better yet, combined with the above and only Flickerstrips that are displaying a pattern called "Clock")

Rewards

The first person who implements all of the bolded features above and submits a pull request to the BinaryClock repository will win a coupon code for 50% off a Flickerstrip Starter Kit. (see README.md for details)