Python 3 library timeout errors

Hi all, I’ve been using the python3 library from zranger:

and despite my minimal python background, I’ve blundered cursing and learning through a zillion inconsistent tab and space errors.

I’ve been running into a bit of a challenge getting a fade in / fade out function running without timeout errors. Perhaps I’m asking too much of the websocket API, but I was hoping for a way to have the current pattern simply fade out, then I suppose it could be set to a blank pattern in the background while ‘off’.
I have been doing the fade over 3 seconds, and I find that if I use just 10 steps (very choppy), there are no issues, but if I do 50 steps (smoother) there is usually a timeout error that is thrown.

My code:

#!/usr/bin/env python3
“”"
uses the pixelblaze library from, and based partially on the example scripy by JEM(ZRanger1)
Requires the pixelblaze, which also requires websocket-client
“”"
from pixelblaze import *
import sys

ARG_IP = sys.argv[1]
ARG_ACTION = sys.argv[2]

def listPB():
pbList = PixelblazeEnumerator()
print(“Testing: PixelblazeEnumerator object created – listening for Pixelblazes”)
time.sleep(2)
print("Available Pixelblazes: ", pbList.getPixelblazeList())

def setOasis():
pb = Pixelblaze(ARG_IP)
pb.stopSequencer() # make sure the sequencer isn’t running
pb.setActivePattern(‘Oasis’)
pb.waitForEmptyQueue(1000)
pb.setControl(‘aura’, color, False)
pb.close()

def setSound():
pb = Pixelblaze(ARG_IP)
pb.stopSequencer() # make sure the sequencer isn’t running
pb.setActivePattern(‘sound - pew-pew-pew!’)
pb.close()

def setBrightness(brightness):
pb = Pixelblaze(ARG_IP)
pb.setBrightness(brightness)
pb.close()

def fadeIn():
steps = 50
totalduration = 3 #number of seconds over which to fade in
brightness = 0
intervald = totalduration/steps
pb = Pixelblaze(ARG_IP)
pb.waitForEmptyQueue(1000)
for i in range(steps):
brightness = (i/steps)
pb.setBrightness(brightness)
print(brightness)
time.sleep(intervald)
time.sleep(1) # error handling, attempting to prevent timeout errors, and ensure final on/off setting correct
pb.setBrightness(1)
if pb.waitForEmptyQueue(2000):
print(‘complete’)
else:
print(‘timed out’)
pb.close()

def fadeOut():
steps = 50
totalduration = 3 #number of seconds over which to fade out
brightness = 1
intervald = totalduration/steps
pb = Pixelblaze(ARG_IP)
pb.waitForEmptyQueue(1000)
for i in range(steps):
brightness = 1-(i/steps)
pb.setBrightness(brightness)
print(brightness)
time.sleep(intervald)
time.sleep(1) # error handling, attempting to prevent timeout errors, and ensure final on/off setting correct
pb.setBrightness(0)
if pb.waitForEmptyQueue(2000):
print(‘complete’)
else:
print(‘timed out’)
pb.close()

def ListPatterns():
pb = Pixelblaze(ARG_IP)
pb.stopSequencer() # make sure the sequencer isn’t running
result = pb.getPatternList()
for key, value in result.items():
print(key, ’ : ', value)
time.sleep(1)
pb.waitForEmptyQueue(1000)
pb.close()

def ListVariables():
pb = Pixelblaze(ARG_IP)
pb.waitForEmptyQueue(1000)
print("Variables: ",pb.getVars())
pb.close()

def setColor(color):
pb = Pixelblaze(ARG_IP)
pb.waitForEmptyQueue(1000)
pb.setControl(‘aura’, color, False)

if ARG_ACTION == ‘fadein’:
fadeIn()
elif ARG_ACTION == ‘fadeout’:
fadeOut()
elif ARG_ACTION == ‘on’:
setBrightness(1)
elif ARG_ACTION == ‘off’:
setBrightness(0)
elif ARG_ACTION == ‘list’:
listPB()
elif ARG_ACTION == ‘brightness’:
newBrightness = int(sys.argv[3])
setBrightness(newBrightness)
elif ARG_ACTION == ‘oasis’:
setOasis()
elif ARG_ACTION == ‘sound’:
setSound()
elif ARG_ACTION == ‘pattern’:
pb = Pixelblaze(ARG_IP)
pb.stopSequencer() # make sure the sequencer isn’t running
print("Attempting to set pattern to: ", sys.argv[3])
pb.setActivePattern(‘sys.argv[3]’)
pb.close()
elif ARG_ACTION == ‘listvariables’:
ListVariables()
elif ARG_ACTION == ‘listpatterns’:
ListPatterns()
elif ARG_ACTION == ‘aura’:
setColor(float(sys.argv[3]))

Yes, I am aware there are probably a myriad of problems with the above script, and I know that the setting color (targeting Oasis for now, which uses aura as the variable) doesn’t work, neither does the setting pattern. Currently setting brightness is working, which was my main (initial) goal.

Errors thrown when I attempt to run : python3 PBScript.py 192.168.1.234 fadein

Traceback (most recent call last):
File "/home/openhabian/.local/lib/python3.7/site-packages/websocket/socket.py", line 113, in recv
bytes
= _recv()
File “/home/openhabian/.local/lib/python3.7/site-packages/websocket/_socket.py”, line 95, in _recv
return sock.recv(bufsize)
socket.timeout: timed out

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “PBScript.py”, line 100, in
fadeIn()
File “PBScript.py”, line 52, in fadeIn
if pb.waitForEmptyQueue(2000):
File “/var/lib/openhab2/pixelblaze.py”, line 170, in waitForEmptyQueue
result = self.ws.recv()
File “/home/openhabian/.local/lib/python3.7/site-packages/websocket/_core.py”, line 353, in recv
opcode, data = self.recv_data()
File “/home/openhabian/.local/lib/python3.7/site-packages/websocket/_core.py”, line 376, in recv_data
opcode, frame = self.recv_data_frame(control_frame)
File “/home/openhabian/.local/lib/python3.7/site-packages/websocket/_core.py”, line 395, in recv_data_frame
frame = self.recv_frame()
File “/home/openhabian/.local/lib/python3.7/site-packages/websocket/_core.py”, line 431, in recv_frame
return self.frame_buffer.recv_frame()
File “/home/openhabian/.local/lib/python3.7/site-packages/websocket/_abnf.py”, line 372, in recv_frame
self.recv_header()
File “/home/openhabian/.local/lib/python3.7/site-packages/websocket/_abnf.py”, line 320, in recv_header
header = self.recv_strict(2)
File "/home/openhabian/.local/lib/python3.7/site-packages/websocket/abnf.py", line 407, in recv_strict
bytes
= self.recv(min(16384, shortage))
File “/home/openhabian/.local/lib/python3.7/site-packages/websocket/_core.py”, line 514, in _recv
return recv(self.sock, bufsize)
File “/home/openhabian/.local/lib/python3.7/site-packages/websocket/_socket.py”, line 116, in recv
raise WebSocketTimeoutException(message)
websocket._exceptions.WebSocketTimeoutException: timed out

Any suggestions on ways to get a smoother timeout? Is this simply overloading the API, and I should expect to run into a wall like this?
Thanks

@zranger1 can probably give some insight, but as you say, there are many things you are not using correctly.

Firstly, you are not overloading the API, the websocket api can handle a lot of calls without issues - but they may not be in realtime as you expect.

The next thing, I would ask if you have the web page open at the same time as you are using the websocket? I ask, because the webpage uses the websocket for updates (that’s what gives the real-time update at the top of the web page), so using waitForEmptyQueue will not work correctly due to the realtime updates from the webpage you are receiving.

The next obvious question, is why are you using waitForEmptyQueue anyway? You don’t have to wait for an empty Queue to send websocket API commands.

Looking at the code for waitForEmptyQueue there does seem to be a bug in it, as it doesn’t return False on a timeout, it throws an exception.

If you wanted to use waitForEmptyQueue, you would need to wrap it in try... except like this:

try:
    pb.waitForEmptyQueue(1000)
except Exception:
    pass

You are also only supposed to create one instance of Pixelblaze, then use if for whatever, not create an instance for every call - however it should still work.

Have you tried fadein() like this:

def fadeIn():
    steps = 50
    totalduration = 3 #number of seconds over which to fade in
    brightness = 0
    intervald = totalduration/steps
    pb = Pixelblaze(ARG_IP)
    for i in range(steps+1):
        brightness = (i/steps)
        pb.setBrightness(brightness)
        print(brightness)
        time.sleep(intervald)
    print('complete')
    pb.close()

You will notice you have steps slightly wrong, as range() counts from 0 to value excluding value - so you get 50 steps, but from 0 to 49 (not 50), so you add 1 to steps in range to get 0 to 50 (which is what I think you intended). You also don’t need to define brightness as 1 or 0 as the first thing range does is define brightness.

You should actually take Pixelblaze out of the functions like this:

pb = Pixelblaze(ARG_IP)

def fadeIn(totalduration = 3, steps = 50):
    intervald = totalduration/steps
    for i in range(steps+1):
        brightness = i/steps
        print(brightness)
        pb.setBrightness(brightness*brightness*brightness)
        time.sleep(intervald)
    print('fadein complete')

def fadeOut(totalduration = 3, steps = 50):
    intervald = totalduration/steps
    for i in range(steps, -1, -1):
        brightness = i/steps
        pb.setBrightness(brightness*brightness*brightness)
        print(brightness)
        time.sleep(intervald)
    print('fadeout complete')

pb.close()

This should work as intended.

I would point out that your original setting for brightness will likely not give a very smooth fade in or out as LED brightness is not perceived as linear by the human eye - it’s a complicated subject, but what it boils down to is that there is a huge change in perceived brightness at the low end (0 to 0.2) of the scale, and very little at the high end (0.5 and above), so what you get is a rapid fade in, which slows down the closer you get to brightness of 1, and vice -versa.

What you can do is use brightness*brightness*brightness for your brightness value. This will look smoother to the eye, so that is what I put in the modified code.

1 Like

I’m away-from-Pixelblaze atm – will take a closer look at this later in the day, but here are a couple of thoughts:

Absolutely for certain, you can’t have the Pixelblaze’s Web UI open in a browser on the same IP address you’re using with Python. The web browser simply doesn’t want to share sockets with other applications. For broad security reasons, that’s probably a good thing.

To get around this without using multiple physical computers, I have in the past (1) set up multiple IP addresses on the same physical adapter (I do this all the time anyway so that I’ve got more than one “port 80”, which everybody seems to want.) and (2) controlled the Pixelblaze from one of my Linux machines, to which I remote in using X2go.

The Pixelblaze buffers incoming websocket requests, and has limited buffer space. For something like this, waitForEmptyQueue isn’t absolutely essential, but it’s doesn’t hurt anything, and actually does ensure you don’t overrun the command buffer. In any case, for fading, I’d rate limit it to no more than 10 requests/second. LED Brightness being as non-linear as it is, you could probably get by with far fewer steps if you use large steps at high brightness and smaller ones as brightness gets low.

Edit: @Nick_W, throwing exceptions on timeout was deliberate. I somewhat arbitrarily decided that throwing an exception was how I wanted to deal with disconnects. If a command times out on a Pixelblaze, it means the connection has died, and the application should probably know about it. Python isn’t my native language, so if there’s a more Pythonic convention, or a better way to handle this, please let me know. :slight_smile:

@zranger1

Looking at the code for waitForEmptyQueue there does seem to be a bug in it, as it doesn’t return False on a timeout, it throws an exception.

I think it should be:

def waitForEmptyQueue(self, timeout_ms=1000):
    """
    Wait until the Pixelblaze's websocket message queue is empty, or until
    timeout_ms milliseconds have elapsed.  Returns True if an empty queue
    acknowldgement was received, False if timeout or error occurs.
    """
    self.ws_flush()
    self.ws.settimeout(timeout_ms / 1000)
    try:
        self.send_string('{"ping": true}')
        result = self.ws.recv()
        self.ws.settimeout(self.default_recv_timeout)

        return True if ((result is not None) and (result.startswith('{"ack"'))) else False
    except websocket._exceptions.WebSocketTimeoutException:
        self.ws.settimeout(self.default_recv_timeout)  # restore normal timeout
    return False

Ah… I lied in the comments! I’ll fix that ASAP. Thanks1

@bdm, I finally got a moment – have been working on (not) my favorite thing, household plumbing, all day. One thing that jumps right out at me is that you don’t need to, and actually shouldn’t, create more than one Pixelblaze object in a program. They do close when they go out of scope, but asking the Pixelblaze to handle multiple connections from the same IP is likely to make it a little wobbly.

I suggest making a global (or class) object in your main program. That way everything else in the program can use the same connection. You can also create an object and pass it around as a parameter.

Here’s a quick and dirty program that does brightness transitions. It takes the Pixelblaze object, the desired new level, and the transition time as parameters. It roughly shapes the brightness curve so it moves more slowly at lower brightness levels, which theoretically looks better. You can control the maximum packets/sec rate – I wouldn’t go above 10 or so for this purpose.

#!/usr/bin/env python3

from pixelblaze import *

def transitionBrightness(pb,level,seconds):
    """
    Changes brightness on the Pixelblaze object <pb> to <level> over
    <seconds> seconds. Note that this call will block execution for
    the entire interval of the transition.  It's a great candidate
    for use w/the async library, (or for threading if you're really
    old school.)
    """
    # figure out how many requests we'll be sending and how quickly
    maxRequestsPerSec = 8
    waitInterval = 1 / maxRequestsPerSec;    
    steps = maxRequestsPerSec * seconds;
    
    # get the current brightness
    result = pb.getHardwareConfig()
    initialBrightness = result['brightness']
    
    # If levels are equal we're done.  Take early out for
    # performance
    if (initialBrightness == level) :
        return
       
    # Do the fade out if we're going to a lower level
    if (initialBrightness > level) : 
        i = steps
        linearDelta = (initialBrightness - level) / steps           
        while (i > 0) :
            newBrightness = level + ((linearDelta * i)) * pow(i/steps,3)    
            pb.setBrightness(newBrightness)
            time.sleep(waitInterval)
            i = i - 1
    # Otherwise fade (new brightness is higher)         
    else :                   
        i = 1
        totalwait = 0;
        linearDelta = (level - initialBrightness) / steps        
        while (i <= steps) :
            newBrightness = initialBrightness + (((linearDelta * i)) * pow(i/steps,3))
            pb.setBrightness(newBrightness)
            time.sleep(waitInterval)
            i = i + 1

if __name__ == "__main__":

    pixelblazeIP = "192.168.1.19"     # insert your own IP address here
       
    # create a Pixelblaze object.
    pb = Pixelblaze(pixelblazeIP)  
             
    print("Testing: transitionBrightness")
    pb.setBrightness(1)
    print("From 1 to 0")
    transitionBrightness(pb,0,3)
     
    print("From 0 to 1");
    transitionBrightness(pb,1,3)
      
    print("From 1 to 0.5")
    transitionBrightness(pb,0.5,3)
    
    print("from 0.5 to 0.75");
    transitionBrightness(pb,0.75,3)
    
    print("from 0.75 to 0.125");
    transitionBrightness(pb,0.125,3)

    pb.close()
    print("Testing: Complete!")

Thanks @zranger1 and @Nick_W
I’ve also been busy with plumbing a lot lately - only the outdoor variety, trying to chase down leaks in water lines. Also not my favourite thing.

Thanks for the suggestions with streamlining the code. I’ve put most of this into my script, with good effect. I like the approach to fading, ensuring it doesn’t make too many steps too fast. I found that running Oasis, it actually looked better with 5/second rather than 8 or more, as the background pattern hesitates with the extra CPU load (/sleep command).

For my use case, the python script is only called as one-off instances by my smart home system, running on a headless linux-based raspberry pi - so I can safely say that I would never access it from the same IP address with a browser. I tried to ensure that I didn’t have a browser to it open on another computer, but I certainly may have missed one.

I finally learned what the if name == “main” statement means - and as far as I can tell it is used here to create a chunk of code that only runs if the script is the parent being run, rather than being called/used in another script. I’ve omitted it for now, as the script runs purely on an as-needed basis, called by my smart home system with command-line arguments (ie, a light switch is flicked off, so the LED strip turns on).

1 Like

I see. Throwing an exception on timeout is perfectly fine. I think the problem people will have is that the exception thrown is for a package that isn’t imported with the Pixelblaze class, so you can’t catch it explicitly.

You would have to import the websocket package, just to catch the timeout exception. That or just use the generic except Exception trick.

You could document this, or create your own exception class as part of the Pixelblaze class.

2 Likes

Actually, @nick_w, I thought about it and decided you were right - it’s a bug - and implemented your fix. In that function, a timeout is not fatal, and should not require special handling. I’m testing it now, along with a few other fixes and improvements and will check everything in shortly.

I think that’s probably a good choice.

The other thing I was thinking was that if the websocket is Ok, and an unrealistic timeout was set, throwing an exception would leave that same timeout in place - which might cause other issues.

This way, the timeout gets reset to default, and as you say, no harm done.

1 Like

@bdm, I just finished an outdoor leak quest too. It resulted in me digging an 80 foot trench (about 1/3 the distance to the road :frowning: ) and replacing PVC that was mostly repair couplings with new PEX pipe.

The only good thing about it was that it comes in 100 foot rolls, and I now have 20 feet of quite nice diffuser for LED projects. So naturally, this was the first thing I made:
(w/Pixelblaze v3 Pico, 150 APA102s, currently powered by long extension cord while I figure out how to mount the batteries.)
20210410_190127_1

2 Likes

Nice lightsaber! (well, I’m sure it could be useful for other things as well…)

I feel your pain for the extensive trenching.

It’s a law of nature – if a person has an addressable LED strip, there will be KITT. If an LED strip and a sturdy translucent tube, there will be a lightsaber!

2 Likes

Batteries that fit inside the pex… with enough umph to power PBs and LEDs. Let me know what you find. I’ve got some 14500 which are 3.7v I was planning on using in hulahoops. Sized like AA.

1 Like