Part four of a series on API design with neopixels.
For this final post in the series it's time to tackle the most ambitious item on my wish list:
neo_sweep(). The goal is to move a group of pixels across the strip, preserving the background color as you go.
A Clean Sweep
Where to start? In a case like this it's often helpful to simplify. Start with a simpler specific case, then move to the more general case. I'm going to start with a one-pixel version:
def sweep1(np, color, duration): count = len(np) for i in range(count): bkgnd = np[i] np[i] = color np.show() sleep(duration) np[i] = bkgnd np.show()
Look familiar? This is very similar to my
neo_sparkle() code above! It's actually a little simpler, since there's no random number involved - I can just use the loop index
i to sequence the pixels one at a time.
Now for the real deal. I need to add a width parameter, and move more than one pixel across the strip. My first thought is maybe I need to set multiple pixels each time through the loop. So if
width == 3 I'd need to set 3 pixels each time, right? Hmmm... no!
When facing problems like this, I go to the whiteboard! I highly recommend you sketch out your plans on paper, whiteboard, or whatever's comfortable for you to visualize what's going on when it gets complicated.
Here I'm visualizing an 8-pixel strip, with a colorful Blue-Red-Yellow repeating background already set up. I want to sweep a 3-pixel wide Green group across the strip. To start with, imagine the Green group is just "off screen" to the left of the strip. I'm going to save the background colors in a list called bkgnd, which starts out empty.
At the start of my for loop,
i = 0. That's the head of my comet, so I need to write Green to that position:
np = color. But before I do, I must save the background color. I push colors into the bkgnd list from the left, and pop them off the right-side later when needed. Each time through the loop I check the erase position, which is width pixels behind the head:
erase = i - width. That means erase is
-3 at first. No point in erasing "negative pixels" since they don't really exist, so there's nothing to erase until
erase >= 0.
Here are the key lines of code inside my loop to do what's described above:
# Each time through the loop: erase = i - width if erase >= 0: np[erase] = bkgnd.pop() # Pop color from list if i < num_pixels: bkgnd.insert(0, np[i]) # Push color into list np[i] = color
These diagrams show how the variables change as
i counts up, each time through the for loop:
The sequence continues as above, until
i goes off the end of the neopixel strip. I have to keep shifting
i more than 8 times in order to completely clear the Green group from the strip. The loop should continue until i = 8 + 3 in this case:
count = num_pixels + width.
i is past the end of the strip, you no longer need to set Green pixels - just erase the background. So setting new pixels and saving the background only happens
if i < num_pixels
Here are the last 3 positions in my example as
i goes past the end:
Here's the complete code for my new
def neo_sweep(np, color, width, duration): bkgnd =  num_pixels = len(np) for i in range(num_pixels + width): erase = i - width if erase >= 0: np[erase] = bkgnd.pop() if i < num_pixels: bkgnd.insert(0, np[i]) np[i] = color np.show() sleep(duration)
Wrapping it Up
You're probably already thinking of a dozen more really cool neopixel API functions to add to this library module. Go for it! As you've seen, adding functions is pretty easy. And once you have the basics down, it's much easier to build higher levels of capability on top of what you've done.
Remix Challenge - User Feedback
When you create an API and share it with other coders, those folks are users of your API. Naturally they'll love it! But they'll also have requests, like the following:
neo_sweep() function is cool, but it's too low-level. My code needs to work with different length pixel strips, so I don't like always having to calculate width. I'd like to specify a percentage of the total length instead. Also rather than duration, I'd like to specify a speed in pixels per second instead. Can you add that? Maybe call it
neo_chase(np, color, width_percent, speed_pps)
Over to you, dear reader. Care to add this capability to the API above?
Below is the complete code to the neoneopixel module described so far. In a future blog post I'll show you how to turn this into a Python class, which adds a bit more convenience in usage and is consistent with how the built-in neopixel module works in MicroPython.
Until next time, Happy Coding!
from microbit import * import random from neopixel import NeoPixel def neo_range(np, color, start, end): for i in range(start, end): np[i] = color def neo_fill(np, color): neo_range(np, color, 0, len(np)) def neo_sparkle(np, color, duration, count): for i in range(count): n = random.randrange(len(np)) bkgnd = np[n] np[n] = color np.show() sleep(duration) np[n] = bkgnd np.show() def neo_sweep(np, color, width, duration): bkgnd =  num_pixels = len(np) for i in range(num_pixels + width): erase = i - width if erase >= 0: np[erase] = bkgnd.pop() if i < num_pixels: bkgnd.insert(0, np[i]) np[i] = color np.show() sleep(duration) if __name__ == "__main__": #--- Test code for the above API --- MY_STRIP_LEN = 30 np = NeoPixel(pin0, MY_STRIP_LEN) np.clear() neo_range( np,(20,0,0), 0, MY_STRIP_LEN // 2) neo_range( np, (0,0,20), MY_STRIP_LEN // 2, MY_STRIP_LEN) neo_sparkle(np, (200,200,200), 100, 30) neo_sweep(np, (0,20,0), 3, 100) np.show()