NeoPixel API: Part 2 - Make a Wish!

NeoPixel API: Part 2 - Make a Wish!

October 27, 2020 by David Ewing

Part two of a series on API design with neopixels.

In the previous post I discussed API design, and the notion of creating a magical new module that goes beyond the basics of MicroPython's built-in neopixel support. Now it's time to dream up a new set of awesome functions that takes neopixels to the next level!

Make A Wish!

I wish to work with groups of neopixels, not just individual ones. So for starters, how about a function that lets me set a range of pixels to a given color?

neo_range(np, color, start, end)

The start and end values can act just like Python's built-in range() function, so this would light up pixel indexes np[start] through np[end - 1]. And you can call neo_range() multiple times to fill different sections of a big neopixel strip.

Often I'm wanting to fill all the pixels with a given color, and the above function would work for that. But it would be nicer if there was a dedicated function to fill all the pixels with a color so I wouldn't have to keep specifying start and end.

neo_fill(np, color)   # Set all pixels to 'color'

Nice! Okay, those are useful, simple, basic and well... kinda boring functions. But I'm dreaming bigger! Let's spice things up a little with a function that makes the pixels "sparkle". Imagine little flickering flashes of color dancing randomly across your neopixel strip, leaving behind no trace of their existence except a delightful fading shimmer across your retina...

neo_sparkle(np, color, duration, count)

Choose the color of your sparks (bright white is nice), the duration for each, and the count of how many times to sparkle. Dazzling!

Hang on... say you do a neo_fill() and then a neo_sparkle() right after it. Do the sparkles erase the "background" color? No way! This is my dream, so I say no matter what's already there (the background) the sparkles leave no trace behind.

Okay, more API goodness. I love those "chase lights" where one or more pixels light up in sequence, and sweep across the whole set. How about a single function to make it happen?

neo_sweep(np, color, width, duration)

Choose the color and width (in pixels) of the moving block of light. The duration parameter controls the animation speed - how long each position is shown before moving to the next.

The importance of naming: When you're designing an API you may find yourself agonizing over the naming of functions and parameters. That's good! There is great power in names. Good names help users of the API form an accurate mental model of how it works. For example I first considered naming the duration parameter above "speed". But that might have been confusing since smaller values increase the speed. And the word "dwell" also got serious consideration. But even though its meaning would've been spot-on, a bit of online research into common word usage revealed that many users might not find it familiar in this context. Bottom line is that it's not easy. Have your thesaurus handy, and choose your words carefully!

Implementing the neopixel API, step 1

Now that you have a nice API defined, it's time to take off that Architect hat and put your Developer hat on. You get to write and test all those amazing API functions. This is your chance to make dreams come true!

I'll start with most basic function, neo_range(). This should loop over all the pixel indexes in the given [start, end) range, setting each one the specified color. I'm using the Python for loop and the built-in range() constructor to index across all the pixels starting with start and stopping just before end. Each time through the loop I set a pixel to the desired color. Note that color is required to be a 3-tuple of (R,G,B) values.

def neo_range(np, color, start, end):
    for i in range(start, end):
        np[i] = color

Test As You Go

As you implement your API always keep this bit of wisdom in mind: "If it's not tested, it's broken." Okay, maybe not always - but odds are good that new code you write won't function perfectly the first time around! You should test one small piece at a time, before assembling it all together into your masterpiece!

When you're writing a small Python module like this, a good approach is to put the test code right in the same file as your implementation. In the code below the API implementation is just a comment and 3 lines of code. The rest of the file is for testing!

# neoneopixel.py - A new NeoPixel module!
 
# Fill a range with color
def neo_range(np, color, start, end):
    for i in range(start, end):
        np[i] = color
 
#--- Test code for the above API ---
MY_STRIP_LEN = 30
np = NeoPixel(pin0, MY_STRIP_LEN)
np.clear()
 
# Fill the first 10 pixels with Red
neo_range(np, (20,0,0), 0, 10)
 
# Fill the last 10 pixels with Blue
neo_range(np, (0,0,20), 20, 30)

Are you ready to give it a try?

Make a new file in CodeSpace and name it neoneopixel.py. Using a proper Python filename with .py extension will trigger CodeSpace to persist the file as a module you can import later. Copy the above code into the text editor panel, and be sure to adjust the MY_STRIP_LEN to match your connected neopixel arrangement. Run the code and you should see those ranges light up in red and blue! Your API works!

Next Step... Your Custom Python import

In the next post, I'll show how to use Python's import statement to use this module from another program.