adafruit/Adafruit_CircuitPython_FancyLED

Using with RGBW Devices

Closed this issue · 4 comments

I searched around and did not find a solution for this, so I am posting this here.
If you want to use this library with RGBW LED's, you need to send an extra byte of data to your strip of RGBW's. The library, rightfully, deals with RGB so the examples show writing color.pack() to a pixel. This only writes 24 bits however, and with RGBW, you need 32, with the last 8 bits related to the white LED's.
There are more elegant ways of doing this I am sure, but a single line solution is...

color_packed=color.pack()
pixels[i] = ((color_packed & 0xff0000) >> 16, (color_packed & 0xff00) >> 8, color_packed & 0xff, white) 
# Mask and shift individual integers out of packed integer value.

'white' can be anything between 0 and 255 obviously. The 24 bit integer is masked then shifted back to an 8 bit integer to represent each color. The equivalent of pixels[x]=(r,g,b,w).

Have fun! Thanks

There seems to be a bug in the handling of single-integer values for RGBW NeoPixels in CircuitPython and the Adafruit_CircuitPython_Pixelbuf and Adafruit_CircuitPython_Pypixelbuf libraries. Not actually here.

Setting an RGBW pixel with a single integer should be possible (unsigned 32-bit value with high 8 bits representing white), but this isn’t handled there; RGB are set and white is stripped out, i.e. this doesn’t work:
pixels[i] = (white << 24) | color_packed
Setting RGBW pixel values is only possible using a 4-element tuple, as in your example. The color.pack() and then immediately unpacking into a tuple really shouldn’t be necessary and is weird and uncomfortable.

FancyLED is RGB-only anyway as you’ve noted, but it does at least work with RGBW pixels (simply ignoring the W element). Seems to me that adding those upper bits should be allowed in user code, and I’ll open an issue in the main repo, referencing this one.

In the meantime, pack and unpack-to-tuple as you’re doing. Sorry.

Huh! So I learned that in CircuitPython, packed 32-bit int colors don’t always work with RGBW the way one might expect…that this is on-purpose and by design, and the preferred way to address RGBW there is to always use tuples.

So, at some point, I’ll probably provide an alternative to FancyLED’s pack() that instead returns an integer 3-tuple, and one can insert those same elements into a new 4-tuple to add in white. In the meantime though, that means pack()-and-extract is the correct approach for now, so keep doing what you’re doing.

This should get rolled into the next CircuitPython bundle, so tomorrow (unless you want to download the .py version here and try that).

The pack() function now accepts an optional argument that allows inserting the W element (it then returns a 4-tuple, which can be passed straight to the NeoPixel setter, no need to unpack/tuple-ify/etc.).

Here’s a simple example, an RGB cycle with a W ramp overlaid. Note that if you’re using the gamma_adjust() function on an RGB color, you’ll probably want to do the same on your W value before packing it in. It’s a little weird, but this was a quick compromise vs. making a whole new RGBW class (maybe something for later, if this combination gets more traction out there, but it’s pretty esoteric at the moment).

import board
import neopixel
import adafruit_fancyled.adafruit_fancyled as fancy

num_leds = 30

# Declare a 6-element RGB rainbow palette
palette = [
    fancy.CRGB(1.0, 0.0, 0.0),  # Red
    fancy.CRGB(0.5, 0.5, 0.0),  # Yellow
    fancy.CRGB(0.0, 1.0, 0.0),  # Green
    fancy.CRGB(0.0, 0.5, 0.5),  # Cyan
    fancy.CRGB(0.0, 0.0, 1.0),  # Blue
    fancy.CRGB(0.5, 0.0, 0.5)]  # Magenta

# Declare an RGBW NeoPixel object on pin GP2 with num_leds pixels, no auto-write.
# Set brightness to max because we'll be using FancyLED's brightness control.
pixels = neopixel.NeoPixel(board.GP2, num_leds, brightness=1.0, auto_write=False, pixel_order=neopixel.GRBW)

offset = 0  # Positional offset into color palette to get it to 'spin'

while True:
    for i in range(num_leds):
        # Load each pixel's color from the palette using an offset, run it
        # through the gamma function, pack RGB value and assign to pixel.
        color = fancy.palette_lookup(palette, offset + i / num_leds)
        color = fancy.gamma_adjust(color)
        # Insert a gamma-corrected W value at the end before assignment
        pixels[i] = color.pack(fancy.gamma_adjust(i / (num_leds - 1)))
    pixels.show()

    offset += 0.02  # Bigger number = faster spin
    print(offset)