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
#!/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:
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")
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)