Pixelblaze-client: Python 3 library for Pixelblaze

One last thing: If the Pixelblaze goes away for a really long time, the library will throw a TimeoutError exception, on the theory that it’s better to inform the application about this sort of thing than to automatically keep going forever. You can catch it as usual, with try/except, something like this:

import time
from pixelblaze3 import *
for ipAddress in Pixelblaze.EnumerateAddresses(timeout=1500) :
    print("Found Pixelblaze at ",ipAddress)    
    pb = Pixelblaze("192.168.1.18")
count=0           
while True:
    try:
        pb.setActiveVariables({'loc': count%100})
        print(count)
        count+=1
        time.sleep(1)
    except TimeoutError:
        print("AAAAAAAAARGH!")
        break

Ok, something weird is happening. When I run that script for several hundred count cycles, the updating slows down a lot. It does this both on a Raspberry pi and my windows machine. It doesn’t drop commands, it just takes a long time to update them. So initially it will update every second, but then eventually it will take longer and longer, even though the script is updating out to the terminal every second. The pixelblaze will continue to slowly update even if I ctrl-c out of the script, but it appears to stop if I reset the shell. It seems like the commands are getting piled up in the PB client somewhere.

Here is the code on the Pixelblaze for reference

export var loc=1;

export function beforeRender(delta) {
  t1 = time(.1)
}

export function render(index) {
  h = t1 + index/pixelCount
  s = 1
  v = index==loc
  hsv(h, s, v)
}

Interesting. How good is the wifi signal at your Pixelblaze? Does it still slow down if you move it closer to the wifi AP?

I’ve got this running on a PB3 w/latest firmware and a solid signal, with time.sleep(0.1). It hasn’t started slowing down yet, but I’ll just leave it on this evening and see what happens.

Its not really mobile, but its fairly close to the router. This behavior seems pretty repeatable, but it takes around ~10 minutes (~600 1s steps). When I run it at 0.1s steps it seems to work without any noticeable lag (in the first couple of minutes). I’ll run it out longer.

edit:
Ok, it went a few thousand cycles at 0.1s pauses, and then slowed to a crawl around 5 minutes.

Mine just did that too after about 12k cycles. Will start debugging. (Script seems to be happily sending commands at speed, but Pixelblaze is somehow way behind.)

1 Like

This problem - Pixelblaze 3 w/latest firmware apparently stops listening to setVars after about 10-15 minutes of running our test program - doesn’t appear to happen on Pixelblaze v2. @spazzle, do you have a PB2 around to confirm? And @wizard, am I missing something obvious here?

The python code that drives the test is below, and the Pixelblaze pattern is in an earlier message. It sends a setVars command for a single variable 10 times a second. (But the frequency doesn’t appear to make any difference.) I’m in for some quality time with wireshark on this tomorrow unless somebody has an easy answer.

import time
from pixelblaze import *

#websocket.enableTrace(True)
for ipAddress in Pixelblaze.EnumerateAddresses(timeout=1500) :
    print("Found Pixelblaze at ",ipAddress)
    pb = Pixelblaze("192.168.1.15")  # I have a lot of these things.  This is the one I want

count=0
interval = 0.1
while True:
    try:
        pb.setActiveVariables({'loc': count%100})
        print(count*interval)
        count+=1
        time.sleep(interval)
    except TimeoutError:
        print("AAAAAAAAARGH!")
        break

I’ve run for 30 minutes so far on a v2 with 2.29 and it hasn’t had problems yet. I’ll run it longer and see if anything happens.

Edit: Still going without issue.

Hmm, interesting. Unless there’s a memory leak somewhere… Could it also be related to the ack processing? In either case I would imagine the problem would come about after the same number of messages, not based on time.
Are there any disconnect / reconnects happening in between?

@wizard, it actually doesn’t seem to matter what mix of commands you send – very reliably, at about 720 seconds in, the Pixelblaze stops responding. There’s no break in the connection, even when everything stalls. Kill the Python program to close the connection and restart it, and everything works again for another 720 seconds.

There’s a chance it might be something on the Pixelblaze side, and it’d be great if something jumps out at you if you take a look, but since the problem doesn’t occur within the same timespan when I’m say, watching a bunch of variables in the webUI, I kind of suspect the Python websocket-client library I’m using.

I’m still trying to figure out exactly what it might be doing or not doing, but the whole websocket connection maintenance, ping/pong mechanism is trickier to manage in synchronous Python, so that’s what I’m working through now.

But why would it only be a problem with V3 Pixelblazes and not V2 if its on the python side?

At a guess? The ESP8266 websocket implementation might be ignoring some handshake requirement in favor of performance. I’ll know more after I’ve had a little more time to watch it run tonight.

1 Like

Update: This is a real issue in the python-client library, and has to do with the underlying websocket library not automatically maintaining ping/pong timing. Fixing it is going to take some refactoring for multithreading and may be a week or two.

In the meantime, if you need to send a continuous stream of data to a Pixelblaze, here’s a callback-driven template that does the right connection maintenance in a separate thread. This just moves a pixel around the display using @spazzle’s pattern from earlier in the thread.

In addition to all of pixelblaze-client’s dependencies, this code also requires rel (registered event listener), which can by installed with

pip3 install rel

Python: Send a variable to a Pixelblaze
import websocket
import _thread
import time
import rel
import json
from pixelblaze import *

count = 0  # number of intervals elapsed
interval = 0.05  # seconds between setVars calls

def on_message(ws, message):
    pass
    #print(message)

def on_error(ws, error):
    print(error)

def on_close(ws, close_status_code, close_msg):
    print("Connection closed")

def on_open(ws):
    print("Connection opened")
    
    
def on_timer():
    global count
    ws.send(json.dumps({"setVars": {'loc' : count % 256}}, indent=None, separators=(',', ':')).encode("utf-8"))
    count=count+1
    return True

def print_status():
    print(int(count * interval))
    return True
    

if __name__ == "__main__":
    #websocket.enableTrace(True)
    
    # list available pixelblazes and select the last one in the list
    for ip in Pixelblaze.EnumerateAddresses(timeout=1500) :
        print("Found Pixelblaze at ",ip)
        ipAddress = ip
        
    # Or, manually choose the one you want if you know the IP address
    # comment out the statement below if you want to take a PB from the list instead
    ipAddress = "192.168.1.18"
    
    # create connection with threaded websocket handler object
    print("Using Pixelblaze at: ",ipAddress)
    ws = websocket.WebSocketApp("ws://" + ipAddress + ":81",
                              on_open=on_open,
                              on_message=on_message,
                              on_error=on_error,
                              on_close=on_close)

    # Set ws dispatcher to reconnect automatically with 5 second reconnect delay
    # if connection closes unexpectedly
    ws.run_forever(dispatcher=rel, reconnect=5)
    
    # terminate on keyboard interrupt
    rel.signal(2, rel.abort)
    
    # send data to PB on timer 
    rel.timeout(interval, on_timer)
    
    # print elapsed time every second so we'll know how we were able to run if something
    # goes wrong
    rel.timeout(1,print_status)
    
    # start the callback dispatcher
    rel.dispatch()
    
  
3 Likes

Thank you @zranger1 for digging in and finding out what was going on!

I’d be interested in helping to test when you have something.

Just FYI, I did put together an asynchronous Python Pixelblaze websocket interface.

Haven’t updated it recently, but it’s https://github.com/NickWaterton/Pixelblaze-Async if anyone wants to try it.

2 Likes

My plan is to interface with both on screen and physical controls to run a number of different lights. Mostly a combination of pixelblaze controlled strips and some DMX controlled objects.
I’m using Dearpygui, which is a more straightforward python gui framework.

pip install dearpygui

Here is a (very simple) example of how I’d like to use it. Mostly I’d probably be controlling speed, color, and brightness. Here I’m just controlling location again. This only updates when you move the location slider, but it still runs into the speed problem after several minutes.

import dearpygui.dearpygui as dpg
from pixelblaze import *
for ipAddress in Pixelblaze.EnumerateAddresses(timeout=1500) :
    print("Found Pixelblaze at ",ipAddress)    
    pb = Pixelblaze(ipAddress)
    
def locset(sender, app_data):
    pb.setActiveVariables({'loc': app_data})
    print(app_data)
    
dpg.create_context()
dpg.create_viewport(title='Custom Title', width=600, height=200)

with dpg.window(label="Example Window", width=600, height=200):
    dpg.add_slider_int(label="location", default_value=0, max_value=300,callback=locset)


dpg.setup_dearpygui()
dpg.show_viewport()
dpg.start_dearpygui()
dpg.destroy_context()

@spazzle, and anybody else who can help test, I’ve uploaded a new library version to github at:

pixelblaze-client v1.1.2-beta.1

To try it out, download the .whl file, install it with pip, and let me know if if works for you.

1 Like

Test on Windows
Test 1: Ran for ~20 minutes, It worked without connect loss :white_check_mark:
Test 2: Disconnected the PB, and plugged it in within a few seconds, it regained connection :white_check_mark:
Test 3: Disconnected PB for ~2 minutes, eventually received a ConnectionResetError: [WinError 10054], plugged the PB back in and the connection resumed :white_check_mark:

I’ll try on an RPI next

2 Likes

Same on the Ras Pi (except it didn’t throw the WinError). Seems pretty solid so far. I was actually able to connect to the PB from 2 different computers at the same time and send commands. I’ll keep going on the ras pi for a few hours.

So far, so good. I’m doing the same thing – running connection test for a few more hours. Then if it still looks ok, I’ll upload it to pypi. Thanks for finding and reporting this!

1 Like