Pixelblaze-client: Python 3 library for Pixelblaze

Hi!,
Sorry about this – the version currently on github is mostly for testing at the moment. We’re in the middle of a gigantic rewrite that will shortly become version 1.0, but right now it’s a bit chaotic.

Probably the best thing to do is to revert to the previous library version, (available from the “Archive” branch on github.) or install the version that’s currently on pypi, which is the “Archive” version plus a couple of fixes.

Is your code on Github up to date? If so, here are versions updated to use the new library APIs:

warlocktable.py
import pygame
import random
import RPi.GPIO as GPIO
import time
import pn532.pn532 as nfc
import csv
from pn532 import *
from pixelblaze import *

pn532 = PN532_SPI(debug=False, reset=20, cs=4)

ic, ver, rev, support = pn532.get_firmware_version()

pn532.SAM_configuration()

pygame.mixer.init()

if __name__ == "__main__":

    # create a Pixelblaze object.
    pb = Pixelblaze("10.1.10.165") 
    pb.setActivePatternByName("KITT")

while True:
    print('Waiting for RFID/NFC card to write to!')
    while True:
        # Check if a card is available to read
        uid = pn532.read_passive_target(timeout=0.5)
        print('.', end="")
        # Try again if no card is available.
        if uid is not None:
            break
    print('Found card with UID:', [hex(i) for i in uid])

    with open('warlocktable.csv') as csv_file:
        csv_reader = csv.reader(csv_file, delimiter=',')
        line_count = 0
        for row in csv_reader:
                print(f'\t{row[0]} has byte array {row[1]}')
                line_count += 1
        print(f'Processed {line_count} lines.')

GPIO.cleanup()

and

TarotWizard.py

import pygame
import random
import RPi.GPIO as GPIO
import time
import pn532.pn532 as nfc

from pn532 import *

test2 = bytearray(b'\x04\xf00\x1a\xcfO\x80')
test1 = bytearray(b'\x04:7\x1a\xcfO\x81')
magician = bytearray(b'\x049i\x9afp\x81')
aceofpentacles = bytearray(b'\x049h\x9afp\x81')
thedevil = bytearray(b'\x049g\x9afp\x81')
wheeloffortune = bytearray(b'\x049f\x9afp\x81')
death = bytearray(b'\x049j\x9afp\x81')
forest = bytearray(b'\x049e\x9afp\x81')
plains = bytearray(b'\x049c\x9afp\x81')
swamp = bytearray(b'\x049b\x9afp\x81')
island = bytearray(b'\x049a\x9afp\x81')
mountain = bytearray(b'\x049d\x9afp\x81')

pn532 = PN532_SPI(debug=False, reset=20, cs=4)

ic, ver, rev, support = pn532.get_firmware_version()

# Configure PN532 to communicate with NTAG215 cards
pn532.SAM_configuration()

pygame.mixer.init()

from pixelblaze import *

if __name__ == "__main__":


    # create a Pixelblaze object.
    pb = Pixelblaze("10.1.10.165") 
    pb.setActivePatternByName("KITT")

    # Look up some pattern IDs.    
    TableTest1 = "blink fade"
    TableTest2 = "fireflies"
    magicianPattern = "green ripple reflections"
    aceofpentaclesPattern = "glitchbands"
    thedevilPattern = "sparkfire"
    wheeloffortunePattern = "spin cycle"
    deathPattern = "opposites"
    forestPattern = "GreenCard"
    swampPattern = "BlackCard"
    mountainPattern = "RedCard"
    islandPattern = "BlueCard"
    plainsPattern = "WhiteCard"

while True:
    print('Waiting for RFID/NFC card to write to!')
    while True:
        # Check if a card is available to read
        uid = pn532.read_passive_target(timeout=0.5)
        print('.', end="")
        # Try again if no card is available.
        if uid is not None:
            break
    print('Found card with UID:', [hex(i) for i in uid])

    print('This card is')
    if uid == test1:
        print('TEST CARD 1')
        pb.setActivePatternByName(TableTest1)
        pygame.mixer.music.stop()
        pygame.mixer.music.load('/home/pi/Documents/MagicTarot/Ov/outre.wav')
        pygame.mixer.music.play(-1);
    elif uid == test2:
        print('TEST CARD 2')
        pb.setActivePatternByName(TableTest2)
        pygame.mixer.music.stop()
        pygame.mixer.music.load('/home/pi/Documents/MagicTarot/Ov/cigarette.wav')
        pygame.mixer.music.play(-1);
    elif uid == magician:
        print('MAGICIAN')
        pb.setActivePatternByName(magicianPattern)
        pygame.mixer.music.stop()
        pygame.mixer.music.load('/home/pi/Documents/MagicTarot/Ov/crowley.wav')
        pygame.mixer.music.play(-1);
    elif uid == aceofpentacles:
        print('ACEOFPENTACLES')
        pb.setActivePatternByName(aceofpentaclesPattern)
        pygame.mixer.music.stop()
        pygame.mixer.music.load('/home/pi/Documents/MagicTarot/Ov/peromyscus.wav')
        pygame.mixer.music.play(-1);
    elif uid == thedevil:
        print('THEDEVIL')
        pb.setActivePatternByName(thedevilPattern)
        pygame.mixer.music.stop()
        pygame.mixer.music.load('/home/pi/Documents/MagicTarot/Ov/javan.wav')
        pygame.mixer.music.play(-1);
    elif uid == wheeloffortune:
        print('WHEELOFFORTUNE')
        pb.setActivePatternByName(wheeloffortunePattern)
        pygame.mixer.music.stop()
        pygame.mixer.music.load('/home/pi/Documents/MagicTarot/Ov/audley.wav')
        pygame.mixer.music.play(-1);
    elif uid == death:
        print('DEATH')
        pb.setActivePatternByName(deathPattern)
        pygame.mixer.music.stop()
        pygame.mixer.music.load('/home/pi/Documents/MagicTarot/Ov/september.wav')
        pygame.mixer.music.play(-1);
    elif uid == forest:

        pb.setActivePatternByName(forestPattern)
        #pygame.mixer.music.stop()
        #pygame.mixer.music.load('/home/pi/Documents/MagicTarot/MagicCards/forest.wav')
        #pygame.mixer.music.play(-1);
    elif uid == island:

        pb.setActivePatternByName(islandPattern)
        #pygame.mixer.music.stop()
        #pygame.mixer.music.load('/home/pi/Documents/MagicTarot/MagicCards/island.wav')
        #pygame.mixer.music.play(-1);
    elif uid == mountain:

        pb.setActivePatternByName(mountainPattern)
        #pygame.mixer.music.stop()
        #pygame.mixer.music.load('/home/pi/Documents/MagicTarot/MagicCards/mountain.wav')
        #pygame.mixer.music.play(-1);
    elif uid == swamp:

        pb.setActivePatternByName(swampPattern)
        #pygame.mixer.music.stop()
        #pygame.mixer.music.load('/home/pi/Documents/MagicTarot/MagicCards/swamp.wav')
        #pygame.mixer.music.play(-1);
    elif uid == plains:

        pb.setActivePatternByName(plainsPattern)
        #pygame.mixer.music.stop()
        #pygame.mixer.music.load('/home/pi/Documents/MagicTarot/MagicCards/plains.wav')
        #pygame.mixer.music.play(-1);
    else:
        print('not a registered card!')
    time.sleep(1)
    
GPIO.cleanup()

Thanks! I’m going to dig into this tomorrow, I rolled back to v0.9.4 to get it working today, and I’ll look at the versions you linked pixie! Thanks so much!

My stuff all needs a cleanup and a rethink anyway, so I’ll be working on that as well.

Thanks @zranger1 for all the work you’ve done on this library since I last peeked in!
I believe in the ages since I set my system up, there was probably a breaking change in new firmware for one of the commands I used, which prompted me to look into your new version. It appears I had unfortunate timing.
Your github page documentation is for 1.0.0, but the pip3 install mirror was for 0.9.6. I updated my script to the function names/format per the 1.0.0 version, to find this out.

After this, I uninstalled from pip3, and pulled the file from github.
Unfortunately, now I started to get errors from the pixelblaze.py file - not sure if this means there is a problem with the version on github.

I made a test script directly from the examples (list pixelblazes), and the same error comes up.
My script:

openhabian@openhab:~/openhabuserdata $ python3 PBScript.py 192.1.1.1 oasis
Traceback (most recent call last):
File “PBScript.py”, line 9, in
from pixelblaze import *
File “/var/lib/openhab/pixelblaze.py”, line 82, in
class Pixelblaze:
File “/var/lib/openhab/pixelblaze.py”, line 657, in Pixelblaze
def getFileList(self, fileTypes:fileTypes=fileTypes.fileAll) → list[str]:
TypeError: ‘type’ object is not subscriptable

Example Script

openhabian@openhab:~/openhabuserdata $ python3 testpix.py
Traceback (most recent call last):
File “testpix.py”, line 2, in
from pixelblaze import *
File “/var/lib/openhab/pixelblaze.py”, line 82, in
class Pixelblaze:
File “/var/lib/openhab/pixelblaze.py”, line 657, in Pixelblaze
def getFileList(self, fileTypes:fileTypes=fileTypes.fileAll) → list[str]:
TypeError: ‘type’ object is not subscriptable

I assume this is a problem with the 1.0.0 pixelblaze.py file, but after updating my script to the new function names, I’m wondering if it is worth the effort, if a bugfix is coming, or if I’m missing something else entirely.

Thanks!

Hi @bdm,
That error is caused by the library trying to use a feature that’s missing in older versions of Python. Along with all the other enormous changes for this version, we also changed the minimum required Python version to 3.9. (And I’d recommend 3.10 if you can use it.)

Once upgraded, installing with pip3 should get you library version 1.0.0 too. This is a behavior I wasn’t aware of — looks like pip grabs the most recent version compatible with your installed Python instead of complaining, as I would have expected.

If OpenHab can’t use a newer Python, let me know, and we’ll figure something out!

Thanks - that would be the something else entirely, then. My mind saw python 3 and then zoned out, I imagine.

I’m running openhabian - which is a dressed-up raspbian 10 (buster) on a pi4, and was able to compile Python 3.10 and 3.9 on it. 3.10 gave me some grief, so I also installed 3.9, and kept having some headaches.

openhabian@openhab:~/openhabuserdata $ python3.9 PBScript.py 192.1.2.3 pattern Oasis
Attempting to set pattern to: Oasis
Traceback (most recent call last):
File “/var/lib/openhab/PBScript.py”, line 221, in
pb.setActivePatternByName(ARG_Extra)
File “/var/lib/openhab/pixelblaze.py”, line 1979, in setActivePatternByName
self.setActivePattern(self._get_pattern_id(patternName), saveToFlash)
AttributeError: ‘Pixelblaze’ object has no attribute ‘_get_pattern_id’

the ARG_Extra was ‘Oasis’, passed from the command line.
My PBScript is a heavily modified version of your early example script.
I have a feeling there are a lot of now bad practices in it, however for now I’ve been reading the API docs heavily and trying to update it sufficiently. For this error, I don’ think it’s something I’ve done wrong. My script is running pb.SetActivePatternByName with the command line argument, and I can’t imagine that any misspelled pattern name should be able to produce the 'has no attribute ‘_get_pattern_id’

incidentally, when running python 3.10, the error for the same command is:

openhabian@openhab:~/openhabuserdata $ python3.10 PBScript.py 192.1.2.3 pattern Oasis
Traceback (most recent call last):
File “/var/lib/openhab/pixelblaze.py”, line 381, in _open
self.ws = websocket.create_connection(uri, sockopt=((socket.SOL_SOCKET, socket.SO_REUSEADDR, 1), (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1),))
AttributeError: module ‘websocket’ has no attribute ‘create_connection’

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
File “/var/lib/openhab/PBScript.py”, line 192, in
pb = Pixelblaze(ARG_IP)
File “/var/lib/openhab/pixelblaze.py”, line 226, in init
self._open()
File “/var/lib/openhab/pixelblaze.py”, line 383, in _open
except websocket._exceptions.WebSocketConnectionClosedException:
AttributeError: module ‘websocket’ has no attribute ‘_exceptions’

I think I had installed all the dependencies as sudo for 3.10, but as I read that may cause issues, I installed them as a user for 3.9.
Those two circumstances were running the exact same PBScript.py.

While my script was capable of more than it, my core needs are: set brightness, set pattern by name. It would be nice to set color as well, but I didn’t end up using that as often as I thought.
I only really had 5 patterns that openhab would control - anything else was me playing in the pixelblaze web UI itself.
My dreams of making an openhab binding are likely far off, given the time it is taking me to get this simple script working, as well as finish migrating my openhab system. For now, baling twine and duct tape should work fine.

All that to say, I’d appreciate any further direction you could provide, or perhaps an example file that does these simple things (your github examples don’t seem to do these). If a known functioning example doesn’t work on my system, then something is hooped with my setup - dependency versions or something.

I’ve just updated the repo and pypi to v1.0.1 – this fixes the bug that broke setActivePatternByName() and adds a new simple Pixelblaze control example - simple.py - to the examples directory.

The websocket problem can probably be solved by uninstalling/reinstalling websocket-client. They’ve made a lot of changes recently too.

If building Python from source isn’t getting all the dependencies set up the way you want them, you might try using the deadsnakes repo, which has builds of every version of Python from the dawn of time to the most current. I have to keep multiple versions around for various projects, and it has been really helpful. To set it up:

$ sudo add-apt-repository ppa:deadsnakes/ppa
$ sudo apt-get update
$ apt-get install python3.10 (or whatever version you need)

(you can install with sudo if you like. Or not. All it does is change the install location.)

I use virtualenv to manage and switch between versions. It really reduces the chaos. Not sure how OpenHab would react to adding yet another layer of partial virtualization, so try with caution.

Let me know if any of this helps!

Thanks, this helps a lot.
Setting patterns by name is now working as expected.
I had several other commands to update, but I believe I have everything working that I need.

It appears that waitForEmptyQueue is no longer needed, as is a pb.Close() command.

It appears that you can get the ID of the current Pattern with getActivePattern(), but there doesn’t seem to be a way to get the name of this pattern. I’d humbly suggest this be added to the list of future possible improvements, should I not be missing it somewhere, as it would be helpful to see on the user end. - edit: I recognize it is referenced in getSequencerState, and could likely be extracted from this

My testing showed some combination of back-to-back requesting getStorageSize(), getStorageUsed(), getUptime(), and/or getVersion() command seemed to timeout or crash the system - there may be a problem there. If I removed those requests from my currentState function, then things seemed to run as expected.
^C resulted in the following traceback:

^CTraceback (most recent call last):
File “/var/lib/openhab/PBScript.py”, line 234, in
currentState()
File “/var/lib/openhab/PBScript.py”, line 148, in currentState
print("Current firmware version : ", pb.getVersion())
File “/var/lib/openhab/pixelblaze.py”, line 1800, in getVersion
self.latestVersion = self.getConfigSettings().get(‘ver’, None)
File “/var/lib/openhab/pixelblaze.py”, line 1302, in getConfigSettings
ignored = self.wsReceive(binaryMessageType=None)
File “/var/lib/openhab/pixelblaze.py”, line 437, in wsReceive
frame = self.ws.recv()
File “/home/openhabian/.local/lib/python3.10/site-packages/websocket/_core.py”, line 362, in recv
opcode, data = self.recv_data()
File “/home/openhabian/.local/lib/python3.10/site-packages/websocket/_core.py”, line 385, in recv_data
opcode, frame = self.recv_data_frame(control_frame)
File “/home/openhabian/.local/lib/python3.10/site-packages/websocket/_core.py”, line 406, in recv_data_frame
frame = self.recv_frame()
File “/home/openhabian/.local/lib/python3.10/site-packages/websocket/_core.py”, line 445, in recv_frame
return self.frame_buffer.recv_frame()
File “/home/openhabian/.local/lib/python3.10/site-packages/websocket/_abnf.py”, line 338, in recv_frame
self.recv_header()
File “/home/openhabian/.local/lib/python3.10/site-packages/websocket/_abnf.py”, line 294, in recv_header
header = self.recv_strict(2)
File "/home/openhabian/.local/lib/python3.10/site-packages/websocket/abnf.py", line 373, in recv_strict
bytes
= self.recv(min(16384, shortage))
File “/home/openhabian/.local/lib/python3.10/site-packages/websocket/_core.py”, line 529, in _recv
return recv(self.sock, bufsize)
File "/home/openhabian/.local/lib/python3.10/site-packages/websocket/socket.py", line 108, in recv
bytes
= _recv()
File “/home/openhabian/.local/lib/python3.10/site-packages/websocket/_socket.py”, line 87, in _recv
return sock.recv(bufsize)
KeyboardInterrupt

Specifically, this happened after I ran getDeviceName(), then getBrandName, then getDiscovery.
I could then run getStorageSize(), getStorageUsed(), and getUptime(). If I stopped here, the script would end as expected.
but If I had anything after that (specifically individually tested were: getVersion(), getActivePattern(), getBrightnessSlider(), getColorControlNames(), getActiveControls())
However, getActiveVariables() worked.

If I didn’t have any of getStorageSIze(), getStorageUsed(), or getUptime(), then all the following requests worked.

I have updated my script, and it appears to be working to the best of my knowledge, with the problematic state requests commented out. I have included it below, for reference or others’ use. I based it on an old example of zranger1, and the transition function was suggested by (name not recalled, will update), otherwise the code is mine. It can be used/modified as desired. If you are looking to use it, you’re probably better off cutting out the nightlight, oasis, and sunrise functions, and likely further gutting it down to what you need. It could also benefit from error checking.

PBScript.py:

#!/usr/bin/env python3
"""
 uses the pixelblaze-client library from, and based partially on the example scripy by JEM(ZRanger1)
 Requires the pixelblaze-client version 1+, which also requires websocket-client, pytz and requests.  If you get odd errors, you may benefit from uninstalling and re-installing (updated versions of ) those dependencies.
 The pixelblaze-client requires Python 3.9+
 Note, the functions for Oasis, sound, and sunrise depend on specific patterns being installed on your pixelblaze.  Adapt to patterns specific to your installation.
 in this example installation given its' integration with Openhab, located in /var/lib/openhab/PBScript.py
"""
#below line commented out as isntalled pixelblaze-client from zranger1's github
from pixelblaze import *
import sys

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

def setOasis():
	print("Setting pattern to Oasis")
	# depreciated pb.stopSequencer()              # make sure the sequencer isn't running
	pb.setActivePatternByName('Oasis')
	# pb.waitForEmptyQueue(1000)
	print("setting color.")
	setVar("aura",0.6666667)
	#setColor(0.6666667)
	#pb.setControl('aura', 0.66667, False)  # ensure the colors are the default blues, as they are modified in some of my other scripts)
	pb.setBrightnessSlider(1)  # as often the lights will have been turned off, and Oasis is the default pattern for smart home toggling
	print("done.")

def setSound():
	print("Setting pattern to a sound pattern")
	# depreciated pb.stopSequencer()              # make sure the sequencer isn't running   
	pb.setActivePatternByName('sound - spectro kalidastrip')
	# pb.waitForEmptyQueue(1000)
	pb.setBrightnessSlider(1)
	print("done")

def setSunrise():
	print("Setting pattern to Sunrise pattern")
	# depreciated pb.stopSequencer()              # make sure the sequencer isn't running
	pb.setActivePatternByName('Sunrise')
	pb.setBrightnessSlider(1)
	print("done")

def setNightlight():
	print("Setting pattern to Nightlight")
	# depreciated pb.stopSequencer()              # make sure the sequencer isn't running
	pb.setActivePatternByName('Nightlight')
	pb.setBrightnessSlider(0.4)
	print("done")


def setBrightness(brightness):
    print("Setting brightness to ",brightness)
    if (brightness > 1):
        print("Maximum value should be 1.0.  This value is too high.  Scaling it by 100, to interpret it as a percent")
        brightness = brightness/100
        print("New brightness is: ",brightness)
    pb.setBrightnessSlider(brightness)
    print("done")

def ListPatterns():
    print("Listing available patterns:")
    result = pb.getPatternList(False)
    for key, value in result.items():
        print(key, ' : ', value,)

def ListVariables():   
	print("Available Variables: ",pb.getActiveVariables())

def setVar(varName, varValue):
	print("attempting to set the variable: ")
	print (varName)
#	if pb.variableExists(varName):
	pb.setActiveVariables({varName: varValue})
#	else :
#		print ("Variable does not exist: ")
#		print (varName)

def setControl(controlName, controlValue):
    print("attempting to set the control: ", controlName, "to: ")
    print(controlValue)
    print("Error checking is up to you!")
    pb.setActiveControls({controlName: controlValue})
    
        
def setColor(color):
    print("Attempting to set the HSV decimal color, should a designated color control exist.  This function will need updating for an RGB picker")
    print("If there is no designated color control, you could try listvariables/setvar or listcontrols/setcontrol to set the specific color variable/control")
    colControl = pb.getColorControlName()
    print("Color control : ", colControl)
    pb.setColorControl(colControl,color)

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 = 5
	waitInterval = 1 / maxRequestsPerSec;    
	steps = maxRequestsPerSec * seconds;
	
	# get the current brightness
	initialBrightness = pb.getBrightnessSlider()
	
	# 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.setBrightnessSlider(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.setBrightnessSlider(newBrightness)
			time.sleep(waitInterval)
			i = i + 1

def currentState():
    print("Pixelblaze current state of device: ", pb.getDeviceName())
    print("Brand name: ", pb.getBrandName())
    print("Discovery Broadcast? : ", pb.getDiscovery())
    # print("Storage size : ", pb.getStorageSize())
    # print("Storage used : ", pb.getStorageUsed())
    # print("Uptime : ", pb.getUptime()) # possibly results in a crash after this runs
    print("Current firmware version : ", pb.getVersion()) #currently not working
    print("Sequencer State : ", pb.getConfigSequencer())
    patternList = pb.getPatternList()
    patternId = pb.getActivePattern()
    print("The current pattern ID: ", patternId)
    print("The current pattern name: ", patternList[patternId])
    print("The current brightness is: ", pb.getBrightnessSlider())
    print("Available Variables: ",pb.getActiveVariables())
    print("Available Colors: ",pb.getColorControlNames())
    print("Available Controls: ",pb.getActiveControls())
    
argnum = len(sys.argv) - 1

# slot command line arguements into expected variables.  Could likely benefit from error checking.
if (argnum < 1) :
	ARG_ACTION = 'help'
	ARG_IP='0.0.0.0'
	ARG_Extra = 'none'
if (argnum == 1) :
	ARG_ACTION = sys.argv[1]
	ARG_IP='0.0.0.0'
	ARG_Extra = 'none'
elif (argnum == 2) :
	ARG_IP = sys.argv[1]
	ARG_ACTION = sys.argv[2]
	ARG_Extra = 'none'
elif (argnum == 3) :
	ARG_IP = sys.argv[1]
	ARG_ACTION = sys.argv[2]
	ARG_Extra = sys.argv[3]
elif (argnum > 3 ) :
	ARG_IP = sys.argv[1]
	ARG_ACTION = sys.argv[2]
	ARG_Extra = sys.argv[3]
	ARG_FINAL = sys.argv[4]

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

#connect to pixelblaze, as long as there was an attempt at supplying an IP
if argnum > 1 :
	pb = Pixelblaze(ARG_IP)

#start to process commands
if ARG_ACTION == 'fadein':
	transitionBrightness(pb,1,3)  #fades in to full brightness (1) over (3) seconds
elif ARG_ACTION == 'fadeout':
	transitionBrightness(pb,0,3)  #fades out to zero brightness (0) over (3) seconds
elif ARG_ACTION == 'on':
	setBrightness(1)
elif ARG_ACTION == 'off':
	setBrightness(0)
elif ARG_ACTION == 'list':
	listPB()
elif ARG_ACTION == 'brightness':
	newBrightness = float(ARG_Extra)
	setBrightness(newBrightness)
elif ARG_ACTION == 'transition':
	newBrightness = float(sys.argv[3])
	transitionBrightness(pb,newBrightness,3)
elif ARG_ACTION == 'oasis':
	setOasis()
elif ARG_ACTION == 'sunrise':
	setSunrise()
elif ARG_ACTION == 'sound':
	setSound()
elif ARG_ACTION == 'nightlight':
	setNightlight()
elif ARG_ACTION == 'patternID':
    pb = Pixelblaze(ARG_IP)
    print("Attempting to set pattern by pattern ID to: ", ARG_Extra)
    pb.setActivePattern(ARG_Extra)
elif ARG_ACTION == 'pattern':
	pb = Pixelblaze(ARG_IP)
	print("Attempting to set pattern to: ", ARG_Extra)
	pb.setActivePatternByName(ARG_Extra)
	#pb.close()
elif (ARG_ACTION == 'listvariables') or (ARG_ACTION == 'listvar'):
	ListVariables()
elif ARG_ACTION == 'listpatterns':
    ListPatterns()
elif ARG_ACTION == 'setvar' or (ARG_ACTION == 'setvariable'):
	setVar(ARG_Extra, ARG_FINAL)
elif ARG_ACTION == 'setcontrol':
	setControl(ARG_Extra, ARG_FINAL)

elif ARG_ACTION == 'color':
	setColor(float(ARG_Extra))
elif ARG_ACTION == 'state':
    currentState()
elif ARG_ACTION == 'none':
	print("no command line arguments provided.  exiting.")
elif (ARG_ACTION == 'help') or (ARG_ACTION == '-h') or (ARG_ACTION == '-help') or (ARG_ACTION == '--help') or (ARG_ACTION == '--h') :
    print(" Usage:  python3 PBScript.py IP_Address command(s)")
    print("Specific commands: on, off, fadein, fadeout, transition <to brightness 0-1>, brightness <0-1>, listpatterns, listvariables, listcontrols, listcolors, setvar <variableName, variableValue>, setcontrol <controlName, controlValue>, pattern <new pattern name>, patternID <new pattern ID>, oasis, sound, sunrise, nightlight, state")
elif ARG_ACTION == 'listcontrols':
    print("Available Controls: ",pb.getActiveControls())
elif ARG_ACTION == 'listcolors':
    print("Available Colors: ",pb.getColorControlNames())
else:
	print("Unrecognized Command")

edit: updated for pattern name in state function, as suggested by zranger1

Thanks for checking this out, and thank you for posting your code. I’ll add more helper functions for working with names and ids to future versions. But for now, here’s a function that will get the active pattern’s name:

def getActivePatternName(pb) :
    patternList = pb.getPatternList()
    patternId = pb.getActivePattern()
    return patternList[patternId]

And I’ll check out the getStorageSIze(), getStorageUsed(), getUptime() bug over the weekend. Will keep you posted!

1 Like

Thanks again!
I’ve updated my PBScript above to include the pattern name resolution as you suggest.

Just posted version 1.0.2 to the github repo and to pypi. This should fix problems with getting configuration information from the Pixelblaze.

1 Like

Hi @zranger1, thank you for writing this module! I’m using it to send/control via mqtt. Question though, how do you deal with Pixelblaze() not being able to connect to the device? Specifically within a with block?

-Dom

Hi @domg,

This is an interesting issue. Here’s how it behaves now:

  • If an initial websocket connection to the pixelblaze can’t be established, the library will eventually (after about 30 seconds) throw a system-appropriate timeout exception. You can trap the exception like this:
ipAddress = "192.168.1.18"   # no Pixelblaze at this address

try:
     with Pixelblaze(ipAddress) as pixelblaze:
         print(f"    at {ipAddress} found '{pixelblaze.getDeviceName()}'")
except socket.error as e:
    print("Couldn't connect to Pixelblaze ", e)
    sys.exit()  
  • Once a connection has been established, if the Pixelblaze disappears, the library will automatically attempt to reconnect. And it’ll keep retrying until the Pixelblaze comes back, or… until forever, whichever comes first.

We did it this way because there are a number of circumstances in which the Pixelblaze can drop connection, or take a very long time to respond, and connection robustness with minimal user intervention was a high priority. I can see how this would be inconvenient for an mqtt proxy though.

In the future, I can see adding a user configurable failsafe mechanism that throws an exception after some number of failure/retry cycles, so that your program can know that the Pixelblaze is gone, rather than just waiting for it to come back.

1 Like

socket.error is what I’m looking for. That would be awesome if there was a failsafe that returned an error but the try/except should work for now. Thanks!

1 Like

I’m trying to understand/ deal with some behavior related to pixelblazes turning on and off.
I have a test program on the pixelblaze that uses an exported variable to set a pattern parameter. In this case it is the location of a single lit pixel. I can set this variable successfully using PB client and change the lit pixel location.

I have a simple python script to test this

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

This works fine.

When I unplug the pixelblaze, the system keeps sending commands, but obviously nothing happens since the unit is unpowered. When I plug the pixelblaze back in, the script hangs. If I ctrl+c out, then it recovers.

I get the same essential behavior at the command line. If I send a command after starting up the PB, it runs. If I disconnect the PB, it sends the command into the ether and goes to the next line. If I plug the pixelblaze back in and then send the command it hangs without error until I hit ctrl+c. Then subsequent commands will work again. Is there a way I can have the system smoothly recover without requiring manual input?

Hi @spazzle,

Just to be sure, are you running on Windows?

This actually always should have worked - we explicitly try to reopen the socket if it gets closed. In investigating though, I found that we missed handling one of the possible IOError exceptions that the websocket library throws when the socket gets closed by Forces Beyond Our Control.

I’ve got a provisional fix. I’ll finish testing, and post an update to the library later this afternoon.

I am on windows for testing now, but I eventually intend to use it on a Raspberry Pi.

I did try a bit more after digging through the PB client code. I saw that I was always plugging the Pixelblaze back in after only a short time. I found that if I wait long enough (it looks like there is a 30s time out), then I got a big pile of errors when I try to send a command. I haven’t tried it on linux.

“ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host”

Yeah, most of our testing was on Linux, so the 10054 was the one we weren’t catching. This update should fix automatic reconnection. You’ll still have the long timeout after the Pixelblaze goes away, while the websocket library figures out that send() isn’t working.

Once the PB comes back though, it will reconnect and keep going without any manual intervention.

Yeah, it seems to be fine on linux.

Ok, an updated pixelblaze-client library (v1.1.1) is up on pypi!
To get the latest: pip install pixelblaze-client==1.1.1