Pixelblaze-client: Python 3 library for Pixelblaze

Cool… I’ll make all these changes and get it checked in. At this point, I’ll have it defer to any other time source. Will just leave a note in the code that at some point we might want to compare IDs and give lower numbered sources priority.

I did browsemaster election for NetBIOS/SMB networks a very long time ago. It was (and still is) pretty hairy. If things are working as they are, not much reason to add complication at the moment.

1 Like

Done!
PixelblazeEnumerator now responds with timesync packets on every beacon as quickly as possible. The old “synchronize(),enable/disable autosync” API gone, the new one is just:

  • enableTimesync() - start synchronization
  • disableTimesync() - stop synchronization

Synchronization is off by default. The user must call enableTimesync() to start it. Once started, if it detects another time source on the network, it will automatically defer. The default senderID for pixelblaze-client is now 890 - if there’s an eventual lower-id–has-priority scheme, it’s 1 level below Firestorm.

… back to working on outdoor lights for me! …

4 Likes

Ok! The pixelblaze-client package is now available on pypi. To install, just
pip install pixelblaze-client
and use
import pixelblaze
or
from pixelblaze import *
in your programs.

Sample code is in the “examples” directory in the repository, and API documentation is still in README.md

First time publishing anything there – my main use of Python to date has been for short one-off data analysis scripts. So:

  • Yay, it worked! It’s up there and available to the general public!
  • Let me know if you encounter any problems.
  • Have fun making cool things!
5 Likes

Just posted a minor update to pixelblaze-cllient. It’s available on pypi now.

This version adds support for the new sequencer features in the latest Pixelblaze firmware. Specifically:

startSequencer() now takes a “mode” parameter, which sets the sequencer’s operating mode: 1 - Shuffle, 2 - Playlist. You still have to use the Web UI to set up your playlist (for now).

I’ve added methods to pause and restart the sequencer without losing your place in the current shuffle or playlist. They are:
pauseSequencer() and playSequencer()

stopSequencer() still stops and disables the sequencer, regardless of mode.

Full documentation is available on pypi, or from the repository’s readme. I’ve been meaning to get this checked in for a while, but it’s just been the Season of Way Too Many Projects…

3 Likes

I’ve updated the pixelblaze-client library to v0.9.4. It’s available on Github and pypi now.

This is the “small step now before we take a huge step later” release. There are bug fixes, performance improvements, a few new commands and some behavior changes aimed at reducing unneeded writes to flash memory.

Here are the details:

  • Documented getPixelCount/setPixelCount(), which lets you get and set the number of LEDs attached to your Pixelblaze.
  • added the pause() and unpause() commands.
  • getPatternList() is now cached, for greatly improved performance. The cache timeout can be set by calling setCacheRefreshTime(seconds). The default is 600 seconds, or 10 minutes.
  • Reduced unneccessary flash writes - setActivePattern(), setActivePatternId() and setBrightness() now take
    an optional saveFlash parameter, which is False by default, and uses the enable_flash_save() safety
    mechanism (described in the API documentation) to prevent inadvertent flash saves.

If you’re a Python user, try this out and let me know how it works for you!

4 Likes

Pull request: If you change the signature of ws_recv to:

    def ws_recv(self, wantBinary=False, packetType=0x07):

and the test for binary packets to:

                elif (wantBinary is True) and (result[0] == packetType):  # binary pattern list packet

then your library can be used to read other things besides the pattern list (I’m redoing my animated previews code to take advantage of the increased number of preview pixels in v3.24, but they’re only in the websocket packets – the preview jpeg saved inside the pattern EPE is still limited to 100 wide by 150 tall).

3 Likes

Thanks! Will get this in this afternoon, and into pypi asap!

If you’ve got a github account, pm me with your github name and I’ll add you as a collaborator on the python client repo so you can check things like this in as you encounter them.

1 Like

Ok - @pixie’s improvement is in! pixelblaze-client v0.9.5 is updated in the repo and available for download on pypi.

3 Likes

Hello! I’ve been using the client to run my project, but i just updated to v0.9.5 and it seems like a few things have changed enough that it broke, and I can’t find documentation that’s helpful.

I’m getting a message about the difference between setActivePattern(name_or_id) and setActivePattern(id). I’m not fluent enough to understand what has changed and why my code isn’t working. The examples seem to be broken as well, so I’m kind of at a loss.

Thanks so much!

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