NeoPixel API: Part 3 - a Python Library Module

Part three of a series on API design with neopixels.

In the previous post I defined a magical new API for high-level control of neopixels in my Python code. I wrote the first function neo_range(), and tested it successfully. Woot! But what's really cool is this code can be used with import just like built-in Python modules. It's time to give that a try!

Your Shiny New Python Module

After you've run this code at least once, you can now import it from another Python program on the micro:bit. Create a new file in CodeSpace (call it whatever you like) and try the following test code:

from neopixel import NeoPixel
from neoneopixel import neo_range

np = NeoPixel(pin0, MY_STRIP_LEN)

# Fill the first 2 pixels with Green
neo_range(np, (0,20,0), 0, 2)

Woohoo! You can import your custom module and use the neo_range() API

But there's a problem.

When you run this code, you'll notice that the red and blue ranges light up when you import neoneopixel. Whoa! The test code inside your module runs when you import it! Yep, Python runs that top-level code during import.

How can you make it so that your "test code" runs only if neoneopixel is run as the main program, and not when it's imported as a module?

Dunder to the Rescue!

Python features some built-in variables you can use to determine how your code was run. To avoid "name collisions" with common variables you use in your code, some of these built-ins are surrounded by double-underscores (or "dunders" in Pythonista). For example, the global variable __name__ will be set to the string value "__main__" within the file executed as the main program.

Here's my fledgling module again, this time with an if statement to ensure the test code doesn't run when the module is used as an import

# 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

if __name__ == "__main__":
    #--- Test code for the above API ---
    MY_STRIP_LEN = 30
    np = NeoPixel(pin0, MY_STRIP_LEN)

    # 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)

Much nicer! Now there's a place for code that executes only when the file is run "standalone", and you can safely import this as a module without accidentally running the tests.

Implementing the neoneopixel API, continued

The next function on my wish-list is neo_fill(). Now that I've written neo_range() this is a piece of cake!

def neo_fill(np, color):
    neo_range(np, color, 0, len(np))

The trick here is to use the Python built-in len() function, which works on neopixels just like it does on lists and strings. In this case it returns the total number of pixels. After that, neo_range() does the heavy lifting!

Now it's time to add some motion to the party, with the neo_sparkle() function. I create each "spark" by first rolling the dice with Python's built-in random module to select an index between 0 and len(np) which will be the location for the flash of color. I then read the current color of that pixel and save it in a variable bkgnd so it can be restored later. After that, it's just a matter of writing the given color at the selected index, delaying for the given duration, and restoring the bkgnd color. Finally, one last call to show() ensures no trace of sparkle is left behind.

from time import sleep
from random import randrange

def neo_sparkle(np, color, duration, count):
    for i in range(count):
        n = randrange(len(np))  # Roll the dice
        bkgnd = np[n]                  # Save the background color
        np[n] = color                  # Flash with new color
        np[n] = bkgnd                  # Restore background color
    np.show()                          # Leave no trace

Notice that the for loop is just used to repeat the "spark" the given number of times. The variable i is not used inside the loop.

Next step... The Amazing neo_sweep() function

The next post will wrap up this series in style, with dazzling chase lights!