Adafruit neopixels provide an easy to use addressable RGB LED platform for the maker. I make use of their 8x pixel stick in one of my projects to provide a visible status of various checks on a rasbperry pi. However, there's a catch..
The neopixels themselves and the Python libraries that interface with them have no persistence, nor can they be read. When you invoke the NeoPixel library's show() function, it will display the given state for all pixels and it is up to the programmer to know that state and set it accordingly. So, in this image, the third pixel is "off" right now and if I want to change only that pixel, I need to also set green for pixels 1 and 2, and blue for pixel 8, before running show() or else those 3 will disappear when the third pixel lights up. We need a way to store and recall the current state of all neopixels when changing one.
As with all aspects of programming there are a dozen ways to do any task, and I'm sure there are better ways. The method I chose for this case was to use Redis to store state.
Redis is described as an "in-memory data structure store, used as a database, cache and message broker". In our case we will use it as a very basic cache to store the state of the pixels. Since I am using this to show various states of the running system in the raspberry pi, there is no need for persistence beyond reboots, so the "in-memory" aspect is no problem.
To make this happen, redis must be installed and running
sudo apt install redis-server; sudo systemctl enable redis-server; sudo systemctl start redis-server
In addition the redis python library must be installed and imported into your script. As part of the setup I also grab the current state of neopixels from redis if they exist, and setup some colors to match what the python library expects for convenience (some of these are tweaked to keep the brightness to my liking)
import redis, board, neopixel
PIXEL_COUNT = 8
pixels = neopixel.NeoPixel(board.D18, PIXEL_COUNT, auto_write=False)
r = redis.Redis(db=2, decode_responses=True)
oldlights = r.hgetall('neopixels')
newlights = r.hgetall('neopixels')
colors = {
'RED': 0x030000,
'GREEN': 0x000300,
'BLUE': 0x000003,
'YELLOW': 0x030300,
'CYAN': 0x000303,
'MAGENTA': 0x030003,
'OFF': 0x000000,
}
The rest is pretty simple, a couple of convenience functions to set a specific pixel, set all pixels, and then the magic in the saving of them (if and only if the new settings differ from what is current, to avoid unnecessary blinks with pixel refreshing)
def set_light(lightid, color='OFF'):
newlights[str(lightid)] = color
def fill(color='OFF'):
for i in range(PIXEL_COUNT):
set_light(i, color)
def save():
global oldlights, newlights
if oldlights != newlights:
for lightid, color in newlights.items():
pixels[int(lightid)] = colors[color]
r.hmset('neopixels', newlights)
pixels.show()
oldlights = r.hgetall('neopixels')
With this in play as a lights library, setting one pixel while maintaining the rest would be done like so:
lights.set_light(2, "RED")
lights.save()
On boot, I have a simple function to fill all neopixels green, sleep for a second, and clear them out. This also ensures that the neopixels, powered externally, are not showing a status that itself persists beyond a reboot.
Inspired by @themarkymark's recent post on the circular neopixel board from adafruit.