├── .gitignore ├── CHANGES.txt ├── LICENSE.txt ├── MANIFEST ├── MANIFEST.in ├── README.rst ├── bin └── blinkstick ├── blinkstick ├── __init__.py ├── _version.py └── blinkstick.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | var 14 | sdist 15 | develop-eggs 16 | .installed.cfg 17 | lib 18 | lib64 19 | 20 | # Installer logs 21 | pip-log.txt 22 | 23 | # Unit test / coverage reports 24 | .coverage 25 | .tox 26 | nosetests.xml 27 | 28 | # Translations 29 | *.mo 30 | 31 | # Mr Developer 32 | .mr.developer.cfg 33 | .project 34 | .pydevproject 35 | 36 | #idea pycharm 37 | .idea 38 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | 1.2.0 (2020-10-12) 5 | ------------------ 6 | 7 | * Bug fixes 8 | * Use PyUSB 1.0.0 9 | * Python 3 compatibility improvements 10 | 11 | 1.1.8 (2015-11-02) 12 | ------------------ 13 | 14 | * API to get and set number of LEDs for BlinkStick Flex 15 | * Mode description update 16 | * Display LED count with --info parameter for command line tool 17 | * Option --set-led-count to change the number of LEDs controlled by BlinkStick Flex board 18 | 19 | 1.1.7 (2015-02-26) 20 | ------------------ 21 | 22 | * Use cross platform USB control transfer method for all functions 23 | * Fix for matrix shift functions 24 | * Updated readme with new installation details for Windows 25 | 26 | 1.1.6 (2014-12-22) 27 | ------------------ 28 | 29 | * Always require PyUSB 1.0.0b1, because b2 is broken 30 | 31 | 1.1.5 (2014-11-23) 32 | ------------------ 33 | 34 | * Python 3 compatibility 35 | * Minor bug fixes 36 | 37 | 1.1.4 (2014-10-03) 38 | ------------------ 39 | 40 | * Correctly calculate required led data to be retrieved from the device for get_color function 41 | * Fix to apply commands to all devices and not just one from command line tool 42 | 43 | 1.1.3 (2014-09-24) 44 | ------------------ 45 | 46 | * Display help only once when run without parameters and multiple BlinkSticks plugged in 47 | * Set color with last parameter of command line tool 48 | * --brightness command line option as alias to --limit 49 | * Color parameter no longer requires hash for hex values in command line script 50 | * Do not show usage information for infoblock, info and mode commands 51 | * Command line tool options grouped into logical sections 52 | * Minor tweaks 53 | 54 | 1.1.2 (2014-09-22) 55 | ------------------ 56 | 57 | * Fix rgb typos 58 | 59 | 1.1.1 (2014-09-22) 60 | ------------------ 61 | 62 | * Fix for pulse feature for remapped values 63 | 64 | 1.1.0 (2014-09-22) 65 | ------------------ 66 | 67 | * Module simplification 68 | * Command line tool banner is only visible in help 69 | * Removed --cpu-usage and --connect features from command line tool 70 | * Explicitly specify beta version of pyusb 71 | * Improved documentation of public methods 72 | * Improved README structure and updated reference 73 | * Added ability to limit the brightness of the LED from base BlinkStick class 74 | 75 | 1.0.1 (2014-09-19) 76 | ------------------ 77 | 78 | * Added missing numpy dependency 79 | 80 | 1.0.0 (2014-09-19) 81 | ------------------ 82 | 83 | * Added support for BlinkStick Pro 84 | * Removed grapefruit and replaced with colour 85 | 86 | 0.7.0 (2013-10-03) 87 | ------------------ 88 | 89 | * Added full support for Python in Windows 90 | 91 | 0.6.0 (2013-08-20) 92 | ------------------ 93 | 94 | * Add ability to restore communication to device after it has been unplugged 95 | * Fix broken grapefruit dependency in setup.py 96 | 97 | 0.5.0 (2013-05-05) 98 | ------------------ 99 | 100 | * Added morph, blink and pulse functions 101 | * Implemented inverse mode 102 | * Improved control script to support new features 103 | 104 | 0.3.1 (2013-04-20) 105 | ------------------ 106 | 107 | * Fixed support for Mac OS X 108 | 109 | 0.3.0 (2013-04-16) 110 | ------------------ 111 | 112 | * Removed example scripts as they have been moved to wiki 113 | https://github.com/arvydas/blinkstick-python/wiki 114 | 115 | * Added BlinkStick control script. All features of BlinkStick can now be 116 | controlled with a single blinkstick command 117 | 118 | 0.2.0 (2013-04-08) 119 | ------------------ 120 | 121 | * Add ability to set color by CSS name or hex string 122 | 123 | 0.1.0 (2013-03-27) 124 | ------------------ 125 | 126 | * Initial release. 127 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2013 Arvydas Juskevicius, Agile Innovative Ltd. 2 | All Rights Reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | 3. The name of the author may not be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED 19 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 21 | EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 22 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 23 | OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 24 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 25 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 26 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 27 | OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | # file GENERATED by distutils, do NOT edit 2 | CHANGES.txt 3 | LICENSE.txt 4 | README.rst 5 | setup.py 6 | bin/blinkstick-connect.py 7 | bin/blinkstick-cpu.py 8 | bin/blinkstick-find.py 9 | bin/blinkstick-info.py 10 | bin/blinkstick-infoblock.py 11 | bin/blinkstick-off.py 12 | bin/blinkstick-random.py 13 | blinkstick/__init__.py 14 | blinkstick/blinkstick.py 15 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.txt 2 | include LICENSE.txt 3 | include README.rst 4 | recursive-include bin * 5 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: http://www.blinkstick.com/images/logos/blinkstick-python.png 2 | :alt: BlinkStick Python 3 | 4 | BlinkStick Python interface to control devices connected to the 5 | computer. 6 | 7 | What is BlinkStick? It's a smart USB LED pixel. More info about it here: 8 | 9 | http://www.blinkstick.com 10 | 11 | Resources 12 | --------- 13 | 14 | - `Code repository on GitHub `_ 15 | - `API reference documentation `_ 16 | - `Code Examples `_ 17 | 18 | Requirements 19 | ------------ 20 | 21 | - Python 22 | - BlinkStick pip module 23 | - Libusb for Mac OSX 24 | 25 | Requirements Installation 26 | ------------------------- 27 | 28 | Linux 29 | ````` 30 | 31 | Install pip (Python package management software): 32 | 33 | :: 34 | 35 | sudo apt-get install python-pip 36 | 37 | Mac OS X 38 | ```````` 39 | 40 | Install libusb with `homebrew `_: 41 | 42 | :: 43 | 44 | brew install libusb 45 | 46 | Install pip 47 | 48 | :: 49 | 50 | sudo easy_install pip 51 | 52 | Known Errors 53 | ^^^^^^^^^^^^ 54 | 55 | :: 56 | 57 | ValueError: No backend available 58 | 59 | This means that the Python usb module cannot find your installation of libusb. 60 | It seems to be an issue when you have ``homebrew`` installed somewhere that is 61 | not expected. 62 | 63 | It can be mitigated with 64 | 65 | :: 66 | 67 | sudo ln -s `brew --prefix`/lib/libusb-* /usr/local/lib/ 68 | 69 | Microsoft Windows 70 | ````````````````` 71 | 72 | * Download and install `Python `_ 2.7.9 or any later version 73 | * During the installation, make sure you select "Add python.exe to Path" to install on local hard drive 74 | 75 | Python 2.7.9 and later already comes with pip making it very easy to install BlinkStick Python package on Windows. 76 | 77 | BlinkStick package Installation 78 | ------------------------------- 79 | 80 | Linux and Mac OS X 81 | `````````````````` 82 | 83 | Install blinkstick Python package with pip: 84 | 85 | :: 86 | 87 | sudo pip install blinkstick 88 | 89 | 90 | Microsoft Windows 91 | ````````````````` 92 | 93 | Open commandline environment by using Win+R keyboard shortcut and typing in: 94 | 95 | :: 96 | 97 | cmd 98 | 99 | Assuming that Python was installed into C:\\Python27 folder, type in the 100 | following into the command window: 101 | 102 | :: 103 | 104 | C:\Python27\Scripts\pip.exe install blinkstick 105 | 106 | Command line tool 107 | ----------------- 108 | 109 | Together with the Python module an additional command line tool is 110 | installed to control BlinkSticks. 111 | 112 | :: 113 | 114 | blinkstick --pulse red 115 | 116 | 117 | You can find more details about command line tool options and usage 118 | examples in the `wiki `_. 119 | 120 | Permission problems in Linux and Mac OS X 121 | ----------------------------------------- 122 | 123 | If the script returns with an error 124 | 125 | :: 126 | 127 | Access denied (insufficient permissions) 128 | 129 | You can either run the script with sudo, for example: 130 | 131 | :: 132 | 133 | sudo blinkstick --set-color random 134 | 135 | Or you can add a udev rule to allow any user to access the device 136 | without root permissions with this single command. 137 | 138 | :: 139 | 140 | sudo blinkstick --add-udev-rule 141 | 142 | There is also another equivalent command that does exactly the same thing: 143 | 144 | :: 145 | 146 | echo "SUBSYSTEM==\"usb\", ATTR{idVendor}==\"20a0\", ATTR{idProduct}==\"41e5\", MODE:=\"0666\"" | sudo tee /etc/udev/rules.d/85-blinkstick.rules 147 | 148 | Reboot computer after you have added the command and all users will have 149 | permissions to access the device without the need of root permissions. 150 | 151 | Maintainers 152 | ----------- 153 | 154 | - Arvydas Juskevicius - http://twitter.com/arvydev 155 | - Rob Berwick - http://twitter.com/robberwick 156 | 157 | -------------------------------------------------------------------------------- /bin/blinkstick: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from optparse import OptionParser, IndentedHelpFormatter, OptionGroup 4 | from blinkstick import blinkstick 5 | import textwrap 6 | import sys 7 | import time 8 | import logging 9 | logging.basicConfig() 10 | 11 | 12 | class IndentedHelpFormatterWithNL(IndentedHelpFormatter): 13 | def format_description(self, description): 14 | if not description: return "" 15 | 16 | desc_width = self.width - self.current_indent 17 | indent = " " * self.current_indent 18 | # the above is still the same 19 | bits = description.split('\n') 20 | formatted_bits = [ 21 | textwrap.fill(bit, 22 | desc_width, 23 | initial_indent=indent, 24 | subsequent_indent=indent) 25 | for bit in bits] 26 | result = "\n".join(formatted_bits) + "\n" 27 | return result 28 | 29 | def format_option(self, option): 30 | # The help for each option consists of two parts: 31 | # * the opt strings and metavars 32 | # eg. ("-x", or "-fFILENAME, --file=FILENAME") 33 | # * the user-supplied help string 34 | # eg. ("turn on expert mode", "read data from FILENAME") 35 | # 36 | # If possible, we write both of these on the same line: 37 | # -x turn on expert mode 38 | # 39 | # But if the opt string list is too long, we put the help 40 | # string on a second line, indented to the same column it would 41 | # start in if it fit on the first line. 42 | # -fFILENAME, --file=FILENAME 43 | # read data from FILENAME 44 | result = [] 45 | opts = self.option_strings[option] 46 | opt_width = self.help_position - self.current_indent - 2 47 | 48 | if len(opts) > opt_width: 49 | opts = "%*s%s\n" % (self.current_indent, "", opts) 50 | indent_first = self.help_position 51 | else: # start help on same line as opts 52 | opts = "%*s%-*s " % (self.current_indent, "", opt_width, opts) 53 | indent_first = 0 54 | 55 | result.append(opts) 56 | 57 | if option.help: 58 | help_text = self.expand_default(option) 59 | # Everything is the same up through here 60 | help_lines = [] 61 | for para in help_text.split("\n"): 62 | help_lines.extend(textwrap.wrap(para, self.help_width)) 63 | # Everything is the same after here 64 | result.append("%*s%s\n" % ( 65 | indent_first, "", help_lines[0])) 66 | result.extend(["%*s%s\n" % (self.help_position, "", line) 67 | for line in help_lines[1:]]) 68 | elif opts[-1] != "\n": 69 | result.append("\n") 70 | return "".join(result) 71 | 72 | def format_usage(self, usage): 73 | return "BlinkStick control script %s\n(c) Agile Innovative Ltd 2013-2014\n\n%s" % (blinkstick.get_blinkstick_package_version(), IndentedHelpFormatter.format_usage(self, usage)) 74 | 75 | 76 | def print_info(stick): 77 | print("Found device:") 78 | print(" Manufacturer: {0}".format(stick.get_manufacturer())) 79 | print(" Description: {0}".format(stick.get_description())) 80 | print(" Variant: {0}".format(stick.get_variant_string())) 81 | print(" Serial: {0}".format(stick.get_serial())) 82 | print(" Current Color: {0}".format(stick.get_color(color_format="hex"))) 83 | print(" Mode: {0}".format(stick.get_mode())) 84 | if stick.get_variant() == blinkstick.BlinkStick.BLINKSTICK_FLEX: 85 | try: 86 | count = stick.get_led_count() 87 | except: 88 | count = -1 89 | 90 | if count == -1: 91 | count = "Error" 92 | print(" LED conf: {0}".format(count)) 93 | print(" Info Block 1: {0}".format(stick.get_info_block1())) 94 | print(" Info Block 2: {0}".format(stick.get_info_block2())) 95 | 96 | 97 | def main(): 98 | global options 99 | global sticks 100 | 101 | parser = OptionParser(usage="usage: %prog [options] [color]", 102 | formatter=IndentedHelpFormatterWithNL() 103 | ) 104 | 105 | parser.add_option("-i", "--info", 106 | action="store_true", dest="info", 107 | help="Display BlinkStick info") 108 | 109 | parser.add_option("-s", "--serial", 110 | dest="serial", 111 | help="Select device by serial number. If unspecified, action will be performed on all BlinkSticks.") 112 | 113 | parser.add_option("-v", "--verbose", 114 | action="store_true", dest="verbose", 115 | help="Display debug output") 116 | 117 | 118 | group = OptionGroup(parser, "Change color", 119 | "These options control the color of the device ") 120 | 121 | group.add_option("--channel", 122 | default=0, dest="channel", 123 | help="Select channel. Applies only to BlinkStick Pro.") 124 | 125 | group.add_option("--index", 126 | default=0, dest="index", 127 | help="Select index. Applies only to BlinkStick Pro.") 128 | 129 | group.add_option("--brightness", 130 | default=100, dest="limit", 131 | help="Limit the brightness of the color 0..100") 132 | 133 | group.add_option("--limit", 134 | default=100, dest="limit", 135 | help="Alias to --brightness option") 136 | 137 | group.add_option("--set-color", 138 | dest="color", 139 | help="Set the color for the device. This can also be the last argument for the script. " 140 | "The value can either be a named color, hex value, 'random' or 'off'.\n\n" 141 | "CSS color names are defined http://www.w3.org/TR/css3-color/ e.g. red, green, blue. " 142 | "Specify color using hexadecimal color value e.g. 'FF3366'") 143 | group.add_option("--inverse", 144 | action="store_true", dest="inverse", 145 | help="Control BlinkSticks in inverse mode") 146 | 147 | group.add_option("--set-led-count", 148 | dest="led_count", 149 | help="Set the number of LEDs to control for supported devices.") 150 | 151 | parser.add_option_group(group) 152 | 153 | group = OptionGroup(parser, "Control animations", 154 | "These options will blink, morph or pulse selected color. ") 155 | 156 | group.add_option("--blink", 157 | dest="blink", 158 | action='store_true', 159 | help="Blink LED (requires --set-color or color set as last argument, and optionally --delay)") 160 | 161 | group.add_option("--pulse", 162 | dest="pulse", 163 | action='store_true', 164 | help="Pulse LED (requires --set-color or color set as last argument, and optionally --duration).") 165 | 166 | group.add_option("--morph", 167 | dest="morph", 168 | action='store_true', 169 | help="Morph to specified color (requires --set-color or color set as last argument, and optionally --duration).") 170 | 171 | group.add_option("--duration", 172 | dest="duration", 173 | default=1000, 174 | help="Set duration of transition in milliseconds (use with --morph and --pulse).") 175 | 176 | group.add_option("--delay", 177 | dest="delay", 178 | default=500, 179 | help="Set time in milliseconds to light LED for (use with --blink).") 180 | 181 | group.add_option("--repeats", 182 | dest="repeats", 183 | default=1, 184 | help="Number of repetitions (use with --blink and --pulse).") 185 | 186 | parser.add_option_group(group) 187 | 188 | group = OptionGroup(parser, "Device data and behaviour", 189 | "These options will change device mode and data stored internally. ") 190 | 191 | group.add_option("--set-mode", 192 | default=0, dest="mode", 193 | help="Set mode for BlinkStick Pro:\n\n 0 - default\n\n 1 - inverse\n\n 2 - ws2812\n\n 3 - ws2812 mirror") 194 | 195 | group.add_option("--set-infoblock1", 196 | dest="infoblock1", 197 | help="Set the first info block for the device.") 198 | 199 | group.add_option("--set-infoblock2", 200 | dest="infoblock2", 201 | help="Set the second info block for the device.") 202 | 203 | parser.add_option_group(group) 204 | 205 | group = OptionGroup(parser, "Advanced options", 206 | "") 207 | 208 | group.add_option("--add-udev-rule", 209 | action="store_true", dest="udev", 210 | help="Add udev rule to access BlinkSticks without root permissions. Must be run as root.") 211 | 212 | parser.add_option_group(group) 213 | 214 | 215 | 216 | (options, args) = parser.parse_args() 217 | 218 | if options.serial is None: 219 | sticks = blinkstick.find_all() 220 | else: 221 | sticks = [blinkstick.find_by_serial(options.serial)] 222 | 223 | if len(sticks) == 0: 224 | print("BlinkStick with serial number " + options.device + " not found...") 225 | return 64 226 | 227 | #Global action 228 | if options.udev: 229 | 230 | try: 231 | filename = "/etc/udev/rules.d/85-blinkstick.rules" 232 | file = open(filename, 'w') 233 | file.write('SUBSYSTEM=="usb", ATTR{idVendor}=="20a0", ATTR{idProduct}=="41e5", MODE:="0666"') 234 | file.close() 235 | 236 | print("Rule added to {0}".format(filename)) 237 | except IOError as e: 238 | print(str(e)) 239 | print("Make sure you run this script as root: sudo blinkstick --add-udev-rule") 240 | return 64 241 | 242 | print("Reboot your computer for changes to take effect") 243 | return 0 244 | 245 | for stick in sticks: 246 | if options.inverse: 247 | stick.set_inverse(True) 248 | 249 | stick.set_max_rgb_value(int(float(options.limit) / 100.0 * 255)) 250 | 251 | stick.set_error_reporting(False) 252 | 253 | #Actions here work on all BlinkSticks 254 | for stick in sticks: 255 | if options.infoblock1: 256 | stick.set_info_block1(options.infoblock1) 257 | 258 | if options.infoblock2: 259 | stick.set_info_block2(options.infoblock2) 260 | 261 | if options.mode: 262 | if options.mode == "0" or options.mode == "1" or options.mode == "2" or options.mode == "3": 263 | stick.set_mode(int(options.mode)) 264 | else: 265 | print("Error: Invalid mode parameter value") 266 | 267 | elif options.led_count: 268 | led_count = int(options.led_count) 269 | 270 | if led_count > 0 and led_count <= 32: 271 | stick.set_led_count(led_count) 272 | else: 273 | print("Error: Invalid led-count parameter value") 274 | 275 | elif options.info: 276 | print_info(stick) 277 | elif options.color or len(args) > 0: 278 | if options.color: 279 | color = options.color 280 | else: 281 | color = args[0] 282 | 283 | # determine color 284 | fargs = {} 285 | if color.startswith('#'): 286 | fargs['hex'] = color 287 | elif color == "random": 288 | fargs['name'] = 'random' 289 | elif color == "off": 290 | fargs['hex'] = "#000000" 291 | else: 292 | if len(color) == 6: 293 | # If color contains 6 chars check if it's hex 294 | try: 295 | int(color, 16) 296 | fargs['hex'] = "#" + color 297 | except: 298 | fargs['name'] = color 299 | else: 300 | fargs['name'] = color 301 | 302 | fargs['index'] = int(options.index) 303 | fargs['channel'] = int(options.channel) 304 | 305 | # handle blink/pulse/morph 306 | func = stick.set_color 307 | if options.blink: 308 | func = stick.blink 309 | fargs['delay'] = options.delay 310 | fargs['repeats'] = int(options.repeats) 311 | elif options.pulse: 312 | func = stick.pulse 313 | fargs['duration'] = options.duration 314 | fargs['repeats'] = int(options.repeats) 315 | elif options.morph: 316 | func = stick.morph 317 | fargs['duration'] = options.duration 318 | 319 | func(**fargs) 320 | 321 | 322 | else: 323 | parser.print_help() 324 | return 0 325 | 326 | return 0 327 | 328 | 329 | if __name__ == "__main__": 330 | sys.exit(main()) 331 | -------------------------------------------------------------------------------- /blinkstick/__init__.py: -------------------------------------------------------------------------------- 1 | from ._version import __version__ 2 | __author__ = 'Rob Berwick, Arvydas Juskevicius' 3 | __copyright__ = "Copyright 2013-2014, Agile Innovative Ltd" 4 | __credits__ = ["Rob Berwick", "Arvydas Juskevicius", "Sam J Sharpe"] 5 | __license__ = "See LICENSE.txt" 6 | __maintainer__ = "Arvydas Juskevicius" 7 | __email__ = "arvydas@agileinnovative.co.uk" 8 | __status__ = "Production" 9 | -------------------------------------------------------------------------------- /blinkstick/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.2.0" 2 | -------------------------------------------------------------------------------- /blinkstick/blinkstick.py: -------------------------------------------------------------------------------- 1 | from ._version import __version__ 2 | import time 3 | import sys 4 | import re 5 | try: 6 | from collections.abc import Callable 7 | except ImportError: 8 | from collections import Callable 9 | 10 | if sys.platform == "win32": 11 | import pywinusb.hid as hid 12 | from ctypes import * 13 | else: 14 | import usb.core 15 | import usb.util 16 | 17 | from random import randint 18 | 19 | """ 20 | Main module to control BlinkStick and BlinkStick Pro devices. 21 | """ 22 | 23 | VENDOR_ID = 0x20a0 24 | PRODUCT_ID = 0x41e5 25 | 26 | class BlinkStickException(Exception): 27 | pass 28 | 29 | 30 | class BlinkStick(object): 31 | """ 32 | BlinkStick class is designed to control regular BlinkStick devices, or BlinkStick Pro 33 | devices in Normal or Inverse modes. Please refer to L{BlinkStick.set_mode} for more details 34 | about BlinkStick Pro device modes. 35 | 36 | Code examples on how you can use this class are available here: 37 | 38 | U{https://github.com/arvydas/blinkstick-python/wiki} 39 | """ 40 | 41 | _names_to_hex = {'aliceblue': '#f0f8ff', 42 | 'antiquewhite': '#faebd7', 43 | 'aqua': '#00ffff', 44 | 'aquamarine': '#7fffd4', 45 | 'azure': '#f0ffff', 46 | 'beige': '#f5f5dc', 47 | 'bisque': '#ffe4c4', 48 | 'black': '#000000', 49 | 'blanchedalmond': '#ffebcd', 50 | 'blue': '#0000ff', 51 | 'blueviolet': '#8a2be2', 52 | 'brown': '#a52a2a', 53 | 'burlywood': '#deb887', 54 | 'cadetblue': '#5f9ea0', 55 | 'chartreuse': '#7fff00', 56 | 'chocolate': '#d2691e', 57 | 'coral': '#ff7f50', 58 | 'cornflowerblue': '#6495ed', 59 | 'cornsilk': '#fff8dc', 60 | 'crimson': '#dc143c', 61 | 'cyan': '#00ffff', 62 | 'darkblue': '#00008b', 63 | 'darkcyan': '#008b8b', 64 | 'darkgoldenrod': '#b8860b', 65 | 'darkgray': '#a9a9a9', 66 | 'darkgrey': '#a9a9a9', 67 | 'darkgreen': '#006400', 68 | 'darkkhaki': '#bdb76b', 69 | 'darkmagenta': '#8b008b', 70 | 'darkolivegreen': '#556b2f', 71 | 'darkorange': '#ff8c00', 72 | 'darkorchid': '#9932cc', 73 | 'darkred': '#8b0000', 74 | 'darksalmon': '#e9967a', 75 | 'darkseagreen': '#8fbc8f', 76 | 'darkslateblue': '#483d8b', 77 | 'darkslategray': '#2f4f4f', 78 | 'darkslategrey': '#2f4f4f', 79 | 'darkturquoise': '#00ced1', 80 | 'darkviolet': '#9400d3', 81 | 'deeppink': '#ff1493', 82 | 'deepskyblue': '#00bfff', 83 | 'dimgray': '#696969', 84 | 'dimgrey': '#696969', 85 | 'dodgerblue': '#1e90ff', 86 | 'firebrick': '#b22222', 87 | 'floralwhite': '#fffaf0', 88 | 'forestgreen': '#228b22', 89 | 'fuchsia': '#ff00ff', 90 | 'gainsboro': '#dcdcdc', 91 | 'ghostwhite': '#f8f8ff', 92 | 'gold': '#ffd700', 93 | 'goldenrod': '#daa520', 94 | 'gray': '#808080', 95 | 'grey': '#808080', 96 | 'green': '#008000', 97 | 'greenyellow': '#adff2f', 98 | 'honeydew': '#f0fff0', 99 | 'hotpink': '#ff69b4', 100 | 'indianred': '#cd5c5c', 101 | 'indigo': '#4b0082', 102 | 'ivory': '#fffff0', 103 | 'khaki': '#f0e68c', 104 | 'lavender': '#e6e6fa', 105 | 'lavenderblush': '#fff0f5', 106 | 'lawngreen': '#7cfc00', 107 | 'lemonchiffon': '#fffacd', 108 | 'lightblue': '#add8e6', 109 | 'lightcoral': '#f08080', 110 | 'lightcyan': '#e0ffff', 111 | 'lightgoldenrodyellow': '#fafad2', 112 | 'lightgray': '#d3d3d3', 113 | 'lightgrey': '#d3d3d3', 114 | 'lightgreen': '#90ee90', 115 | 'lightpink': '#ffb6c1', 116 | 'lightsalmon': '#ffa07a', 117 | 'lightseagreen': '#20b2aa', 118 | 'lightskyblue': '#87cefa', 119 | 'lightslategray': '#778899', 120 | 'lightslategrey': '#778899', 121 | 'lightsteelblue': '#b0c4de', 122 | 'lightyellow': '#ffffe0', 123 | 'lime': '#00ff00', 124 | 'limegreen': '#32cd32', 125 | 'linen': '#faf0e6', 126 | 'magenta': '#ff00ff', 127 | 'maroon': '#800000', 128 | 'mediumaquamarine': '#66cdaa', 129 | 'mediumblue': '#0000cd', 130 | 'mediumorchid': '#ba55d3', 131 | 'mediumpurple': '#9370d8', 132 | 'mediumseagreen': '#3cb371', 133 | 'mediumslateblue': '#7b68ee', 134 | 'mediumspringgreen': '#00fa9a', 135 | 'mediumturquoise': '#48d1cc', 136 | 'mediumvioletred': '#c71585', 137 | 'midnightblue': '#191970', 138 | 'mintcream': '#f5fffa', 139 | 'mistyrose': '#ffe4e1', 140 | 'moccasin': '#ffe4b5', 141 | 'navajowhite': '#ffdead', 142 | 'navy': '#000080', 143 | 'oldlace': '#fdf5e6', 144 | 'olive': '#808000', 145 | 'olivedrab': '#6b8e23', 146 | 'orange': '#ffa500', 147 | 'orangered': '#ff4500', 148 | 'orchid': '#da70d6', 149 | 'palegoldenrod': '#eee8aa', 150 | 'palegreen': '#98fb98', 151 | 'paleturquoise': '#afeeee', 152 | 'palevioletred': '#d87093', 153 | 'papayawhip': '#ffefd5', 154 | 'peachpuff': '#ffdab9', 155 | 'peru': '#cd853f', 156 | 'pink': '#ffc0cb', 157 | 'plum': '#dda0dd', 158 | 'powderblue': '#b0e0e6', 159 | 'purple': '#800080', 160 | 'red': '#ff0000', 161 | 'rosybrown': '#bc8f8f', 162 | 'royalblue': '#4169e1', 163 | 'saddlebrown': '#8b4513', 164 | 'salmon': '#fa8072', 165 | 'sandybrown': '#f4a460', 166 | 'seagreen': '#2e8b57', 167 | 'seashell': '#fff5ee', 168 | 'sienna': '#a0522d', 169 | 'silver': '#c0c0c0', 170 | 'skyblue': '#87ceeb', 171 | 'slateblue': '#6a5acd', 172 | 'slategray': '#708090', 173 | 'slategrey': '#708090', 174 | 'snow': '#fffafa', 175 | 'springgreen': '#00ff7f', 176 | 'steelblue': '#4682b4', 177 | 'tan': '#d2b48c', 178 | 'teal': '#008080', 179 | 'thistle': '#d8bfd8', 180 | 'tomato': '#ff6347', 181 | 'turquoise': '#40e0d0', 182 | 'violet': '#ee82ee', 183 | 'wheat': '#f5deb3', 184 | 'white': '#ffffff', 185 | 'whitesmoke': '#f5f5f5', 186 | 'yellow': '#ffff00', 187 | 'yellowgreen': '#9acd32'} 188 | 189 | HEX_COLOR_RE = re.compile(r'^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$') 190 | 191 | UNKNOWN = 0 192 | BLINKSTICK = 1 193 | BLINKSTICK_PRO = 2 194 | BLINKSTICK_STRIP = 3 195 | BLINKSTICK_SQUARE = 4 196 | BLINKSTICK_NANO = 5 197 | BLINKSTICK_FLEX = 6 198 | 199 | inverse = False 200 | error_reporting = True 201 | max_rgb_value = 255 202 | 203 | def __init__(self, device=None, error_reporting=True): 204 | """ 205 | Constructor for the class. 206 | 207 | @type error_reporting: Boolean 208 | @param error_reporting: display errors if they occur during communication with the device 209 | """ 210 | self.error_reporting = error_reporting 211 | 212 | if device: 213 | self.device = device 214 | if sys.platform == "win32": 215 | self.device.open() 216 | self.reports = self.device.find_feature_reports() 217 | else: 218 | self.open_device(device) 219 | 220 | self.bs_serial = self.get_serial() 221 | 222 | def _usb_get_string(self, device, index): 223 | try: 224 | return usb.util.get_string(device, index, 1033) 225 | except usb.USBError: 226 | # Could not communicate with BlinkStick device 227 | # attempt to find it again based on serial 228 | 229 | if self._refresh_device(): 230 | return usb.util.get_string(self.device, index, 1033) 231 | else: 232 | raise BlinkStickException("Could not communicate with BlinkStick {0} - it may have been removed".format(self.bs_serial)) 233 | 234 | def _usb_ctrl_transfer(self, bmRequestType, bRequest, wValue, wIndex, data_or_wLength): 235 | if sys.platform == "win32": 236 | if bmRequestType == 0x20: 237 | if sys.version_info[0] < 3: 238 | data = (c_ubyte * len(data_or_wLength))(*[c_ubyte(ord(c)) for c in data_or_wLength]) 239 | else: 240 | data = (c_ubyte * len(data_or_wLength))(*[c_ubyte(c) for c in data_or_wLength]) 241 | data[0] = wValue 242 | if not self.device.send_feature_report(data): 243 | if self._refresh_device(): 244 | self.device.send_feature_report(data) 245 | else: 246 | raise BlinkStickException("Could not communicate with BlinkStick {0} - it may have been removed".format(self.bs_serial)) 247 | 248 | elif bmRequestType == 0x80 | 0x20: 249 | return self.reports[wValue - 1].get() 250 | else: 251 | try: 252 | return self.device.ctrl_transfer(bmRequestType, bRequest, wValue, wIndex, data_or_wLength) 253 | except usb.USBError: 254 | # Could not communicate with BlinkStick device 255 | # attempt to find it again based on serial 256 | 257 | if self._refresh_device(): 258 | return self.device.ctrl_transfer(bmRequestType, bRequest, wValue, wIndex, data_or_wLength) 259 | else: 260 | raise BlinkStickException("Could not communicate with BlinkStick {0} - it may have been removed".format(self.bs_serial)) 261 | 262 | def _refresh_device(self): 263 | if not hasattr(self, 'bs_serial'): 264 | return False 265 | d = find_by_serial(self.bs_serial) 266 | if d: 267 | self.device = d.device 268 | return True 269 | 270 | def get_serial(self): 271 | """ 272 | Returns the serial number of device.:: 273 | 274 | BSnnnnnn-1.0 275 | || | | |- Software minor version 276 | || | |--- Software major version 277 | || |-------- Denotes sequential number 278 | ||----------- Denotes BlinkStick device 279 | 280 | Software version defines the capabilities of the device 281 | 282 | @rtype: str 283 | @return: Serial number of the device 284 | """ 285 | if sys.platform == "win32": 286 | return self.device.serial_number 287 | else: 288 | return self._usb_get_string(self.device, 3) 289 | 290 | def get_manufacturer(self): 291 | """ 292 | Get the manufacturer of the device 293 | 294 | @rtype: str 295 | @return: Device manufacturer's name 296 | """ 297 | if sys.platform == "win32": 298 | return self.device.vendor_name 299 | else: 300 | return self._usb_get_string(self.device, 1) 301 | 302 | def get_variant(self): 303 | """ 304 | Get the product variant of the device. 305 | 306 | @rtype: int 307 | @return: BlinkStick.UNKNOWN, BlinkStick.BLINKSTICK, BlinkStick.BLINKSTICK_PRO and etc 308 | """ 309 | 310 | serial = self.get_serial() 311 | major = serial[-3] 312 | minor = serial[-1] 313 | 314 | if sys.platform == "win32": 315 | version_attribute = self.device.version_number 316 | else: 317 | version_attribute = self.device.bcdDevice 318 | 319 | if major == "1": 320 | return self.BLINKSTICK 321 | elif major == "2": 322 | return self.BLINKSTICK_PRO 323 | elif major == "3": 324 | if version_attribute == 0x200: 325 | return self.BLINKSTICK_SQUARE 326 | elif version_attribute == 0x201: 327 | return self.BLINKSTICK_STRIP 328 | elif version_attribute == 0x202: 329 | return self.BLINKSTICK_NANO 330 | elif version_attribute == 0x203: 331 | return self.BLINKSTICK_FLEX 332 | else: 333 | return self.UNKNOWN 334 | else: 335 | return self.UNKNOWN 336 | 337 | def get_variant_string(self): 338 | """ 339 | Get the product variant of the device as string. 340 | 341 | @rtype: string 342 | @return: "BlinkStick", "BlinkStick Pro", etc 343 | """ 344 | product = self.get_variant() 345 | 346 | if product == self.BLINKSTICK: 347 | return "BlinkStick" 348 | elif product == self.BLINKSTICK_PRO: 349 | return "BlinkStick Pro" 350 | elif product == self.BLINKSTICK_SQUARE: 351 | return "BlinkStick Square" 352 | elif product == self.BLINKSTICK_STRIP: 353 | return "BlinkStick Strip" 354 | elif product == self.BLINKSTICK_NANO: 355 | return "BlinkStick Nano" 356 | elif product == self.BLINKSTICK_FLEX: 357 | return "BlinkStick Flex" 358 | 359 | return "Unknown" 360 | 361 | def get_description(self): 362 | """ 363 | Get the description of the device 364 | 365 | @rtype: str 366 | @return: Device description 367 | """ 368 | if sys.platform == "win32": 369 | return self.device.product_name 370 | else: 371 | return self._usb_get_string(self.device, 2) 372 | 373 | def set_error_reporting(self, error_reporting): 374 | """ 375 | Enable or disable error reporting 376 | 377 | @type error_reporting: Boolean 378 | @param error_reporting: display errors if they occur during communication with the device 379 | """ 380 | self.error_reporting = error_reporting 381 | 382 | def set_color(self, channel=0, index=0, red=0, green=0, blue=0, name=None, hex=None): 383 | """ 384 | Set the color to the device as RGB 385 | 386 | @type red: int 387 | @param red: Red color intensity 0 is off, 255 is full red intensity 388 | @type green: int 389 | @param green: Green color intensity 0 is off, 255 is full green intensity 390 | @type blue: int 391 | @param blue: Blue color intensity 0 is off, 255 is full blue intensity 392 | @type name: str 393 | @param name: Use CSS color name as defined here: U{http://www.w3.org/TR/css3-color/} 394 | @type hex: str 395 | @param hex: Specify color using hexadecimal color value e.g. '#FF3366' 396 | """ 397 | 398 | red, green, blue = self._determine_rgb(red=red, green=green, blue=blue, name=name, hex=hex) 399 | 400 | r = int(round(red, 3)) 401 | g = int(round(green, 3)) 402 | b = int(round(blue, 3)) 403 | 404 | if self.inverse: 405 | r, g, b = 255 - r, 255 - g, 255 - b 406 | 407 | if index == 0 and channel == 0: 408 | control_string = bytes(bytearray([0, r, g, b])) 409 | report_id = 0x0001 410 | else: 411 | control_string = bytes(bytearray([5, channel, index, r, g, b])) 412 | report_id = 0x0005 413 | 414 | if self.error_reporting: 415 | self._usb_ctrl_transfer(0x20, 0x9, report_id, 0, control_string) 416 | else: 417 | try: 418 | self._usb_ctrl_transfer(0x20, 0x9, report_id, 0, control_string) 419 | except Exception: 420 | pass 421 | 422 | def _determine_rgb(self, red=0, green=0, blue=0, name=None, hex=None): 423 | 424 | try: 425 | if name: 426 | # Special case for name="random" 427 | if name == "random": 428 | red = randint(0, 255) 429 | green = randint(0, 255) 430 | blue = randint(0, 255) 431 | else: 432 | red, green, blue = self._name_to_rgb(name) 433 | elif hex: 434 | red, green, blue = self._hex_to_rgb(hex) 435 | except ValueError: 436 | red = green = blue = 0 437 | 438 | red, green, blue = _remap_rgb_value([red, green, blue], self.max_rgb_value) 439 | 440 | # TODO - do smarts to determine input type from red var in case it is not int 441 | 442 | return red, green, blue 443 | 444 | def _get_color_rgb(self, index=0): 445 | if index == 0: 446 | device_bytes = self._usb_ctrl_transfer(0x80 | 0x20, 0x1, 0x0001, 0, 33) 447 | if self.inverse: 448 | return [255 - device_bytes[1], 255 - device_bytes[2], 255 - device_bytes[3]] 449 | else: 450 | return [device_bytes[1], device_bytes[2], device_bytes[3]] 451 | else: 452 | data = self.get_led_data((index + 1) * 3) 453 | 454 | return [data[index * 3 + 1], data[index * 3], data[index * 3 + 2]] 455 | 456 | def _get_color_hex(self, index=0): 457 | r, g, b = self._get_color_rgb(index) 458 | return '#%02x%02x%02x' % (r, g, b) 459 | 460 | def get_color(self, index=0, color_format='rgb'): 461 | """ 462 | Get the current device color in the defined format. 463 | 464 | Currently supported formats: 465 | 466 | 1. rgb (default) - Returns values as 3-tuple (r,g,b) 467 | 2. hex - returns current device color as hexadecimal string 468 | 469 | >>> b = blinkstick.find_first() 470 | >>> b.set_color(red=255,green=0,blue=0) 471 | >>> (r,g,b) = b.get_color() # Get color as rbg tuple 472 | (255,0,0) 473 | >>> hex = b.get_color(color_format='hex') # Get color as hex string 474 | '#ff0000' 475 | 476 | @type index: int 477 | @param index: the index of the LED 478 | @type color_format: str 479 | @param color_format: "rgb" or "hex". Defaults to "rgb". 480 | 481 | @rtype: (int, int, int) or str 482 | @return: Either 3-tuple for R, G and B values, or hex string 483 | """ 484 | 485 | # Attempt to find a function to return the appropriate format 486 | get_color_func = getattr(self, "_get_color_%s" % color_format, self._get_color_rgb) 487 | if isinstance(get_color_func, Callable): 488 | return get_color_func(index) 489 | else: 490 | # Should never get here, as we should always default to self._get_color_rgb 491 | raise BlinkStickException("Could not return current color in format %s" % color_format) 492 | 493 | def _determine_report_id(self, led_count): 494 | report_id = 9 495 | max_leds = 64 496 | 497 | if led_count <= 8 * 3: 498 | max_leds = 8 499 | report_id = 6 500 | elif led_count <= 16 * 3: 501 | max_leds = 16 502 | report_id = 7 503 | elif led_count <= 32 * 3: 504 | max_leds = 32 505 | report_id = 8 506 | elif led_count <= 64 * 3: 507 | max_leds = 64 508 | report_id = 9 509 | 510 | return report_id, max_leds 511 | 512 | def set_led_data(self, channel, data): 513 | """ 514 | Send LED data frame. 515 | 516 | @type channel: int 517 | @param channel: the channel which to send data to (R=0, G=1, B=2) 518 | @type data: int[0..64*3] 519 | @param data: The LED data frame in GRB format 520 | """ 521 | 522 | report_id, max_leds = self._determine_report_id(len(data)) 523 | 524 | report = [0, channel] 525 | 526 | for i in range(0, max_leds * 3): 527 | if len(data) > i: 528 | report.append(data[i]) 529 | else: 530 | report.append(0) 531 | 532 | self._usb_ctrl_transfer(0x20, 0x9, report_id, 0, bytes(bytearray(report))) 533 | 534 | def get_led_data(self, count): 535 | """ 536 | Get LED data frame on the device. 537 | 538 | @type count: int 539 | @param count: How much data to retrieve. Can be in the range of 0..64*3 540 | @rtype: int[0..64*3] 541 | @return: LED data currently stored in the RAM of the device 542 | """ 543 | 544 | report_id, max_leds = self._determine_report_id(count) 545 | 546 | device_bytes = self._usb_ctrl_transfer(0x80 | 0x20, 0x1, report_id, 0, max_leds * 3 + 2) 547 | 548 | return device_bytes[2: 2 + count * 3] 549 | 550 | def set_mode(self, mode): 551 | """ 552 | Set device mode for BlinkStick Pro. Device currently supports the following modes: 553 | 554 | - 0 - (default) use R, G and B channels to control single RGB LED 555 | - 1 - same as 0, but inverse mode 556 | - 2 - control up to 64 WS2812 individual LEDs per each R, G and B channel 557 | 558 | You can find out more about BlinkStick Pro modes: 559 | 560 | U{http://www.blinkstick.com/help/tutorials/blinkstick-pro-modes} 561 | 562 | @type mode: int 563 | @param mode: Device mode to set 564 | """ 565 | control_string = bytes(bytearray([4, mode])) 566 | 567 | self._usb_ctrl_transfer(0x20, 0x9, 0x0004, 0, control_string) 568 | 569 | def get_mode(self): 570 | """ 571 | Get BlinkStick Pro mode. Device currently supports the following modes: 572 | 573 | - 0 - (default) use R, G and B channels to control single RGB LED 574 | - 1 - same as 0, but inverse mode 575 | - 2 - control up to 64 WS2812 individual LEDs per each R, G and B channel 576 | 577 | You can find out more about BlinkStick Pro modes: 578 | 579 | U{http://www.blinkstick.com/help/tutorials/blinkstick-pro-modes} 580 | 581 | @rtype: int 582 | @return: Device mode 583 | """ 584 | 585 | device_bytes = self._usb_ctrl_transfer(0x80 | 0x20, 0x1, 0x0004, 0, 2) 586 | 587 | if len(device_bytes) >= 2: 588 | return device_bytes[1] 589 | else: 590 | return -1 591 | 592 | def set_led_count(self, count): 593 | """ 594 | Set number of LEDs for supported devices 595 | 596 | @type count: int 597 | @param count: number of LEDs to control 598 | """ 599 | control_string = bytes(bytearray([0x81, count])) 600 | 601 | self._usb_ctrl_transfer(0x20, 0x9, 0x81, 0, control_string) 602 | 603 | 604 | def get_led_count(self): 605 | """ 606 | Get number of LEDs for supported devices 607 | 608 | @rtype: int 609 | @return: Number of LEDs 610 | """ 611 | 612 | device_bytes = self._usb_ctrl_transfer(0x80 | 0x20, 0x1, 0x81, 0, 2) 613 | 614 | if len(device_bytes) >= 2: 615 | return device_bytes[1] 616 | else: 617 | return -1 618 | 619 | def get_info_block1(self): 620 | """ 621 | Get the infoblock1 of the device. 622 | 623 | This is a 32 byte array that can contain any data. It's supposed to 624 | hold the "Name" of the device making it easier to identify rather than 625 | a serial number. 626 | 627 | @rtype: str 628 | @return: InfoBlock1 currently stored on the device 629 | """ 630 | 631 | device_bytes = self._usb_ctrl_transfer(0x80 | 0x20, 0x1, 0x0002, 0, 33) 632 | result = "" 633 | for i in device_bytes[1:]: 634 | if i == 0: 635 | break 636 | result += chr(i) 637 | return result 638 | 639 | def get_info_block2(self): 640 | """ 641 | Get the infoblock2 of the device. 642 | 643 | This is a 32 byte array that can contain any data. 644 | 645 | @rtype: str 646 | @return: InfoBlock2 currently stored on the device 647 | """ 648 | device_bytes = self._usb_ctrl_transfer(0x80 | 0x20, 0x1, 0x0003, 0, 33) 649 | result = "" 650 | for i in device_bytes[1:]: 651 | if i == 0: 652 | break 653 | result += chr(i) 654 | return result 655 | 656 | def _data_to_message(self, data): 657 | """ 658 | Helper method to convert a string to byte array of 32 bytes. 659 | 660 | @type data: str 661 | @param data: The data to convert to byte array 662 | 663 | @rtype: byte[32] 664 | @return: It fills the rest of bytes with zeros. 665 | """ 666 | bytes = [1] 667 | for c in data: 668 | bytes.append(ord(c)) 669 | 670 | for i in range(32 - len(data)): 671 | bytes.append(0) 672 | 673 | return bytes 674 | 675 | def set_info_block1(self, data): 676 | """ 677 | Sets the infoblock1 with specified string. 678 | 679 | It fills the rest of 32 bytes with zeros. 680 | 681 | @type data: str 682 | @param data: InfoBlock1 for the device to set 683 | """ 684 | self._usb_ctrl_transfer(0x20, 0x9, 0x0002, 0, self._data_to_message(data)) 685 | 686 | def set_info_block2(self, data): 687 | """ 688 | Sets the infoblock2 with specified string. 689 | 690 | It fills the rest of 32 bytes with zeros. 691 | 692 | @type data: str 693 | @param data: InfoBlock2 for the device to set 694 | """ 695 | self._usb_ctrl_transfer(0x20, 0x9, 0x0003, 0, self._data_to_message(data)) 696 | 697 | def set_random_color(self): 698 | """ 699 | Sets random color to the device. 700 | """ 701 | self.set_color(name="random") 702 | 703 | def turn_off(self): 704 | """ 705 | Turns off LED. 706 | """ 707 | self.set_color() 708 | 709 | def pulse(self, channel=0, index=0, red=0, green=0, blue=0, name=None, hex=None, repeats=1, duration=1000, steps=50): 710 | """ 711 | Morph to the specified color from black and back again. 712 | 713 | @type red: int 714 | @param red: Red color intensity 0 is off, 255 is full red intensity 715 | @type green: int 716 | @param green: Green color intensity 0 is off, 255 is full green intensity 717 | @type blue: int 718 | @param blue: Blue color intensity 0 is off, 255 is full blue intensity 719 | @type name: str 720 | @param name: Use CSS color name as defined here: U{http://www.w3.org/TR/css3-color/} 721 | @type hex: str 722 | @param hex: Specify color using hexadecimal color value e.g. '#FF3366' 723 | @type repeats: int 724 | @param repeats: Number of times to pulse the LED 725 | @type duration: int 726 | @param duration: Duration for pulse in milliseconds 727 | @type steps: int 728 | @param steps: Number of gradient steps 729 | """ 730 | self.turn_off() 731 | for x in range(repeats): 732 | self.morph(channel=channel, index=index, red=red, green=green, blue=blue, name=name, hex=hex, duration=duration, steps=steps) 733 | self.morph(channel=channel, index=index, red=0, green=0, blue=0, duration=duration, steps=steps) 734 | 735 | def blink(self, channel=0, index=0, red=0, green=0, blue=0, name=None, hex=None, repeats=1, delay=500): 736 | """ 737 | Blink the specified color. 738 | 739 | @type red: int 740 | @param red: Red color intensity 0 is off, 255 is full red intensity 741 | @type green: int 742 | @param green: Green color intensity 0 is off, 255 is full green intensity 743 | @type blue: int 744 | @param blue: Blue color intensity 0 is off, 255 is full blue intensity 745 | @type name: str 746 | @param name: Use CSS color name as defined here: U{http://www.w3.org/TR/css3-color/} 747 | @type hex: str 748 | @param hex: Specify color using hexadecimal color value e.g. '#FF3366' 749 | @type repeats: int 750 | @param repeats: Number of times to pulse the LED 751 | @type delay: int 752 | @param delay: time in milliseconds to light LED for, and also between blinks 753 | """ 754 | ms_delay = float(delay) / float(1000) 755 | for x in range(repeats): 756 | if x: 757 | time.sleep(ms_delay) 758 | self.set_color(channel=channel, index=index, red=red, green=green, blue=blue, name=name, hex=hex) 759 | time.sleep(ms_delay) 760 | self.set_color(channel=channel, index=index) 761 | 762 | def morph(self, channel=0, index=0, red=0, green=0, blue=0, name=None, hex=None, duration=1000, steps=50): 763 | """ 764 | Morph to the specified color. 765 | 766 | @type red: int 767 | @param red: Red color intensity 0 is off, 255 is full red intensity 768 | @type green: int 769 | @param green: Green color intensity 0 is off, 255 is full green intensity 770 | @type blue: int 771 | @param blue: Blue color intensity 0 is off, 255 is full blue intensity 772 | @type name: str 773 | @param name: Use CSS color name as defined here: U{http://www.w3.org/TR/css3-color/} 774 | @type hex: str 775 | @param hex: Specify color using hexadecimal color value e.g. '#FF3366' 776 | @type duration: int 777 | @param duration: Duration for morph in milliseconds 778 | @type steps: int 779 | @param steps: Number of gradient steps (default 50) 780 | """ 781 | 782 | r_end, g_end, b_end = self._determine_rgb(red=red, green=green, blue=blue, name=name, hex=hex) 783 | # descale the above values 784 | r_end, g_end, b_end = _remap_rgb_value_reverse([r_end, g_end, b_end], self.max_rgb_value) 785 | 786 | r_start, g_start, b_start = _remap_rgb_value_reverse(self._get_color_rgb(index), self.max_rgb_value) 787 | 788 | if r_start > 255 or g_start > 255 or b_start > 255: 789 | r_start = 0 790 | g_start = 0 791 | b_start = 0 792 | 793 | gradient = [] 794 | 795 | steps += 1 796 | for n in range(1, steps): 797 | d = 1.0 * n / steps 798 | r = (r_start * (1 - d)) + (r_end * d) 799 | g = (g_start * (1 - d)) + (g_end * d) 800 | b = (b_start * (1 - d)) + (b_end * d) 801 | 802 | gradient.append((r, g, b)) 803 | 804 | ms_delay = float(duration) / float(1000 * steps) 805 | 806 | self.set_color(channel=channel, index=index, red=r_start, green=g_start, blue=b_start) 807 | 808 | for grad in gradient: 809 | grad_r, grad_g, grad_b = grad 810 | 811 | self.set_color(channel=channel, index=index, red=grad_r, green=grad_g, blue=grad_b) 812 | time.sleep(ms_delay) 813 | 814 | self.set_color(channel=channel, index=index, red=r_end, green=g_end, blue=b_end) 815 | 816 | def open_device(self, d): 817 | """Open device. 818 | @param d: Device to open 819 | """ 820 | if self.device is None: 821 | raise BlinkStickException("Could not find BlinkStick...") 822 | 823 | if self.device.is_kernel_driver_active(0): 824 | try: 825 | self.device.detach_kernel_driver(0) 826 | except usb.core.USBError as e: 827 | raise BlinkStickException("Could not detach kernel driver: %s" % str(e)) 828 | 829 | return True 830 | 831 | def get_inverse(self): 832 | """ 833 | Get the value of inverse mode. This applies only to BlinkStick. Please use L{set_mode} for BlinkStick Pro 834 | to permanently set the inverse mode to the device. 835 | 836 | @rtype: bool 837 | @return: True if inverse mode, otherwise false 838 | """ 839 | return self.inverse 840 | 841 | def set_inverse(self, value): 842 | """ 843 | Set inverse mode. This applies only to BlinkStick. Please use L{set_mode} for BlinkStick Pro 844 | to permanently set the inverse mode to the device. 845 | 846 | @type value: bool 847 | @param value: True/False to set the inverse mode 848 | """ 849 | self.inverse = value 850 | 851 | def set_max_rgb_value(self, value): 852 | """ 853 | Set RGB color limit. {set_color} function will automatically remap 854 | the values to maximum supplied. 855 | 856 | @type value: int 857 | @param value: 0..255 maximum value for each R, G and B color 858 | """ 859 | self.max_rgb_value = value 860 | 861 | def get_max_rgb_value(self, max_rgb_value): 862 | """ 863 | Get RGB color limit. {set_color} function will automatically remap 864 | the values to maximum set. 865 | 866 | @rtype: int 867 | @return: 0..255 maximum value for each R, G and B color 868 | """ 869 | return self.max_rgb_value 870 | 871 | def _name_to_hex(self, name): 872 | """ 873 | Convert a color name to a normalized hexadecimal color value. 874 | 875 | The color name will be normalized to lower-case before being 876 | looked up, and when no color of that name exists in the given 877 | specification, ``ValueError`` is raised. 878 | 879 | Examples: 880 | 881 | >>> _name_to_hex('white') 882 | '#ffffff' 883 | >>> _name_to_hex('navy') 884 | '#000080' 885 | >>> _name_to_hex('goldenrod') 886 | '#daa520' 887 | """ 888 | normalized = name.lower() 889 | try: 890 | hex_value = self._names_to_hex[normalized] 891 | except KeyError: 892 | raise ValueError("'%s' is not defined as a named color." % (name)) 893 | return hex_value 894 | 895 | def _hex_to_rgb(self, hex_value): 896 | """ 897 | Convert a hexadecimal color value to a 3-tuple of integers 898 | suitable for use in an ``rgb()`` triplet specifying that color. 899 | 900 | The hexadecimal value will be normalized before being converted. 901 | 902 | Examples: 903 | 904 | >>> _hex_to_rgb('#fff') 905 | (255, 255, 255) 906 | >>> _hex_to_rgb('#000080') 907 | (0, 0, 128) 908 | 909 | """ 910 | hex_digits = self._normalize_hex(hex_value) 911 | return tuple([int(s, 16) for s in (hex_digits[1:3], hex_digits[3:5], hex_digits[5:7])]) 912 | 913 | def _normalize_hex(self, hex_value): 914 | """ 915 | Normalize a hexadecimal color value to the following form and 916 | return the result:: 917 | 918 | #[a-f0-9]{6} 919 | 920 | In other words, the following transformations are applied as 921 | needed: 922 | 923 | * If the value contains only three hexadecimal digits, it is expanded to six. 924 | 925 | * The value is normalized to lower-case. 926 | 927 | If the supplied value cannot be interpreted as a hexadecimal color 928 | value, ``ValueError`` is raised. 929 | 930 | Examples: 931 | 932 | >>> _normalize_hex('#0099cc') 933 | '#0099cc' 934 | >>> _normalize_hex('#0099CC') 935 | '#0099cc' 936 | >>> _normalize_hex('#09c') 937 | '#0099cc' 938 | >>> _normalize_hex('#09C') 939 | '#0099cc' 940 | >>> _normalize_hex('0099cc') 941 | Traceback (most recent call last): 942 | ... 943 | ValueError: '0099cc' is not a valid hexadecimal color value. 944 | 945 | """ 946 | try: 947 | hex_digits = self.HEX_COLOR_RE.match(hex_value).groups()[0] 948 | except AttributeError: 949 | raise ValueError("'%s' is not a valid hexadecimal color value." % hex_value) 950 | if len(hex_digits) == 3: 951 | hex_digits = ''.join([2 * s for s in hex_digits]) 952 | return '#%s' % hex_digits.lower() 953 | 954 | def _name_to_rgb(self, name): 955 | """ 956 | Convert a color name to a 3-tuple of integers suitable for use in 957 | an ``rgb()`` triplet specifying that color. 958 | 959 | The color name will be normalized to lower-case before being 960 | looked up, and when no color of that name exists in the given 961 | specification, ``ValueError`` is raised. 962 | 963 | Examples: 964 | 965 | >>> _name_to_rgb('white') 966 | (255, 255, 255) 967 | >>> _name_to_rgb('navy') 968 | (0, 0, 128) 969 | >>> _name_to_rgb('goldenrod') 970 | (218, 165, 32) 971 | 972 | """ 973 | return self._hex_to_rgb(self._name_to_hex(name)) 974 | 975 | class BlinkStickPro(object): 976 | """ 977 | BlinkStickPro class is specifically designed to control the individually 978 | addressable LEDs connected to the device. The tutorials section contains 979 | all the details on how to connect them to BlinkStick Pro. 980 | 981 | U{http://www.blinkstick.com/help/tutorials} 982 | 983 | Code example on how you can use this class are available here: 984 | 985 | U{https://github.com/arvydas/blinkstick-python/wiki#code-examples-for-blinkstick-pro} 986 | """ 987 | 988 | def __init__(self, r_led_count=0, g_led_count=0, b_led_count=0, delay=0.002, max_rgb_value=255): 989 | """ 990 | Initialize BlinkStickPro class. 991 | 992 | @type r_led_count: int 993 | @param r_led_count: number of LEDs on R channel 994 | @type g_led_count: int 995 | @param g_led_count: number of LEDs on G channel 996 | @type b_led_count: int 997 | @param b_led_count: number of LEDs on B channel 998 | @type delay: int 999 | @param delay: default transmission delay between frames 1000 | @type max_rgb_value: int 1001 | @param max_rgb_value: maximum color value for RGB channels 1002 | """ 1003 | 1004 | self.r_led_count = r_led_count 1005 | self.g_led_count = g_led_count 1006 | self.b_led_count = b_led_count 1007 | 1008 | self.fps_count = -1 1009 | 1010 | self.data_transmission_delay = delay 1011 | 1012 | self.max_rgb_value = max_rgb_value 1013 | 1014 | # initialise data store for each channel 1015 | # pre-populated with zeroes 1016 | 1017 | self.data = [[], [], []] 1018 | 1019 | for i in range(0, r_led_count): 1020 | self.data[0].append([0, 0, 0]) 1021 | 1022 | for i in range(0, g_led_count): 1023 | self.data[1].append([0, 0, 0]) 1024 | 1025 | for i in range(0, b_led_count): 1026 | self.data[2].append([0, 0, 0]) 1027 | 1028 | self.bstick = None 1029 | 1030 | def set_color(self, channel, index, r, g, b, remap_values=True): 1031 | """ 1032 | Set the color of a single pixel 1033 | 1034 | @type channel: int 1035 | @param channel: R, G or B channel 1036 | @type index: int 1037 | @param index: the index of LED on the channel 1038 | @type r: int 1039 | @param r: red color byte 1040 | @type g: int 1041 | @param g: green color byte 1042 | @type b: int 1043 | @param b: blue color byte 1044 | """ 1045 | 1046 | if remap_values: 1047 | r, g, b = [_remap_color(val, self.max_rgb_value) for val in [r, g, b]] 1048 | 1049 | self.data[channel][index] = [g, r, b] 1050 | 1051 | def get_color(self, channel, index): 1052 | """ 1053 | Get the current color of a single pixel. 1054 | 1055 | @type channel: int 1056 | @param channel: the channel of the LED 1057 | @type index: int 1058 | @param index: the index of the LED 1059 | 1060 | @rtype: (int, int, int) 1061 | @return: 3-tuple for R, G and B values 1062 | """ 1063 | 1064 | val = self.data[channel][index] 1065 | return [val[1], val[0], val[2]] 1066 | 1067 | def clear(self): 1068 | """ 1069 | Set all pixels to black in the frame buffer. 1070 | """ 1071 | for x in range(0, self.r_led_count): 1072 | self.set_color(0, x, 0, 0, 0) 1073 | 1074 | for x in range(0, self.g_led_count): 1075 | self.set_color(1, x, 0, 0, 0) 1076 | 1077 | for x in range(0, self.b_led_count): 1078 | self.set_color(2, x, 0, 0, 0) 1079 | 1080 | def off(self): 1081 | """ 1082 | Set all pixels to black in on the device. 1083 | """ 1084 | self.clear() 1085 | self.send_data_all() 1086 | 1087 | def connect(self, serial=None): 1088 | """ 1089 | Connect to the first BlinkStick found 1090 | 1091 | @type serial: str 1092 | @param serial: Select the serial number of BlinkStick 1093 | """ 1094 | 1095 | if serial is None: 1096 | self.bstick = find_first() 1097 | else: 1098 | self.bstick = find_by_serial(serial=serial) 1099 | 1100 | return self.bstick is not None 1101 | 1102 | def send_data(self, channel): 1103 | """ 1104 | Send data stored in the internal buffer to the channel. 1105 | 1106 | @param channel: 1107 | - 0 - R pin on BlinkStick Pro board 1108 | - 1 - G pin on BlinkStick Pro board 1109 | - 2 - B pin on BlinkStick Pro board 1110 | """ 1111 | packet_data = [item for sublist in self.data[channel] for item in sublist] 1112 | 1113 | try: 1114 | self.bstick.set_led_data(channel, packet_data) 1115 | time.sleep(self.data_transmission_delay) 1116 | except Exception as e: 1117 | print("Exception: {0}".format(e)) 1118 | 1119 | def send_data_all(self): 1120 | """ 1121 | Send data to all channels 1122 | """ 1123 | if self.r_led_count > 0: 1124 | self.send_data(0) 1125 | 1126 | if self.g_led_count > 0: 1127 | self.send_data(1) 1128 | 1129 | if self.b_led_count > 0: 1130 | self.send_data(2) 1131 | 1132 | class BlinkStickProMatrix(BlinkStickPro): 1133 | """ 1134 | BlinkStickProMatrix class is specifically designed to control the individually 1135 | addressable LEDs connected to the device and arranged in a matrix. The tutorials section contains 1136 | all the details on how to connect them to BlinkStick Pro with matrices. 1137 | 1138 | U{http://www.blinkstick.com/help/tutorials/blinkstick-pro-adafruit-neopixel-matrices} 1139 | 1140 | Code example on how you can use this class are available here: 1141 | 1142 | U{https://github.com/arvydas/blinkstick-python/wiki#code-examples-for-blinkstick-pro} 1143 | 1144 | Matrix is driven by using L{BlinkStickProMatrix.set_color} with [x,y] coordinates and class automatically 1145 | divides data into subsets and sends it to the matrices. 1146 | 1147 | For example, if you have 2 8x8 matrices connected to BlinkStickPro and you initialize 1148 | the class with 1149 | 1150 | >>> matrix = BlinkStickProMatrix(r_columns=8, r_rows=8, g_columns=8, g_rows=8) 1151 | 1152 | Then you can set the internal framebuffer by using {set_color} command: 1153 | 1154 | >>> matrix.set_color(x=10, y=5, r=255, g=0, b=0) 1155 | >>> matrix.set_color(x=6, y=3, r=0, g=255, b=0) 1156 | 1157 | And send data to both matrices in one go: 1158 | 1159 | >>> matrix.send_data_all() 1160 | 1161 | """ 1162 | 1163 | def __init__(self, r_columns=0, r_rows=0, g_columns=0, g_rows=0, b_columns=0, b_rows=0, delay=0.002, max_rgb_value=255): 1164 | """ 1165 | Initialize BlinkStickProMatrix class. 1166 | 1167 | @type r_columns: int 1168 | @param r_columns: number of matric columns for R channel 1169 | @type g_columns: int 1170 | @param g_columns: number of matric columns for R channel 1171 | @type b_columns: int 1172 | @param b_columns: number of matric columns for R channel 1173 | @type delay: int 1174 | @param delay: default transmission delay between frames 1175 | @type max_rgb_value: int 1176 | @param max_rgb_value: maximum color value for RGB channels 1177 | """ 1178 | r_leds = r_columns * r_rows 1179 | g_leds = g_columns * g_rows 1180 | b_leds = b_columns * b_rows 1181 | 1182 | self.r_columns = r_columns 1183 | self.r_rows = r_rows 1184 | self.g_columns = g_columns 1185 | self.g_rows = g_rows 1186 | self.b_columns = b_columns 1187 | self.b_rows = b_rows 1188 | 1189 | super(BlinkStickProMatrix, self).__init__(r_led_count=r_leds, g_led_count=g_leds, b_led_count=b_leds, delay=delay, max_rgb_value=max_rgb_value) 1190 | 1191 | self.rows = max(r_rows, g_rows, b_rows) 1192 | self.cols = r_columns + g_columns + b_columns 1193 | 1194 | # initialise data store for matrix pre-populated with zeroes 1195 | self.matrix_data = [] 1196 | 1197 | for i in range(0, self.rows * self.cols): 1198 | self.matrix_data.append([0, 0, 0]) 1199 | 1200 | def set_color(self, x, y, r, g, b, remap_values=True): 1201 | """ 1202 | Set the color of a single pixel in the internal framebuffer. 1203 | 1204 | @type x: int 1205 | @param x: the x location in the matrix 1206 | @type y: int 1207 | @param y: the y location in the matrix 1208 | @type r: int 1209 | @param r: red color byte 1210 | @type g: int 1211 | @param g: green color byte 1212 | @type b: int 1213 | @param b: blue color byte 1214 | @type remap_values: bool 1215 | @param remap_values: Automatically remap values based on the {max_rgb_value} supplied in the constructor 1216 | """ 1217 | 1218 | if remap_values: 1219 | r, g, b = [_remap_color(val, self.max_rgb_value) for val in [r, g, b]] 1220 | 1221 | self.matrix_data[self._coord_to_index(x, y)] = [g, r, b] 1222 | 1223 | def _coord_to_index(self, x, y): 1224 | return y * self.cols + x 1225 | 1226 | def get_color(self, x, y): 1227 | """ 1228 | Get the current color of a single pixel. 1229 | 1230 | @type x: int 1231 | @param x: x coordinate of the internal framebuffer 1232 | @type y: int 1233 | @param y: y coordinate of the internal framebuffer 1234 | 1235 | @rtype: (int, int, int) 1236 | @return: 3-tuple for R, G and B values 1237 | """ 1238 | 1239 | val = self.matrix_data[self._coord_to_index(x, y)] 1240 | return [val[1], val[0], val[2]] 1241 | 1242 | def shift_left(self, remove=False): 1243 | """ 1244 | Shift all LED values in the matrix to the left 1245 | 1246 | @type remove: bool 1247 | @param remove: whether to remove the pixels on the last column or move the to the first column 1248 | """ 1249 | if not remove: 1250 | temp = [] 1251 | for y in range(0, self.rows): 1252 | temp.append(self.get_color(0, y)) 1253 | 1254 | for y in range(0, self.rows): 1255 | for x in range(0, self.cols - 1): 1256 | r, g, b = self.get_color(x + 1, y) 1257 | 1258 | self.set_color(x, y, r, g, b, False) 1259 | 1260 | if remove: 1261 | for y in range(0, self.rows): 1262 | self.set_color(self.cols - 1, y, 0, 0, 0, False) 1263 | else: 1264 | for y in range(0, self.rows): 1265 | col = temp[y] 1266 | self.set_color(self.cols - 1, y, col[0], col[1], col[2], False) 1267 | 1268 | def shift_right(self, remove=False): 1269 | """ 1270 | Shift all LED values in the matrix to the right 1271 | 1272 | @type remove: bool 1273 | @param remove: whether to remove the pixels on the last column or move the to the first column 1274 | """ 1275 | 1276 | if not remove: 1277 | temp = [] 1278 | for y in range(0, self.rows): 1279 | temp.append(self.get_color(self.cols - 1, y)) 1280 | 1281 | for y in range(0, self.rows): 1282 | for x in reversed(range(1, self.cols)): 1283 | r, g, b = self.get_color(x - 1, y) 1284 | 1285 | self.set_color(x, y, r, g, b, False) 1286 | 1287 | if remove: 1288 | for y in range(0, self.rows): 1289 | self.set_color(0, y, 0, 0, 0, False) 1290 | else: 1291 | for y in range(0, self.rows): 1292 | col = temp[y] 1293 | self.set_color(0, y, col[0], col[1], col[2], False) 1294 | 1295 | def shift_down(self, remove=False): 1296 | """ 1297 | Shift all LED values in the matrix down 1298 | 1299 | @type remove: bool 1300 | @param remove: whether to remove the pixels on the last column or move the to the first column 1301 | """ 1302 | 1303 | if not remove: 1304 | temp = [] 1305 | for x in range(0, self.cols): 1306 | temp.append(self.get_color(x, self.rows - 1)) 1307 | 1308 | for y in reversed(range(1, self.rows)): 1309 | for x in range(0, self.cols): 1310 | r, g, b = self.get_color(x, y - 1) 1311 | 1312 | self.set_color(x, y, r, g, b, False) 1313 | 1314 | if remove: 1315 | for x in range(0, self.cols): 1316 | self.set_color(x, 0, 0, 0, 0, False) 1317 | else: 1318 | for x in range(0, self.cols): 1319 | col = temp[x] 1320 | self.set_color(x, 0, col[0], col[1], col[2], False) 1321 | 1322 | def shift_up(self, remove=False): 1323 | """ 1324 | Shift all LED values in the matrix up 1325 | 1326 | @type remove: bool 1327 | @param remove: whether to remove the pixels on the last column or move the to the first column 1328 | """ 1329 | 1330 | if not remove: 1331 | temp = [] 1332 | for x in range(0, self.cols): 1333 | temp.append(self.get_color(x, 0)) 1334 | 1335 | for x in range(0, self.cols): 1336 | for y in range(0, self.rows - 1): 1337 | r, g, b = self.get_color(x, y + 1) 1338 | 1339 | self.set_color(x, y, r, g, b, False) 1340 | 1341 | if remove: 1342 | for x in range(0, self.cols): 1343 | self.set_color(x, self.rows - 1, 0, 0, 0, False) 1344 | else: 1345 | for x in range(0, self.cols): 1346 | col = temp[x] 1347 | self.set_color(x, self.rows - 1, col[0], col[1], col[2], False) 1348 | 1349 | def number(self, x, y, n, r, g, b): 1350 | """ 1351 | Render a 3x5 number n at location x,y and r,g,b color 1352 | 1353 | @type x: int 1354 | @param x: the x location in the matrix (left of the number) 1355 | @type y: int 1356 | @param y: the y location in the matrix (top of the number) 1357 | @type n: int 1358 | @param n: number digit to render 0..9 1359 | @type r: int 1360 | @param r: red color byte 1361 | @type g: int 1362 | @param g: green color byte 1363 | @type b: int 1364 | @param b: blue color byte 1365 | """ 1366 | if n == 0: 1367 | self.rectangle(x, y, x + 2, y + 4, r, g, b) 1368 | elif n == 1: 1369 | self.line(x + 1, y, x + 1, y + 4, r, g, b) 1370 | self.line(x, y + 4, x + 2, y + 4, r, g, b) 1371 | self.set_color(x, y + 1, r, g, b) 1372 | elif n == 2: 1373 | self.line(x, y, x + 2, y, r, g, b) 1374 | self.line(x, y + 2, x + 2, y + 2, r, g, b) 1375 | self.line(x, y + 4, x + 2, y + 4, r, g, b) 1376 | self.set_color(x + 2, y + 1, r, g, b) 1377 | self.set_color(x, y + 3, r, g, b) 1378 | elif n == 3: 1379 | self.line(x, y, x + 2, y, r, g, b) 1380 | self.line(x, y + 2, x + 2, y + 2, r, g, b) 1381 | self.line(x, y + 4, x + 2, y + 4, r, g, b) 1382 | self.set_color(x + 2, y + 1, r, g, b) 1383 | self.set_color(x + 2, y + 3, r, g, b) 1384 | elif n == 4: 1385 | self.line(x, y, x, y + 2, r, g, b) 1386 | self.line(x + 2, y, x + 2, y + 4, r, g, b) 1387 | self.set_color(x + 1, y + 2, r, g, b) 1388 | elif n == 5: 1389 | self.line(x, y, x + 2, y, r, g, b) 1390 | self.line(x, y + 2, x + 2, y + 2, r, g, b) 1391 | self.line(x, y + 4, x + 2, y + 4, r, g, b) 1392 | self.set_color(x, y + 1, r, g, b) 1393 | self.set_color(x + 2, y + 3, r, g, b) 1394 | elif n == 6: 1395 | self.line(x, y, x + 2, y, r, g, b) 1396 | self.line(x, y + 2, x + 2, y + 2, r, g, b) 1397 | self.line(x, y + 4, x + 2, y + 4, r, g, b) 1398 | self.set_color(x, y + 1, r, g, b) 1399 | self.set_color(x + 2, y + 3, r, g, b) 1400 | self.set_color(x, y + 3, r, g, b) 1401 | elif n == 7: 1402 | self.line(x + 1, y + 2, x + 1, y + 4, r, g, b) 1403 | self.line(x, y, x + 2, y, r, g, b) 1404 | self.set_color(x + 2, y + 1, r, g, b) 1405 | elif n == 8: 1406 | self.line(x, y, x + 2, y, r, g, b) 1407 | self.line(x, y + 2, x + 2, y + 2, r, g, b) 1408 | self.line(x, y + 4, x + 2, y + 4, r, g, b) 1409 | self.set_color(x, y + 1, r, g, b) 1410 | self.set_color(x + 2, y + 1, r, g, b) 1411 | self.set_color(x + 2, y + 3, r, g, b) 1412 | self.set_color(x, y + 3, r, g, b) 1413 | elif n == 9: 1414 | self.line(x, y, x + 2, y, r, g, b) 1415 | self.line(x, y + 2, x + 2, y + 2, r, g, b) 1416 | self.line(x, y + 4, x + 2, y + 4, r, g, b) 1417 | self.set_color(x, y + 1, r, g, b) 1418 | self.set_color(x + 2, y + 1, r, g, b) 1419 | self.set_color(x + 2, y + 3, r, g, b) 1420 | 1421 | def rectangle(self, x1, y1, x2, y2, r, g, b): 1422 | """ 1423 | Draw a rectangle with it's corners at x1:y1 and x2:y2 1424 | 1425 | @type x1: int 1426 | @param x1: the x1 location in the matrix for first corner of the rectangle 1427 | @type y1: int 1428 | @param y1: the y1 location in the matrix for first corner of the rectangle 1429 | @type x2: int 1430 | @param x2: the x2 location in the matrix for second corner of the rectangle 1431 | @type y2: int 1432 | @param y2: the y2 location in the matrix for second corner of the rectangle 1433 | @type r: int 1434 | @param r: red color byte 1435 | @type g: int 1436 | @param g: green color byte 1437 | @type b: int 1438 | @param b: blue color byte 1439 | """ 1440 | 1441 | self.line(x1, y1, x1, y2, r, g, b) 1442 | self.line(x1, y1, x2, y1, r, g, b) 1443 | self.line(x2, y1, x2, y2, r, g, b) 1444 | self.line(x1, y2, x2, y2, r, g, b) 1445 | 1446 | def line(self, x1, y1, x2, y2, r, g, b): 1447 | """ 1448 | Draw a line from x1:y1 and x2:y2 1449 | 1450 | @type x1: int 1451 | @param x1: the x1 location in the matrix for the start of the line 1452 | @type y1: int 1453 | @param y1: the y1 location in the matrix for the start of the line 1454 | @type x2: int 1455 | @param x2: the x2 location in the matrix for the end of the line 1456 | @type y2: int 1457 | @param y2: the y2 location in the matrix for the end of the line 1458 | @type r: int 1459 | @param r: red color byte 1460 | @type g: int 1461 | @param g: green color byte 1462 | @type b: int 1463 | @param b: blue color byte 1464 | """ 1465 | points = [] 1466 | is_steep = abs(y2 - y1) > abs(x2 - x1) 1467 | if is_steep: 1468 | x1, y1 = y1, x1 1469 | x2, y2 = y2, x2 1470 | rev = False 1471 | if x1 > x2: 1472 | x1, x2 = x2, x1 1473 | y1, y2 = y2, y1 1474 | rev = True 1475 | delta_x = x2 - x1 1476 | delta_y = abs(y2 - y1) 1477 | error = int(delta_x / 2) 1478 | y = y1 1479 | y_step = None 1480 | 1481 | if y1 < y2: 1482 | y_step = 1 1483 | else: 1484 | y_step = -1 1485 | for x in range(x1, x2 + 1): 1486 | if is_steep: 1487 | #print y, "~", x 1488 | self.set_color(y, x, r, g, b) 1489 | points.append((y, x)) 1490 | else: 1491 | #print x, " ", y 1492 | self.set_color(x, y, r, g, b) 1493 | points.append((x, y)) 1494 | error -= delta_y 1495 | if error < 0: 1496 | y += y_step 1497 | error += delta_x 1498 | # Reverse the list if the coordinates were reversed 1499 | if rev: 1500 | points.reverse() 1501 | return points 1502 | 1503 | def clear(self): 1504 | """ 1505 | Set all pixels to black in the cached matrix 1506 | """ 1507 | for y in range(0, self.rows): 1508 | for x in range(0, self.cols): 1509 | self.set_color(x, y, 0, 0, 0) 1510 | 1511 | def send_data(self, channel): 1512 | """ 1513 | Send data stored in the internal buffer to the channel. 1514 | 1515 | @param channel: 1516 | - 0 - R pin on BlinkStick Pro board 1517 | - 1 - G pin on BlinkStick Pro board 1518 | - 2 - B pin on BlinkStick Pro board 1519 | """ 1520 | 1521 | start_col = 0 1522 | end_col = 0 1523 | 1524 | if channel == 0: 1525 | start_col = 0 1526 | end_col = self.r_columns 1527 | 1528 | if channel == 1: 1529 | start_col = self.r_columns 1530 | end_col = start_col + self.g_columns 1531 | 1532 | if channel == 2: 1533 | start_col = self.r_columns + self.g_columns 1534 | end_col = start_col + self.b_columns 1535 | 1536 | self.data[channel] = [] 1537 | 1538 | #slice the huge array to individual packets 1539 | for y in range(0, self.rows): 1540 | start = y * self.cols + start_col 1541 | end = y * self.cols + end_col 1542 | 1543 | self.data[channel].extend(self.matrix_data[start: end]) 1544 | 1545 | super(BlinkStickProMatrix, self).send_data(channel) 1546 | 1547 | def _find_blicksticks(find_all=True): 1548 | if sys.platform == "win32": 1549 | devices = hid.HidDeviceFilter(vendor_id = VENDOR_ID, product_id = PRODUCT_ID).get_devices() 1550 | if find_all: 1551 | return devices 1552 | elif len(devices) > 0: 1553 | return devices[0] 1554 | else: 1555 | return None 1556 | 1557 | else: 1558 | return usb.core.find(find_all=find_all, idVendor=VENDOR_ID, idProduct=PRODUCT_ID) 1559 | 1560 | 1561 | def find_all(): 1562 | """ 1563 | Find all attached BlinkStick devices. 1564 | 1565 | @rtype: BlinkStick[] 1566 | @return: a list of BlinkStick objects or None if no devices found 1567 | """ 1568 | result = [] 1569 | for d in _find_blicksticks(): 1570 | result.extend([BlinkStick(device=d)]) 1571 | 1572 | return result 1573 | 1574 | 1575 | def find_first(): 1576 | """ 1577 | Find first attached BlinkStick. 1578 | 1579 | @rtype: BlinkStick 1580 | @return: BlinkStick object or None if no devices are found 1581 | """ 1582 | d = _find_blicksticks(find_all=False) 1583 | 1584 | if d: 1585 | return BlinkStick(device=d) 1586 | 1587 | 1588 | def find_by_serial(serial=None): 1589 | """ 1590 | Find BlinkStick device based on serial number. 1591 | 1592 | @rtype: BlinkStick 1593 | @return: BlinkStick object or None if no devices are found 1594 | """ 1595 | 1596 | devices = [] 1597 | if sys.platform == "win32": 1598 | devices = [d for d in _find_blicksticks() 1599 | if d.serial_number == serial] 1600 | else: 1601 | for d in _find_blicksticks(): 1602 | try: 1603 | if usb.util.get_string(d, 3, 1033) == serial: 1604 | devices = [d] 1605 | break 1606 | except Exception as e: 1607 | print("{0}".format(e)) 1608 | 1609 | if devices: 1610 | return BlinkStick(device=devices[0]) 1611 | 1612 | 1613 | def _remap(value, leftMin, leftMax, rightMin, rightMax): 1614 | # Figure out how 'wide' each range is 1615 | leftSpan = leftMax - leftMin 1616 | rightSpan = rightMax - rightMin 1617 | 1618 | # Convert the left range into a 0-1 range (float) 1619 | valueScaled = float(value - leftMin) / float(leftSpan) 1620 | 1621 | # Convert the 0-1 range into a value in the right range. 1622 | return int(rightMin + (valueScaled * rightSpan)) 1623 | 1624 | def _remap_color(value, max_value): 1625 | return _remap(value, 0, 255, 0, max_value) 1626 | 1627 | def _remap_color_reverse(value, max_value): 1628 | return _remap(value, 0, max_value, 0, 255) 1629 | 1630 | def _remap_rgb_value(rgb_val, max_value): 1631 | return [_remap_color(rgb_val[0], max_value), 1632 | _remap_color(rgb_val[1], max_value), 1633 | _remap_color(rgb_val[2], max_value)] 1634 | 1635 | def _remap_rgb_value_reverse(rgb_val, max_value): 1636 | return [_remap_color_reverse(rgb_val[0], max_value), 1637 | _remap_color_reverse(rgb_val[1], max_value), 1638 | _remap_color_reverse(rgb_val[2], max_value)] 1639 | 1640 | def get_blinkstick_package_version(): 1641 | return __version__ 1642 | 1643 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import re 4 | import sys 5 | from setuptools import setup, find_packages 6 | 7 | 8 | def read(fname): 9 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 10 | 11 | 12 | PKG = 'blinkstick' 13 | VERSIONFILE = os.path.join(PKG, "_version.py") 14 | verstr = "unknown" 15 | try: 16 | verstrline = open(VERSIONFILE, "rt").read() 17 | except EnvironmentError: 18 | pass # Okay, there is no version file. 19 | else: 20 | VSRE = r"(\d+\.\d+\.\d+)" 21 | mo = re.search(VSRE, verstrline, re.M) 22 | if mo: 23 | verstr = mo.group(1) 24 | else: 25 | print("unable to find version in {0}").format(VERSIONFILE) 26 | raise RuntimeError("if {0}.py exists, it is required to be well-formed".format(VERSIONFILE)) 27 | 28 | if sys.platform == "win32": 29 | os_requires = [ 30 | "pywinusb" 31 | ] 32 | else: 33 | os_requires = [ 34 | "pyusb>=1.0.0" 35 | ] 36 | 37 | setup( 38 | name='BlinkStick', 39 | version=verstr, 40 | author='Arvydas Juskevicius', 41 | author_email='arvydas@arvydas.co.uk', 42 | packages=find_packages(), 43 | scripts=["bin/blinkstick"], 44 | url='http://pypi.python.org/pypi/BlinkStick/', 45 | license='LICENSE.txt', 46 | description='Python package to control BlinkStick USB devices.', 47 | long_description=read('README.rst'), 48 | install_requires=os_requires, 49 | ) 50 | --------------------------------------------------------------------------------