2

I am writing a Python class that will combine control of two different types of RGB LED "pixel" strands from a RPi. You can see all of my code here on Github. I will quote the relevant code here, but if you want to look at the everything, it's available there.

I'm running on a Raspberry Pi 2, if that matters.

When I run the tests at the end of superpixel.py with one strand of each type in the superset, I get this error:

$ sudo python superpixel.py
Traceback (most recent call last):
  File "superpixel.py", line 196, in <module>
    strand.begin()
  File "superpixel.py", line 63, in begin
    self.show()
  File "superpixel.py", line 68, in show
    strand.show()
  File "/home/pi/Lava-Lounge/tikinook/paleopixel.py", line 100, in show
    spidev.close()
IOError: [Errno 110] Connection timed out

Walking backwards, here is superpixel.py:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-

import time

import neopixel
import paleopixel

# SuperPixel
# Author:  Mark Boszko
#
# Combines both NeoPixels and PaleoPixels (WS2801) into a single addressable
# SuperPixel type. Also allows merging of separate pixel strands into one
# master strand or array.


# My LED strip configurations (for test):
NEOPIXEL_COUNT   = 244   # Number of NeoPixels in the strand
NEOPIXEL_PIN     = 18    # GPIO pin connected to the pixels (must support PWM!)
PALEOPIXEL_COUNT = 50    # Number of PaleoPixels in the strand

#####
# 
# SuperPixel - superset pixel strand class
# 
#####

def Color(red, green, blue):
    """Convert the provided red, green, blue color to a 24-bit color value.
    Each color component should be a value 0-255 where 0 is the lowest intensity
    and 255 is the highest intensity.
    """
    return ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | (blue & 0xFF)

class SuperPixel(object):
    def __init__(self, *strands):
        """Class to represent a superset of both neopixel and paleopixel strands

        strands - Variable argument list of sub-strands which should make up
                  the one SuperPixel strand. The sub-strands are added to the
                  super-strand in the order the arguments are listed.
        """
        self._strands = strands
        pixel_count = 0
        for strand in self._strands:
            pixel_count = pixel_count + strand.numPixels()

        # Create an array for all of the LED color data
        self._led_data = [0] * pixel_count

    def __del__(self):
        # Clean up memory used by the library when not needed anymore.
        if self._led_data is not None:
            self._led_data = None
        if self._strands is not None:
            self._strands = None

    def begin(self):
        """Initialize _led_data to zeroes and set up NeoPixels
        """
        for strand in self._strands:
            strand.begin()
        self.show()

    def show(self):
        """Update the display with the data from the LED buffer."""
        for strand in self._strands:
            strand.show()

    def setPixelColor(self, n, color):
        """Set LED at position n to the provided 24-bit color value (in RGB order).
        """
        if (n >= len(self._led_data)):
            return  # out of bounds; throw it away

        # SuperPixel internal representation:
        self._led_data[n] = color

        # Now also set it in the sub-strand
        pixel_offset = 0
        pixel_max    = 0
        for strand in self._strands:
        # TODO: Determine which strand this pixel is a part of, and set it.
            pixel_max = pixel_offset + strand.numPixels()
            if (pixel_offset <= n) and (n < pixel_max):
                pixel = n - pixel_offset
                strand.setPixelColor(pixel, color)
                break
            else:  # Must be in the next one
                pixel_offset = pixel_offset + strand.numPixels()

    def setPixelColorRGB(self, n, red, green, blue):
        """Set LED at position n to the provided red, green, and blue color.
        Each color component should be a value from 0 to 255 (where 0 is the
        lowest intensity and 255 is the highest intensity).
        """
        self.setPixelColor(n, Color(red, green, blue))

    def getPixels(self):
        """Return an object which allows access to the LED display data as if 
        it were a sequence of 24-bit RGB values.
        """
        return self._led_data

    def numPixels(self):
        """Return the number of pixels in the display."""
        return len(self._led_data)

    def getPixelColor(self, n):
        """Get the 24-bit RGB color value for the LED at position n."""
        return self._led_data[n]



#####
# 
# Test functions which animate LEDs in various ways.
# 
#####

def colorWipe(strip, color, wait_ms=50):
    """Wipe color across display a pixel at a time."""
    for i in range(strip.numPixels()):
        strip.setPixelColor(i, color)
        strip.show()
        time.sleep(wait_ms/1000.0)

def theaterChase(strip, color, wait_ms=50, iterations=10):
    """Movie theater marquee style chaser animation."""
    for j in range(iterations):
        for q in range(3):
            for i in range(0, strip.numPixels(), 3):
                strip.setPixelColor(i+q, color)
            strip.show()
            time.sleep(wait_ms/1000.0)
            for i in range(0, strip.numPixels(), 3):
                strip.setPixelColor(i+q, 0)

def wheel(pos):
    """Generate rainbow colors across 0-255 positions."""
    if pos < 85:
        return Color(pos * 3, 255 - pos * 3, 0)
    elif pos < 170:
        pos -= 85
        return Color(255 - pos * 3, 0, pos * 3)
    else:
        pos -= 170
        return Color(0, pos * 3, 255 - pos * 3)

def rainbow(strip, wait_ms=20, iterations=1):
    """Draw rainbow that fades across all pixels at once."""
    for j in range(256*iterations):
        for i in range(strip.numPixels()):
            strip.setPixelColor(i, wheel((i+j) & 255))
        strip.show()
        time.sleep(wait_ms/1000.0)

def rainbowCycle(strip, wait_ms=20, iterations=5):
    """Draw rainbow that uniformly distributes itself across all pixels."""
    for j in range(256*iterations):
        for i in range(strip.numPixels()):
            strip.setPixelColor(i, wheel(((i * 256 / strip.numPixels()) + j) & 255))
        strip.show()
        time.sleep(wait_ms/1000.0)

def theaterChaseRainbow(strip, wait_ms=50):
    """Rainbow movie theater marquee style chaser animation."""
    for j in range(256):
        for q in range(3):
            for i in range(0, strip.numPixels(), 3):
                strip.setPixelColor(i+q, wheel((i+j) % 255))
            strip.show()
            time.sleep(wait_ms/1000.0)
            for i in range(0, strip.numPixels(), 3):
                strip.setPixelColor(i+q, 0)



#####      
#      
# Let's test it out!
#
#####

# Main program logic follows:
if __name__ == '__main__':
    # Create pixel strands with appropriate configuration.
    strand1 = neopixel.Adafruit_NeoPixel(NEOPIXEL_COUNT, NEOPIXEL_PIN)
    strand2 = paleopixel.PaleoPixel(PALEOPIXEL_COUNT)

    # Combine them into one SuperPixel strand
    strand = SuperPixel(strand1, strand2)

    # Intialize the SuperPixel strand (must be called once, before other 
    # functions, if the SuperPixel strand contains any NeoPixels)
    strand.begin()

    print('Press Ctrl-C to quit.')
    while True:
        # Color wipe animations.
        colorWipe(strand, Color(255, 0, 0))  # Red wipe
        colorWipe(strand, Color(0, 255, 0))  # Blue wipe
        colorWipe(strand, Color(0, 0, 255))  # Green wipe
        # Theater chase animations.
        theaterChase(strand, Color(127, 127, 127))  # White theater chase
        theaterChase(strand, Color(127,   0,   0))  # Red theater chase
        theaterChase(strand, Color(  0,   0, 127))  # Blue theater chase
        # Rainbow animations.
        rainbow(strand)
        rainbowCycle(strand)
        theaterChaseRainbow(strand)

And here is the first half of paleopixel.py (excluding tests):

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
Adafruit NeoPixel library port to control the older WS2801 pixels from 
  Raspberry Pi hardware SPI. Includes Adafruit “strandtest”-style functions
  and performs a self-test if run as main.


Author: Mark Boszko

Raspberry Pi SPI driver code for WS2801 pixels based on Adafruit_LEDpixels.py
https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code

Python port of NeoPixel library based on the rpi_ws281x library port,
  by Tony DiCola and Jeremy Garff
https://github.com/jgarff/rpi_ws281x


Usage:

You'll need to translate 3.3V SPI logic levels from the Raspberry Pi to 5V.
Several options are possible, and I have a suggested circuit on my blog post
that describes this project in more detail:

http://stationinthemetro.com/2016/02/27/tiki-nook-build-part-2-raspberry-pi-led-control

- Connect the RPi's MOSI output to the SDI (Serial Data Input) on the WS2801s
- Connect the RPi SCLK clock output to CKI (ClocK Input) on the WS2801
- The RPi's 5V pins will probably not be enough to power a string of
    any significant length. Use an external power supply of appropriate amperage
- Be sure to connect the RPi ground to the LED strip's ground


License:

Licensed under The MIT License (MIT). Please see LICENSE.txt for full text
of the license.


Version History:

- 1.0.1 - 2016-02-29 - Changed license from CC-BY-4.0 to MIT, due to 
                       recommendation by Creative Commons not to apply their 
                       licenses to software. See CC's FAQ for details:
                       https://creativecommons.org/faq/#can-i-apply-a-creative-commons-license-to-software
- 1.0   - 2016-02-27 - Started development and complete rewrite, all in the 
                       same day!

"""


import RPi.GPIO as GPIO, time, os


# LED strip configuration:
LED_COUNT      = 50      # Number of LED pixels.

#####
# 
# PaleoPixel - NeoPixel library port for WS2801 control over RPi hardware SPI
# 
#####

def Color(red, green, blue):
    """Convert the provided red, green, blue color to a 24-bit color value.
    Each color component should be a value 0-255 where 0 is the lowest intensity
    and 255 is the highest intensity.
    """
    return ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | (blue & 0xFF)

class PaleoPixel(object):
    def __init__(self, num):
        """Class to represent a WS2801 LED display.

        num - number of pixels in the display.
        """
        # Create an array for the LED data
        self._led_data = [0] * num

    def __del__(self):
        # Clean up memory used by the library when not needed anymore.
        if self._led_data is not None:
            self._led_data = None

    def begin(self):
        """Initialize _led_data to zeroes.
        Not necessary, since we do this in __init__, but handy.
        """
        for i in range(len(self._led_data)):
            self._led_data[i] = 0
        self.show()

    def show(self):
        """Update the display with the data from the LED buffer."""
        spidev = file("/dev/spidev0.0", "w")
        for i in range(len(self._led_data)):
            spidev.write(chr((self._led_data[i]>>16) & 0xFF))
            spidev.write(chr((self._led_data[i]>>8) & 0xFF))
            spidev.write(chr(self._led_data[i] & 0xFF))
        spidev.close()
        time.sleep(0.002)

    def setPixelColor(self, n, color):
        """Set LED at position n to the provided 24-bit color value (in RGB order).
        """
        if (n >= len(self._led_data)):
            return
        self._led_data[n] = color

    def setPixelColorRGB(self, n, red, green, blue):
        """Set LED at position n to the provided red, green, and blue color.
        Each color component should be a value from 0 to 255 (where 0 is the
        lowest intensity and 255 is the highest intensity).
        """
        self.setPixelColor(n, Color(red, green, blue))

    def getPixels(self):
        """Return an object which allows access to the LED display data as if 
        it were a sequence of 24-bit RGB values.
        """
        return self._led_data

    def numPixels(self):
        """Return the number of pixels in the display."""
        return len(self._led_data)

    def getPixelColor(self, n):
        """Get the 24-bit RGB color value for the LED at position n."""
        return self._led_data[n]

I should note that paleopixel.py tests fine on its own, and if I test superpixel.py with one strand (of either type), it works fine. In fact, if I test with two strands of the same type, it also works fine. It's only with one strand of each type that it fails with IOError: [Errno 110] Connection timed out.

Is it possible to extend the timeout length for the spidev write? I admit I am not super familiar with SPI, but I tried to dig into the docs, and couldn't find anything. Or would there be a faster way to write out the bits, so that it doesn't time out? (Just making guesses at a solution.)

4
  • I didn't really read the code (I'm not a python user) but have you tried re-trying in a loop with a 1s gap a few times? Not that this will necessarily solve your problem, but it will narrow the nature of it down a bit.
    – goldilocks
    Mar 1, 2016 at 8:57
  • @goldilocks Which action(s) are you suggesting that I put in the loop? I'm not sure that I follow. Mar 1, 2016 at 18:34
  • There's an IO connection timeout; again I'm not a python user but this is passes through from a lower level (C) and you need to isolate 1) Exactly what command that is; 2) If gapping/retrying a bit helps. Like I said, it is not a solution, but it is a means of digging
    – goldilocks
    Mar 1, 2016 at 18:40
  • Sorry -- "digging" should have been "debugging" (I'm on a phone and editing the post seems impossible on the SE app).
    – goldilocks
    Mar 1, 2016 at 18:42

1 Answer 1

1

A friend of mine suggested I try to remove a layer of abstraction (the Python file handlers), and do this with PaleoPixel.show()

class PaleoPixel(object):
    [...]

    def show(self):
        """Update the display with the data from the LED buffer."""
        # Trying to use the os access directly, instead of going through the
        # Python file code. Suggestion by Mike Ash.
        spidev = os.open('/dev/spidev0.0', os.O_WRONLY)
        for i in range(len(self._led_data)):
            os.write(spidev, chr((self._led_data[i]>>16) & 0xFF))
            os.write(spidev, chr((self._led_data[i]>>8) & 0xFF))
            os.write(spidev, chr(self._led_data[i] & 0xFF))
        os.close(spidev)
        time.sleep(0.002)
        # This works, for the most part, but there is some flickering
        # in the PaleoPixel strand when used inside a SuperPixel set

While it is not as perfectly smooth as I would like it to be (I may also try it using python-spidev to see if that improves the speed), it does at least answer my original question, and gets rid of the time-out error.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.