USB pipe error, both from Python and Ruby implementations

I’ve see similar reports of this error - the latest (?) from 2018 - but the solutions mentioned aren’t working for me, hence the question here again.

I’m integrating with a BlinkStick Strip from a Python script (on macOS), using the blinkstick package (and Python’s usblib library). Sometimes very quick, sometimes after a couple of hours, I encounter an USB error: “[Errno 32] Pipe error”.

This time it happens from Python, but I’ve seen the same problem when I was using a Ruby script with the blinkstick gem (and Ruby’s libusb library). Whether the process runs in foreground or background doesn’t seem to matter, and I’m only sending commands to the BlinkStick from one process.

The trace output of the error is here:

  File "/usr/local/lib/python3.9/site-packages/blinkstick/blinkstick.py", line 249, in _usb_ctrl_transfer
    return self.device.ctrl_transfer(bmRequestType, bRequest, wValue, wIndex, data_or_wLength)
  File "/usr/local/lib/python3.9/site-packages/usb/core.py", line 1072, in ctrl_transfer
    ret = self._ctx.backend.ctrl_transfer(
  File "/usr/local/lib/python3.9/site-packages/usb/backend/libusb1.py", line 893, in ctrl_transfer
    ret = _check(self.lib.libusb_control_transfer(
  File "/usr/local/lib/python3.9/site-packages/usb/backend/libusb1.py", line 604, in _check
    raise USBError(_strerror(ret), ret, _libusb_errno[ret])
usb.core.USBError: [Errno 32] Pipe error

As I said, sometimes the error occurs quickly after running. Sometimes the error only happens after an hour or so. It’s hard to replicate or trigger.

Oh, and I’m making sure to not swamp the BlinkStick with commands. I’ve added a time.sleep(0.1) wait before every interaction with the BlinkStick.


For reference, here’s the traceback of the Ruby variant:

	2: from /Users/jschulen/Code/iot/blinkodoro/blinkstick.rb:50:in `color='
	1: from /Users/jschulen/.gem/ruby/2.7.2/gems/libusb-0.6.4/lib/libusb/dev_handle.rb:525:in `control_transfer'
/Users/jschulen/.gem/ruby/2.7.2/gems/libusb-0.6.4/lib/libusb/dev_handle.rb:549:in `submit_transfer': error TRANSFER_STALL (LIBUSB::ERROR_PIPE)

Do you have some sample code that would help me reproduce this error or maybe part of the code you are using to control BlinkStick? What colors do you light up the BlinkStick?

Here’s my complete implementation for that Blinkodoro proof-of-concept in Python

from blinkstick import blinkstick
import psutil
import datetime
import time
import math
import colorsys

NR_LEDS = 8
POMODORO_LENGTH = 30
INTENSITY = 0.05

def led_data(minutes, seconds):
  data = []
  if 0 <= minutes < 25:
    # Add blue pixels to show current Pomodoro.
    for led in range(NR_LEDS):
      if led < math.floor(minutes / 5):
        data.append([240, 100])
      elif led == math.floor(minutes / 5):  
        data.append([240, (minutes % 5 * 60 + seconds) / (60*5/100)])
      else:
        data.append([0, 0])
  elif 25 <= minutes < 30:
    for led in range(NR_LEDS):
      # Start with three LEDs off.
      if led <= 2:
        data.append([0, 0])
      # Add red pixels to show break.
      elif 3 <= led < math.floor(minutes - 25 + 3):
        data.append([0, 100]) 
      elif led == math.floor(minutes - 25 + 3):
        data.append([0, seconds / 60 * 100])
      else:
        data.append([0, 0])
  return data

def set_leds(led_data):
  i = 0
  for led in led_data:
    hue = led[0]
    value = led[1]
    rgb = colorsys.hsv_to_rgb(hue / 360.0, 1, value / 100 * INTENSITY)
    time.sleep(0.1)
    bstick.set_color(0, i, rgb[0] * 255, rgb[1] * 255, rgb[2] * 255)
    i += 1

# Main loop
if __name__ == '__main__':
  # First BlinkStick Strip.
  time.sleep(0.1)
  bstick = blinkstick.find_first()
  if bstick is None or bstick.get_variant() != bstick.BLINKSTICK_STRIP:
    print("No BlinkStick Strip found...")
    exit()
  else:
    print("Blinkodoro started, showing process in current Pomodoro or break.")

  minutes = 0
  seconds = 0

  # Go into a forever loop.
  while True:
    # Get current time: just the minute mark in current half hour.
    minutes = datetime.datetime.now().minute  % POMODORO_LENGTH
    seconds = datetime.datetime.now().second

    if minutes == 0:
      time.sleep(10)
    else:
      # Determine LED data for current time.
      data = led_data(minutes, seconds)
      # Light LEDs according to LED data.
      set_leds(data)
      time.sleep(5)

Here’s a similar one in Ruby, exhibiting the similar error:

require 'yaml'
require 'color'
require './blinkstick'

HOUR = 60*60  # hour in seconds

# Color (gradient between two) used for LED color.
COLORS = [240, 359]  # hue for blue is 240 degrees, for red 359 degrees.

# Ranges where blinkstick LED goes on.
LIGHT_ON = [ (25*60)..(29*60+29),
             (55*60)..(59*60+29) ]
# Ranges where blinkstick LED blinks.
BLINK = [ (29*60+30)..(29*60+59),
          (59*60+30)..(59*60+59) ]

def map(a, b, s)
  v = s.clamp(a.min, a.max)
  af, al, bf, bl = a.first, a.last, b.first, b.last
  bf + (v - af)*(bl - bf)/(al - af)
end

# Turns on the LED, returning the necessary sleep time.
def turn_on(blinkstick, remaining)
  puts "#{Time.now} turn_on"
  $status = :on

  # Brightness is function of remaining seconds: 300-0 -> 1-10
  brightness = map([300,0], [1,10], remaining) 
  # Hue is for color between blue and red.
  hue = map([300,0], COLORS, remaining)

  # Create color, using hue and brightness.
  color = Color::HSL.new(hue, 100, brightness) 
  color.to_rgb

  # Set LED on BlinkStick to color.
  sleep 0.1
  blinkstick.color = color.to_rgb

  # Sleep for two more seconds.
  2
end

# Blinks the LED, returning the necessary sleep time.
def blink(blinkstick)
  puts "#{Time.now} blink"
  $status = :blink

  sleep 0.1
  blinkstick.off

  # Set color, using hue and brightness.
  color = Color::HSL.new(COLORS.last, 100, 10) 
  color.to_rgb

  5.times do
    # Set LED on BlinkStick to color, wait a while.
    sleep 0.4
    blinkstick.color = color.to_rgb

    # Turn it off.
    sleep 0.4
    blinkstick.off
  end

  # Limited additional sleep time.
  1
end

# Turns off the LED, returning the proper sleep time.
def turn_off(blinkstick)
  puts "#{Time.now} turn_off"
  $status = :off

  sleep 0.5
  blinkstick.off

  # It's fine to sleep for five more seconds.
  5 
end

# Main script
$status = :off
$blinkstick = BlinkStick::find_all().first

# Make sure it's turned off at the start.
turn_off($blinkstick)

while true do
  now = Time.now.to_i
  seconds_in_hour = now % HOUR
  sleep_for_seconds = 0

  begin
    if LIGHT_ON.any? do |range| range.include?(seconds_in_hour) end
      sleep_for_seconds = turn_on($blinkstick, HOUR - seconds_in_hour)
    elsif BLINK.any? do |range| range.include?(seconds_in_hour) end
      sleep_for_seconds = blink($blinkstick)
    else
      sleep_for_seconds = turn_off($blinkstick)
    end
  rescue LIBUSB::ERROR_NO_DEVICE
    STDERR.puts "Connection with BlinkStick lost... trying to connect again, just once."
    $blinkstick = find_blinkstick($serial)
  rescue LIBUSB::ERROR_INTERRUPTED
    STDERR.puts "Transfer interrupted. Trying to connect again, just once."
    $blinkstick = find_blinkstick($serial)
  end

  sleep sleep_for_seconds
end

Thanks for the code. If you unplug it and plug it back in does it work as expected?

Yes, and even without unplugging. Restarting the script works as well… for some time, until the error returns after 30 seconds, or 17 minutes, or two hours, or longer. Can’t predict when the error shows up again.

I’m not saying the underlying error is in the Blinkstick Python package or the Ruby gem - I simply don’t know where (or why, and when) the error occurs.

Might be libusb error. I will see if I can reproduce this.

I thought about that too… Originally, I started with Ruby, until I encountered that problem… only to discover that in Python I had the same (or similar) problem :smiley: As I said, I don’t have a clue how to trigger the error.

So, if it is in libusb (Ruby), then likely also in pyusb or libusb (Python). With that, perhaps the error is in the BlinkStick library / gem.

If there’s any trace that I need to set up, please mention so (and instructions how to do so). I’m on macOS Catalina.

I’ve simplified the script (in Python), stripping it from the non-essentials for debugging this one. Hopefully the error is triggered by this one as well - in relation to communication to the BlinkStick Strip nothing changed from the previously posted code.

from blinkstick import blinkstick
import datetime
import time
import math
import colorsys
import random

NR_LEDS = 8
INTENSITY = 0.1
SLEEP = 0.1

def led_data(seconds):
  data = []
  for led in range(NR_LEDS):
    hue = random.randint(seconds * 5, seconds * 6)
    data.append(hue)
  return data

def set_leds(led_data):
  i = 0
  for hue in led_data:
    rgb = colorsys.hsv_to_rgb(hue / 360.0, 1, 1 * INTENSITY)
    time.sleep(SLEEP)
    bstick.set_color(0, i, rgb[0] * 255, rgb[1] * 255, rgb[2] * 255)
    i += 1

# Main loop
if __name__ == '__main__':
  # First BlinkStick Strip.
  time.sleep(SLEEP)
  bstick = blinkstick.find_first()
  if bstick is None or bstick.get_variant() != bstick.BLINKSTICK_STRIP:
    print("No BlinkStick Strip found...")
    exit()

  # Go into a forever loop.
  while True:
    seconds = datetime.datetime.now().second

    # Determine LED data for current time.
    data = led_data(seconds)
    # Light LEDs according to LED data.
    set_leds(data)
    # Pause a little.
    time.sleep(SLEEP)

Oh, and coming back to an earlier question (I just read): the error occurs independently from the colors used – I think. The problem is manifest when using just plain blue and red colors, or when using random hues.