1 from ._version import __version__
2 import time
3 import sys
4 import re
5
6 if sys.platform == "win32":
7 import pywinusb.hid as hid
8 from ctypes import *
9 else:
10 import usb.core
11 import usb.util
12
13 from random import randint
14
15 """
16 Main module to control BlinkStick and BlinkStick Pro devices.
17 """
18
19 VENDOR_ID = 0x20a0
20 PRODUCT_ID = 0x41e5
21
24
25
27 """
28 BlinkStick class is designed to control regular BlinkStick devices, or BlinkStick Pro
29 devices in Normal or Inverse modes. Please refer to L{BlinkStick.set_mode} for more details
30 about BlinkStick Pro device modes.
31
32 Code examples on how you can use this class are available here:
33
34 U{https://github.com/arvydas/blinkstick-python/wiki}
35 """
36
37 _names_to_hex = {'aliceblue': '#f0f8ff',
38 'antiquewhite': '#faebd7',
39 'aqua': '#00ffff',
40 'aquamarine': '#7fffd4',
41 'azure': '#f0ffff',
42 'beige': '#f5f5dc',
43 'bisque': '#ffe4c4',
44 'black': '#000000',
45 'blanchedalmond': '#ffebcd',
46 'blue': '#0000ff',
47 'blueviolet': '#8a2be2',
48 'brown': '#a52a2a',
49 'burlywood': '#deb887',
50 'cadetblue': '#5f9ea0',
51 'chartreuse': '#7fff00',
52 'chocolate': '#d2691e',
53 'coral': '#ff7f50',
54 'cornflowerblue': '#6495ed',
55 'cornsilk': '#fff8dc',
56 'crimson': '#dc143c',
57 'cyan': '#00ffff',
58 'darkblue': '#00008b',
59 'darkcyan': '#008b8b',
60 'darkgoldenrod': '#b8860b',
61 'darkgray': '#a9a9a9',
62 'darkgrey': '#a9a9a9',
63 'darkgreen': '#006400',
64 'darkkhaki': '#bdb76b',
65 'darkmagenta': '#8b008b',
66 'darkolivegreen': '#556b2f',
67 'darkorange': '#ff8c00',
68 'darkorchid': '#9932cc',
69 'darkred': '#8b0000',
70 'darksalmon': '#e9967a',
71 'darkseagreen': '#8fbc8f',
72 'darkslateblue': '#483d8b',
73 'darkslategray': '#2f4f4f',
74 'darkslategrey': '#2f4f4f',
75 'darkturquoise': '#00ced1',
76 'darkviolet': '#9400d3',
77 'deeppink': '#ff1493',
78 'deepskyblue': '#00bfff',
79 'dimgray': '#696969',
80 'dimgrey': '#696969',
81 'dodgerblue': '#1e90ff',
82 'firebrick': '#b22222',
83 'floralwhite': '#fffaf0',
84 'forestgreen': '#228b22',
85 'fuchsia': '#ff00ff',
86 'gainsboro': '#dcdcdc',
87 'ghostwhite': '#f8f8ff',
88 'gold': '#ffd700',
89 'goldenrod': '#daa520',
90 'gray': '#808080',
91 'grey': '#808080',
92 'green': '#008000',
93 'greenyellow': '#adff2f',
94 'honeydew': '#f0fff0',
95 'hotpink': '#ff69b4',
96 'indianred': '#cd5c5c',
97 'indigo': '#4b0082',
98 'ivory': '#fffff0',
99 'khaki': '#f0e68c',
100 'lavender': '#e6e6fa',
101 'lavenderblush': '#fff0f5',
102 'lawngreen': '#7cfc00',
103 'lemonchiffon': '#fffacd',
104 'lightblue': '#add8e6',
105 'lightcoral': '#f08080',
106 'lightcyan': '#e0ffff',
107 'lightgoldenrodyellow': '#fafad2',
108 'lightgray': '#d3d3d3',
109 'lightgrey': '#d3d3d3',
110 'lightgreen': '#90ee90',
111 'lightpink': '#ffb6c1',
112 'lightsalmon': '#ffa07a',
113 'lightseagreen': '#20b2aa',
114 'lightskyblue': '#87cefa',
115 'lightslategray': '#778899',
116 'lightslategrey': '#778899',
117 'lightsteelblue': '#b0c4de',
118 'lightyellow': '#ffffe0',
119 'lime': '#00ff00',
120 'limegreen': '#32cd32',
121 'linen': '#faf0e6',
122 'magenta': '#ff00ff',
123 'maroon': '#800000',
124 'mediumaquamarine': '#66cdaa',
125 'mediumblue': '#0000cd',
126 'mediumorchid': '#ba55d3',
127 'mediumpurple': '#9370d8',
128 'mediumseagreen': '#3cb371',
129 'mediumslateblue': '#7b68ee',
130 'mediumspringgreen': '#00fa9a',
131 'mediumturquoise': '#48d1cc',
132 'mediumvioletred': '#c71585',
133 'midnightblue': '#191970',
134 'mintcream': '#f5fffa',
135 'mistyrose': '#ffe4e1',
136 'moccasin': '#ffe4b5',
137 'navajowhite': '#ffdead',
138 'navy': '#000080',
139 'oldlace': '#fdf5e6',
140 'olive': '#808000',
141 'olivedrab': '#6b8e23',
142 'orange': '#ffa500',
143 'orangered': '#ff4500',
144 'orchid': '#da70d6',
145 'palegoldenrod': '#eee8aa',
146 'palegreen': '#98fb98',
147 'paleturquoise': '#afeeee',
148 'palevioletred': '#d87093',
149 'papayawhip': '#ffefd5',
150 'peachpuff': '#ffdab9',
151 'peru': '#cd853f',
152 'pink': '#ffc0cb',
153 'plum': '#dda0dd',
154 'powderblue': '#b0e0e6',
155 'purple': '#800080',
156 'red': '#ff0000',
157 'rosybrown': '#bc8f8f',
158 'royalblue': '#4169e1',
159 'saddlebrown': '#8b4513',
160 'salmon': '#fa8072',
161 'sandybrown': '#f4a460',
162 'seagreen': '#2e8b57',
163 'seashell': '#fff5ee',
164 'sienna': '#a0522d',
165 'silver': '#c0c0c0',
166 'skyblue': '#87ceeb',
167 'slateblue': '#6a5acd',
168 'slategray': '#708090',
169 'slategrey': '#708090',
170 'snow': '#fffafa',
171 'springgreen': '#00ff7f',
172 'steelblue': '#4682b4',
173 'tan': '#d2b48c',
174 'teal': '#008080',
175 'thistle': '#d8bfd8',
176 'tomato': '#ff6347',
177 'turquoise': '#40e0d0',
178 'violet': '#ee82ee',
179 'wheat': '#f5deb3',
180 'white': '#ffffff',
181 'whitesmoke': '#f5f5f5',
182 'yellow': '#ffff00',
183 'yellowgreen': '#9acd32'}
184
185 HEX_COLOR_RE = re.compile(r'^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$')
186
187 inverse = False
188 error_reporting = True
189 max_rgb_value = 255
190
191 - def __init__(self, device=None, error_reporting=True):
192 """
193 Constructor for the class.
194
195 @type error_reporting: Boolean
196 @param error_reporting: display errors if they occur during communication with the device
197 """
198 self.error_reporting = error_reporting
199
200 if device:
201 self.device = device
202 if sys.platform == "win32":
203 self.device.open()
204 self.reports = self.device.find_feature_reports()
205 else:
206 self.open_device(device)
207
208 self.bs_serial = self.get_serial()
209
211 try:
212 return usb.util.get_string(device, length, index)
213 except usb.USBError:
214
215
216
217 if self._refresh_device():
218 return usb.util.get_string(self.device, length, index)
219 else:
220 raise BlinkStickException("Could not communicate with BlinkStick {0} - it may have been removed".format(self.bs_serial))
221
223 if sys.platform == "win32":
224 if bmRequestType == 0x20:
225 data = (c_ubyte * len(data_or_wLength))(*[c_ubyte(ord(c)) for c in data_or_wLength])
226 data[0] = wValue
227 if not self.device.send_feature_report(data):
228 if self._refresh_device():
229 self.device.send_feature_report(data)
230 else:
231 raise BlinkStickException("Could not communicate with BlinkStick {0} - it may have been removed".format(self.bs_serial))
232
233 elif bmRequestType == 0x80 | 0x20:
234 return self.reports[wValue - 1].get()
235 else:
236 try:
237 return self.device.ctrl_transfer(bmRequestType, bRequest, wValue, wIndex, data_or_wLength)
238 except usb.USBError:
239
240
241
242 if self._refresh_device():
243 return self.device.ctrl_transfer(bmRequestType, bRequest, wValue, wIndex, data_or_wLength)
244 else:
245 raise BlinkStickException("Could not communicate with BlinkStick {0} - it may have been removed".format(self.bs_serial))
246
248 d = find_by_serial(self.bs_serial)
249 if d:
250 self.device = d.device
251 return True
252
254 """
255 Returns the serial number of device.::
256
257 BSnnnnnn-1.0
258 || | | |- Software minor version
259 || | |--- Software major version
260 || |-------- Denotes sequential number
261 ||----------- Denotes BlinkStick device
262
263 Software version defines the capabilities of the device
264
265 @rtype: str
266 @return: Serial number of the device
267 """
268 if sys.platform == "win32":
269 return self.device.serial_number
270 else:
271 return self._usb_get_string(self.device, 256, 3)
272
274 """
275 Get the manufacturer of the device
276
277 @rtype: str
278 @return: Device manufacturer's name
279 """
280 if sys.platform == "win32":
281 return self.device.vendor_name
282 else:
283 return self._usb_get_string(self.device, 256, 1)
284
285
287 """
288 Get the description of the device
289
290 @rtype: str
291 @return: Device description
292 """
293 if sys.platform == "win32":
294 return self.device.product_name
295 else:
296 return self._usb_get_string(self.device, 256, 2)
297
299 """
300 Enable or disable error reporting
301
302 @type error_reporting: Boolean
303 @param error_reporting: display errors if they occur during communication with the device
304 """
305 self.error_reporting = error_reporting
306
307 - def set_color(self, channel=0, index=0, red=0, green=0, blue=0, name=None, hex=None):
308 """
309 Set the color to the device as RGB
310
311 @type red: int
312 @param red: Red color intensity 0 is off, 255 is full red intensity
313 @type green: int
314 @param green: Green color intensity 0 is off, 255 is full green intensity
315 @type blue: int
316 @param blue: Blue color intensity 0 is off, 255 is full blue intensity
317 @type name: str
318 @param name: Use CSS color name as defined here: U{http://www.w3.org/TR/css3-color/}
319 @type hex: str
320 @param hex: Specify color using hexadecimal color value e.g. '#FF3366'
321 """
322
323 red, green, blue = self._determine_rgb(red=red, green=green, blue=blue, name=name, hex=hex)
324
325 r = int(round(red, 3))
326 g = int(round(green, 3))
327 b = int(round(blue, 3))
328
329 if self.inverse:
330 r, g, b = 255 - r, 255 - g, 255 - b
331
332 if index == 0 and channel == 0:
333 control_string = bytes(bytearray([0, r, g, b]))
334 report_id = 0x0001
335 else:
336 control_string = bytes(bytearray([5, channel, index, r, g, b]))
337 report_id = 0x0005
338
339 if self.error_reporting:
340 self._usb_ctrl_transfer(0x20, 0x9, report_id, 0, control_string)
341 else:
342 try:
343 self._usb_ctrl_transfer(0x20, 0x9, report_id, 0, control_string)
344 except Exception as e:
345 pass
346
347 - def _determine_rgb(self, red=0, green=0, blue=0, name=None, hex=None):
348
349 try:
350 if name:
351
352 if name is "random":
353 red = randint(0, 255)
354 green = randint(0, 255)
355 blue = randint(0, 255)
356 else:
357 red, green, blue = self._name_to_rgb(name)
358 elif hex:
359 red, green, blue = self._hex_to_rgb(hex)
360 except ValueError:
361 red = green = blue = 0
362
363 red, green, blue = _remap_rgb_value([red, green, blue], self.max_rgb_value)
364
365
366
367 return red, green, blue
368
370 if index == 0:
371 device_bytes = self._usb_ctrl_transfer(0x80 | 0x20, 0x1, 0x0001, 0, 33)
372 if self.inverse:
373 return [255 - device_bytes[1], 255 - device_bytes[2], 255 - device_bytes[3]]
374 else:
375 return [device_bytes[1], device_bytes[2], device_bytes[3]]
376 else:
377 data = self.get_led_data((index + 1) * 3)
378
379 return [data[index * 3 + 1], data[index * 3], data[index * 3 + 2]]
380
382 r, g, b = self._get_color_rgb(index)
383 return '#%02x%02x%02x' % (r, g, b)
384
385 - def get_color(self, index=0, color_format='rgb'):
386 """
387 Get the current device color in the defined format.
388
389 Currently supported formats:
390
391 1. rgb (default) - Returns values as 3-tuple (r,g,b)
392 2. hex - returns current device color as hexadecimal string
393
394 >>> b = blinkstick.find_first()
395 >>> b.set_color(red=255,green=0,blue=0)
396 >>> (r,g,b) = b.get_color() # Get color as rbg tuple
397 (255,0,0)
398 >>> hex = b.get_color(color_format='hex') # Get color as hex string
399 '#ff0000'
400
401 @type index: int
402 @param index: the index of the LED
403 @type color_format: str
404 @param color_format: "rgb" or "hex". Defaults to "rgb".
405
406 @rtype: (int, int, int) or str
407 @return: Either 3-tuple for R, G and B values, or hex string
408 """
409
410
411 get_color_func = getattr(self, "_get_color_%s" % color_format, self._get_color_rgb)
412 if callable(get_color_func):
413 return get_color_func(index)
414 else:
415
416 raise BlinkStickException("Could not return current color in format %s" % color_format)
417
419 report_id = 9
420 max_leds = 64
421
422 if led_count <= 8 * 3:
423 max_leds = 8
424 report_id = 6
425 elif led_count <= 16 * 3:
426 max_leds = 16
427 report_id = 7
428 elif led_count <= 32 * 3:
429 max_leds = 32
430 report_id = 8
431 elif led_count <= 64 * 3:
432 max_leds = 64
433 report_id = 9
434
435 return report_id, max_leds
436
438 """
439 Send LED data frame.
440
441 @type channel: int
442 @param channel: the channel which to send data to (R=0, G=1, B=2)
443 @type data: int[0..64*3]
444 @param data: The LED data frame in GRB format
445 """
446
447 report_id, max_leds = self._determine_report_id(len(data))
448
449 report = [0, channel]
450
451 for i in range(0, max_leds * 3):
452 if len(data) > i:
453 report.append(data[i])
454 else:
455 report.append(0)
456
457 self.device.ctrl_transfer(0x20, 0x9, report_id, 0, bytes(bytearray(report)))
458
460 """
461 Get LED data frame on the device.
462
463 @type count: int
464 @param count: How much data to retrieve. Can be in the range of 0..64*3
465 @rtype: int[0..64*3]
466 @return: LED data currently stored in the RAM of the device
467 """
468
469 report_id, max_leds = self._determine_report_id(count)
470
471 device_bytes = self._usb_ctrl_transfer(0x80 | 0x20, 0x1, report_id, 0, max_leds * 3 + 2)
472
473 return device_bytes[2: 2 + count * 3]
474
476 """
477 Set device mode for BlinkStick Pro. Device currently supports the following modes:
478
479 - 0 - (default) use R, G and B channels to control single RGB LED
480 - 1 - same as 0, but inverse mode
481 - 2 - control up to 64 WS2812 individual LEDs per each R, G and B channel
482
483 You can find out more about BlinkStick Pro modes:
484
485 U{http://www.blinkstick.com/help/tutorials/blinkstick-pro-modes}
486
487 @type mode: int
488 @param mode: Device mode to set
489 """
490 control_string = bytes(bytearray([4, mode]))
491
492 self.device.ctrl_transfer(0x20, 0x9, 0x0004, 0, control_string)
493
495 """
496 Get BlinkStick Pro mode. Device currently supports the following modes:
497
498 - 0 - (default) use R, G and B channels to control single RGB LED
499 - 1 - same as 0, but inverse mode
500 - 2 - control up to 64 WS2812 individual LEDs per each R, G and B channel
501
502 You can find out more about BlinkStick Pro modes:
503
504 U{http://www.blinkstick.com/help/tutorials/blinkstick-pro-modes}
505
506 @rtype: int
507 @return: Device mode
508 """
509
510 device_bytes = self.device.ctrl_transfer(0x80 | 0x20, 0x1, 0x0004, 0, 2)
511
512 if len(device_bytes) >= 2:
513 return device_bytes[1]
514 else:
515 return -1
516
518 """
519 Get the infoblock1 of the device.
520
521 This is a 32 byte array that can contain any data. It's supposed to
522 hold the "Name" of the device making it easier to identify rather than
523 a serial number.
524
525 @rtype: str
526 @return: InfoBlock1 currently stored on the device
527 """
528
529 device_bytes = self._usb_ctrl_transfer(0x80 | 0x20, 0x1, 0x0002, 0, 33)
530 result = ""
531 for i in device_bytes[1:]:
532 if i == 0:
533 break
534 result += chr(i)
535 return result
536
538 """
539 Get the infoblock2 of the device.
540
541 This is a 32 byte array that can contain any data.
542
543 @rtype: str
544 @return: InfoBlock2 currently stored on the device
545 """
546 device_bytes = self._usb_ctrl_transfer(0x80 | 0x20, 0x1, 0x0003, 0, 33)
547 result = ""
548 for i in device_bytes[1:]:
549 if i == 0:
550 break
551 result += chr(i)
552 return result
553
555 """
556 Helper method to convert a string to byte array of 32 bytes.
557
558 @type data: str
559 @param data: The data to convert to byte array
560
561 @rtype: byte[32]
562 @return: It fills the rest of bytes with zeros.
563 """
564 bytes = [1]
565 for c in data:
566 bytes.append(ord(c))
567
568 for i in range(32 - len(data)):
569 bytes.append(0)
570
571 return bytes
572
574 """
575 Sets the infoblock1 with specified string.
576
577 It fills the rest of 32 bytes with zeros.
578
579 @type data: str
580 @param data: InfoBlock1 for the device to set
581 """
582 self._usb_ctrl_transfer(0x20, 0x9, 0x0002, 0, self._data_to_message(data))
583
585 """
586 Sets the infoblock2 with specified string.
587
588 It fills the rest of 32 bytes with zeros.
589
590 @type data: str
591 @param data: InfoBlock2 for the device to set
592 """
593 self._usb_ctrl_transfer(0x20, 0x9, 0x0003, 0, self._data_to_message(data))
594
596 """
597 Sets random color to the device.
598 """
599 self.set_color(name="random")
600
602 """
603 Turns off LED.
604 """
605 self.set_color()
606
607 - def pulse(self, channel=0, index=0, red=0, green=0, blue=0, name=None, hex=None, repeats=1, duration=1000, steps=50):
608 """
609 Morph to the specified color from black and back again.
610
611 @type red: int
612 @param red: Red color intensity 0 is off, 255 is full red intensity
613 @type green: int
614 @param green: Green color intensity 0 is off, 255 is full green intensity
615 @type blue: int
616 @param blue: Blue color intensity 0 is off, 255 is full blue intensity
617 @type name: str
618 @param name: Use CSS color name as defined here: U{http://www.w3.org/TR/css3-color/}
619 @type hex: str
620 @param hex: Specify color using hexadecimal color value e.g. '#FF3366'
621 @type repeats: int
622 @param repeats: Number of times to pulse the LED
623 @type duration: int
624 @param duration: Duration for pulse in milliseconds
625 @type steps: int
626 @param steps: Number of gradient steps
627 """
628 r, g, b = self._determine_rgb(red=red, green=green, blue=blue, name=name, hex=hex)
629
630 self.turn_off()
631 for x in range(repeats):
632 self.morph(channel=channel, index=index, red=r, green=g, blue=b, duration=duration, steps=steps)
633 self.morph(channel=channel, index=index, red=0, green=0, blue=0, duration=duration, steps=steps)
634
635 - def blink(self, channel=0, index=0, red=0, green=0, blue=0, name=None, hex=None, repeats=1, delay=500):
636 """
637 Blink the specified color.
638
639 @type red: int
640 @param red: Red color intensity 0 is off, 255 is full red intensity
641 @type green: int
642 @param green: Green color intensity 0 is off, 255 is full green intensity
643 @type blue: int
644 @param blue: Blue color intensity 0 is off, 255 is full blue intensity
645 @type name: str
646 @param name: Use CSS color name as defined here: U{http://www.w3.org/TR/css3-color/}
647 @type hex: str
648 @param hex: Specify color using hexadecimal color value e.g. '#FF3366'
649 @type repeats: int
650 @param repeats: Number of times to pulse the LED
651 @type delay: int
652 @param delay: time in milliseconds to light LED for, and also between blinks
653 """
654 r, g, b = self._determine_rgb(red=red, green=green, blue=blue, name=name, hex=hex)
655 ms_delay = float(delay) / float(1000)
656 for x in range(repeats):
657 if x:
658 time.sleep(ms_delay)
659 self.set_color(channel=channel, index=index, red=r, green=g, blue=b)
660 time.sleep(ms_delay)
661 self.set_color(channel=channel, index=index)
662
663 - def morph(self, channel=0, index=0, red=0, green=0, blue=0, name=None, hex=None, duration=1000, steps=50):
664 """
665 Morph to the specified color.
666
667 @type red: int
668 @param red: Red color intensity 0 is off, 255 is full red intensity
669 @type green: int
670 @param green: Green color intensity 0 is off, 255 is full green intensity
671 @type blue: int
672 @param blue: Blue color intensity 0 is off, 255 is full blue intensity
673 @type name: str
674 @param name: Use CSS color name as defined here: U{http://www.w3.org/TR/css3-color/}
675 @type hex: str
676 @param hex: Specify color using hexadecimal color value e.g. '#FF3366'
677 @type duration: int
678 @param duration: Duration for morph in milliseconds
679 @type steps: int
680 @param steps: Number of gradient steps (default 50)
681 """
682
683 r_end, g_end, b_end = self._determine_rgb(red=red, green=green, blue=blue, name=name, hex=hex)
684
685 r_start, g_start, b_start = _remap_rgb_value_reverse(self._get_color_rgb(index), self.max_rgb_value)
686
687 if r_start > 255 or g_start > 255 or b_start > 255:
688 r_start = 0
689 g_start = 0
690 b_start = 0
691
692 gradient = []
693
694 steps += 1
695 for n in range(1, steps):
696 d = 1.0 * n / steps
697 r = (r_start * (1 - d)) + (r_end * d)
698 g = (g_start * (1 - d)) + (g_end * d)
699 b = (b_start * (1 - d)) + (b_end * d)
700
701 gradient.append((r, g, b))
702
703 ms_delay = float(duration) / float(1000 * steps)
704
705 self.set_color(channel=channel, index=index, red=r_start, green=g_start, blue=b_start)
706
707 for grad in gradient:
708 grad_r, grad_g, grad_b = grad
709
710 self.set_color(channel=channel, index=index, red=grad_r, green=grad_g, blue=grad_b)
711 time.sleep(ms_delay)
712
713 self.set_color(channel=channel, index=index, red=r_end, green=g_end, blue=b_end)
714
716 """Open device.
717 @param d: Device to open
718 """
719 if self.device is None:
720 raise BlinkStickException("Could not find BlinkStick...")
721
722 if self.device.is_kernel_driver_active(0):
723 try:
724 self.device.detach_kernel_driver(0)
725 except usb.core.USBError as e:
726 raise BlinkStickException("Could not detach kernel driver: %s" % str(e))
727
728 return True
729
731 """
732 Get the value of inverse mode. This applies only to BlinkStick. Please use L{set_mode} for BlinkStick Pro
733 to permanently set the inverse mode to the device.
734
735 @rtype: bool
736 @return: True if inverse mode, otherwise false
737 """
738 return self.inverse
739
741 """
742 Set inverse mode. This applies only to BlinkStick. Please use L{set_mode} for BlinkStick Pro
743 to permanently set the inverse mode to the device.
744
745 @type value: bool
746 @param value: True/False to set the inverse mode
747 """
748 self.inverse = value
749
751 """
752 Set RGB color limit. {set_color} function will automatically remap
753 the values to maximum supplied.
754
755 @type value: int
756 @param value: 0..255 maximum value for each R, G and B color
757 """
758 self.max_rgb_value = value
759
761 """
762 Get RGB color limit. {set_color} function will automatically remap
763 the values to maximum set.
764
765 @rtype: int
766 @return: 0..255 maximum value for each R, G and B color
767 """
768 return self.max_rgb_value
769
771 """
772 Convert a color name to a normalized hexadecimal color value.
773
774 The color name will be normalized to lower-case before being
775 looked up, and when no color of that name exists in the given
776 specification, ``ValueError`` is raised.
777
778 Examples:
779
780 >>> _name_to_hex('white')
781 '#ffffff'
782 >>> _name_to_hex('navy')
783 '#000080'
784 >>> _name_to_hex('goldenrod')
785 '#daa520'
786 """
787 normalized = name.lower()
788 try:
789 hex_value = self._names_to_hex[normalized]
790 except KeyError:
791 raise ValueError("'%s' is not defined as a named color." % (name))
792 return hex_value
793
795 """
796 Convert a hexadecimal color value to a 3-tuple of integers
797 suitable for use in an ``rgb()`` triplet specifying that color.
798
799 The hexadecimal value will be normalized before being converted.
800
801 Examples:
802
803 >>> _hex_to_rgb('#fff')
804 (255, 255, 255)
805 >>> _hex_to_rgb('#000080')
806 (0, 0, 128)
807
808 """
809 hex_digits = self._normalize_hex(hex_value)
810 return tuple([int(s, 16) for s in (hex_digits[1:3], hex_digits[3:5], hex_digits[5:7])])
811
813 """
814 Normalize a hexadecimal color value to the following form and
815 return the result::
816
817 #[a-f0-9]{6}
818
819 In other words, the following transformations are applied as
820 needed:
821
822 * If the value contains only three hexadecimal digits, it is expanded to six.
823
824 * The value is normalized to lower-case.
825
826 If the supplied value cannot be interpreted as a hexadecimal color
827 value, ``ValueError`` is raised.
828
829 Examples:
830
831 >>> _normalize_hex('#0099cc')
832 '#0099cc'
833 >>> _normalize_hex('#0099CC')
834 '#0099cc'
835 >>> _normalize_hex('#09c')
836 '#0099cc'
837 >>> _normalize_hex('#09C')
838 '#0099cc'
839 >>> _normalize_hex('0099cc')
840 Traceback (most recent call last):
841 ...
842 ValueError: '0099cc' is not a valid hexadecimal color value.
843
844 """
845 try:
846 hex_digits = self.HEX_COLOR_RE.match(hex_value).groups()[0]
847 except AttributeError:
848 raise ValueError("'%s' is not a valid hexadecimal color value." % hex_value)
849 if len(hex_digits) == 3:
850 hex_digits = ''.join([2 * s for s in hex_digits])
851 return '#%s' % hex_digits.lower()
852
854 """
855 Convert a color name to a 3-tuple of integers suitable for use in
856 an ``rgb()`` triplet specifying that color.
857
858 The color name will be normalized to lower-case before being
859 looked up, and when no color of that name exists in the given
860 specification, ``ValueError`` is raised.
861
862 Examples:
863
864 >>> _name_to_rgb('white')
865 (255, 255, 255)
866 >>> _name_to_rgb('navy')
867 (0, 0, 128)
868 >>> _name_to_rgb('goldenrod')
869 (218, 165, 32)
870
871 """
872 return self._hex_to_rgb(self._name_to_hex(name))
873
875 """
876 BlinkStickPro class is specifically designed to control the individually
877 addressable LEDs connected to the device. The tutorials section contains
878 all the details on how to connect them to BlinkStick Pro.
879
880 U{http://www.blinkstick.com/help/tutorials}
881
882 Code example on how you can use this class are available here:
883
884 U{https://github.com/arvydas/blinkstick-python/wiki#code-examples-for-blinkstick-pro}
885 """
886
887 - def __init__(self, r_led_count=0, g_led_count=0, b_led_count=0, delay=0.002, max_rgb_value=255):
888 """
889 Initialize BlinkStickPro class.
890
891 @type r_led_count: int
892 @param r_led_count: number of LEDs on R channel
893 @type g_led_count: int
894 @param g_led_count: number of LEDs on G channel
895 @type b_led_count: int
896 @param b_led_count: number of LEDs on B channel
897 @type delay: int
898 @param delay: default transmission delay between frames
899 @type max_rgb_value: int
900 @param max_rgb_value: maximum color value for RGB channels
901 """
902
903 self.r_led_count = r_led_count
904 self.g_led_count = g_led_count
905 self.b_led_count = b_led_count
906
907 self.fps_count = -1
908
909 self.data_transmission_delay = delay
910
911 self.max_rgb_value = max_rgb_value
912
913
914
915
916 self.data = [[], [], []]
917
918 for i in range(0, r_led_count):
919 self.data[0].append([0, 0, 0])
920
921 for i in range(0, g_led_count):
922 self.data[1].append([0, 0, 0])
923
924 for i in range(0, b_led_count):
925 self.data[2].append([0, 0, 0])
926
927 self.bstick = None
928
929 - def set_color(self, channel, index, r, g, b, remap_values=True):
930 """
931 Set the color of a single pixel
932
933 @type channel: int
934 @param channel: R, G or B channel
935 @type index: int
936 @param index: the index of LED on the channel
937 @type r: int
938 @param r: red color byte
939 @type g: int
940 @param g: green color byte
941 @type b: int
942 @param b: blue color byte
943 """
944
945 if remap_values:
946 r, g, b = [_remap_color(val, self.max_rgb_value) for val in [r, g, b]]
947
948 self.data[channel][index] = [g, r, b]
949
951 """
952 Get the current color of a single pixel.
953
954 @type channel: int
955 @param channel: the channel of the LED
956 @type index: int
957 @param index: the index of the LED
958
959 @rtype: (int, int, int)
960 @return: 3-tuple for R, G and B values
961 """
962
963 val = self.data[channel][index]
964 return [val[1], val[0], val[2]]
965
967 """
968 Set all pixels to black in the frame buffer.
969 """
970 for x in range(0, self.r_led_count):
971 self.set_color(0, x, 0, 0, 0)
972
973 for x in range(0, self.g_led_count):
974 self.set_color(1, x, 0, 0, 0)
975
976 for x in range(0, self.b_led_count):
977 self.set_color(2, x, 0, 0, 0)
978
980 """
981 Set all pixels to black in on the device.
982 """
983 self.clear()
984 self.send_data_all()
985
987 """
988 Connect to the first BlinkStick found
989
990 @type serial: str
991 @param serial: Select the serial number of BlinkStick
992 """
993
994 if serial is None:
995 self.bstick = find_first()
996 else:
997 self.bstick = find_by_serial(serial=serial)
998
999 return self.bstick is not None
1000
1002 """
1003 Send data stored in the internal buffer to the channel.
1004
1005 @param channel:
1006 - 0 - R pin on BlinkStick Pro board
1007 - 1 - G pin on BlinkStick Pro board
1008 - 2 - B pin on BlinkStick Pro board
1009 """
1010 packet_data = [item for sublist in self.data[channel] for item in sublist]
1011
1012 try:
1013 self.bstick.set_led_data(channel, packet_data)
1014 time.sleep(self.data_transmission_delay)
1015 except Exception as e:
1016 print "Exception: {0}".format(e)
1017
1019 """
1020 Send data to all channels
1021 """
1022 if self.r_led_count > 0:
1023 self.send_data(0)
1024
1025 if self.g_led_count > 0:
1026 self.send_data(1)
1027
1028 if self.b_led_count > 0:
1029 self.send_data(2)
1030
1032 """
1033 BlinkStickProMatrix class is specifically designed to control the individually
1034 addressable LEDs connected to the device and arranged in a matrix. The tutorials section contains
1035 all the details on how to connect them to BlinkStick Pro with matrices.
1036
1037 U{http://www.blinkstick.com/help/tutorials/blinkstick-pro-adafruit-neopixel-matrices}
1038
1039 Code example on how you can use this class are available here:
1040
1041 U{https://github.com/arvydas/blinkstick-python/wiki#code-examples-for-blinkstick-pro}
1042
1043 Matrix is driven by using L{BlinkStickProMatrix.set_color} with [x,y] coordinates and class automatically
1044 divides data into subsets and sends it to the matrices.
1045
1046 For example, if you have 2 8x8 matrices connected to BlinkStickPro and you initialize
1047 the class with
1048
1049 >>> matrix = BlinkStickProMatrix(r_columns=8, r_rows=8, g_columns=8, g_rows=8)
1050
1051 Then you can set the internal framebuffer by using {set_color} command:
1052
1053 >>> matrix.set_color(x=10, y=5, r=255, g=0, b=0)
1054 >>> matrix.set_color(x=6, y=3, r=0, g=255, b=0)
1055
1056 And send data to both matrices in one go:
1057
1058 >>> matrix.send_data_all()
1059
1060 """
1061
1062 - 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):
1063 """
1064 Initialize BlinkStickProMatrix class.
1065
1066 @type r_columns: int
1067 @param r_columns: number of matric columns for R channel
1068 @type g_columns: int
1069 @param g_columns: number of matric columns for R channel
1070 @type b_columns: int
1071 @param b_columns: number of matric columns for R channel
1072 @type delay: int
1073 @param delay: default transmission delay between frames
1074 @type max_rgb_value: int
1075 @param max_rgb_value: maximum color value for RGB channels
1076 """
1077 r_leds = r_columns * r_rows
1078 g_leds = g_columns * g_rows
1079 b_leds = b_columns * b_rows
1080
1081 self.r_columns = r_columns
1082 self.r_rows = r_rows
1083 self.g_columns = g_columns
1084 self.g_rows = g_rows
1085 self.b_columns = b_columns
1086 self.b_rows = b_rows
1087
1088 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)
1089
1090 self.rows = max(r_rows, g_rows, b_rows)
1091 self.cols = r_columns + g_columns + b_columns
1092
1093
1094 self.matrix_data = []
1095
1096 for i in range(0, self.rows * self.cols):
1097 self.matrix_data.append([0, 0, 0])
1098
1099 - def set_color(self, x, y, r, g, b, remap_values=True):
1100 """
1101 Set the color of a single pixel in the internal framebuffer.
1102
1103 @type x: int
1104 @param x: the x location in the matrix
1105 @type y: int
1106 @param y: the y location in the matrix
1107 @type r: int
1108 @param r: red color byte
1109 @type g: int
1110 @param g: green color byte
1111 @type b: int
1112 @param b: blue color byte
1113 @type remap_values: bool
1114 @param remap_values: Automatically remap values based on the {max_rgb_value} supplied in the constructor
1115 """
1116
1117 if remap_values:
1118 r, g, b = [_remap_color(val, self.max_rgb_value) for val in [r, g, b]]
1119
1120 self.matrix_data[self._coord_to_index(x, y)] = [g, r, b]
1121
1123 return y * self.cols + x
1124
1126 """
1127 Get the current color of a single pixel.
1128
1129 @type x: int
1130 @param x: x coordinate of the internal framebuffer
1131 @type y: int
1132 @param y: y coordinate of the internal framebuffer
1133
1134 @rtype: (int, int, int)
1135 @return: 3-tuple for R, G and B values
1136 """
1137
1138 val = self.matrix_data[self._coord_to_index(x, y)]
1139 return [val[1], val[0], val[2]]
1140
1142 """
1143 Shift all LED values in the matrix to the left
1144
1145 @type remove: bool
1146 @param remove: whether to remove the pixels on the last column or move the to the first column
1147 """
1148 if not remove:
1149 temp = []
1150 for y in range(0, self.rows):
1151 temp.append(self.get_color(0, y))
1152
1153 for y in range(0, self.rows):
1154 for x in range(0, self.cols - 1):
1155 r, g, b = self.get_color(x + 1, y)
1156
1157 self.set_color(x, y, r, g, b, False)
1158
1159 if remove:
1160 for y in range(0, self.rows):
1161 self.set_color(self.cols - 1, y, 0, 0, 0, False)
1162 else:
1163 for y in range(0, self.rows):
1164 col = temp[y]
1165 self.set_color(self.cols - 1, y, col[0], col[1], col[2], False)
1166
1168 """
1169 Shift all LED values in the matrix to the right
1170
1171 @type remove: bool
1172 @param remove: whether to remove the pixels on the last column or move the to the first column
1173 """
1174
1175 if not remove:
1176 temp = []
1177 for y in range(0, self.rows):
1178 temp.append(self.get_color(self.cols - 1, y))
1179
1180 for y in range(0, self.rows):
1181 for x in reversed(range(1, self.cols)):
1182 r, g, b = self.get_color(x - 1, y)
1183
1184 self.set_color(x, y, r, g, b, False)
1185
1186 if remove:
1187 for y in range(0, self.rows):
1188 self.set_color(0, y, 0, 0, 0, False)
1189 else:
1190 for y in range(0, self.rows):
1191 col = temp[y]
1192 self.set_color(self.cols - 1, y, col[0], col[1], col[2], False)
1193
1195 """
1196 Shift all LED values in the matrix down
1197
1198 @type remove: bool
1199 @param remove: whether to remove the pixels on the last column or move the to the first column
1200 """
1201
1202 if not remove:
1203 temp = []
1204 for x in range(0, self.cols):
1205 temp.append(self.get_color(x, self.rows - 1))
1206
1207 for x in range(0, self.cols):
1208 for y in reversed(range(1, self.rows)):
1209 r, g, b = self.get_color(x, y - 1)
1210
1211 self.set_color(x, y, r, g, b, False)
1212
1213 if remove:
1214 for x in range(0, self.cols):
1215 self.set_color(x, 0, 0, 0, 0, False)
1216 else:
1217 for x in range(0, self.cols):
1218 col = temp[y]
1219 self.set_color(x, 0, col[0], col[1], col[2], False)
1220
1221
1223 """
1224 Shift all LED values in the matrix up
1225
1226 @type remove: bool
1227 @param remove: whether to remove the pixels on the last column or move the to the first column
1228 """
1229
1230 if not remove:
1231 temp = []
1232 for x in range(0, self.cols):
1233 temp.append(self.get_color(x, 0))
1234
1235 for x in range(0, self.cols):
1236 for y in range(0, self.rows - 1):
1237 r, g, b = self.get_color(x, y + 1)
1238
1239 self.set_color(x, y, r, g, b, False)
1240
1241 if remove:
1242 for x in range(0, self.cols):
1243 self.set_color(x, self.rows - 1, 0, 0, 0, False)
1244 else:
1245 for x in range(0, self.cols):
1246 col = temp[y]
1247 self.set_color(x, self.rows - 1, col[0], col[1], col[2], False)
1248
1249 - def number(self, x, y, n, r, g, b):
1250 """
1251 Render a 3x5 number n at location x,y and r,g,b color
1252
1253 @type x: int
1254 @param x: the x location in the matrix (left of the number)
1255 @type y: int
1256 @param y: the y location in the matrix (top of the number)
1257 @type n: int
1258 @param n: number digit to render 0..9
1259 @type r: int
1260 @param r: red color byte
1261 @type g: int
1262 @param g: green color byte
1263 @type b: int
1264 @param b: blue color byte
1265 """
1266 if n == 0:
1267 self.rectangle(x, y, x + 2, y + 4, r, g, b)
1268 elif n == 1:
1269 self.line(x + 1, y, x + 1, y + 4, r, g, b)
1270 self.line(x, y + 4, x + 2, y + 4, r, g, b)
1271 self.set_color(x, y + 1, r, g, b)
1272 elif n == 2:
1273 self.line(x, y, x + 2, y, r, g, b)
1274 self.line(x, y + 2, x + 2, y + 2, r, g, b)
1275 self.line(x, y + 4, x + 2, y + 4, r, g, b)
1276 self.set_color(x + 2, y + 1, r, g, b)
1277 self.set_color(x, y + 3, r, g, b)
1278 elif n == 3:
1279 self.line(x, y, x + 2, y, r, g, b)
1280 self.line(x, y + 2, x + 2, y + 2, r, g, b)
1281 self.line(x, y + 4, x + 2, y + 4, r, g, b)
1282 self.set_color(x + 2, y + 1, r, g, b)
1283 self.set_color(x + 2, y + 3, r, g, b)
1284 elif n == 4:
1285 self.line(x, y, x, y + 2, r, g, b)
1286 self.line(x + 2, y, x + 2, y + 4, r, g, b)
1287 self.set_color(x + 1, y + 2, r, g, b)
1288 elif n == 5:
1289 self.line(x, y, x + 2, y, r, g, b)
1290 self.line(x, y + 2, x + 2, y + 2, r, g, b)
1291 self.line(x, y + 4, x + 2, y + 4, r, g, b)
1292 self.set_color(x, y + 1, r, g, b)
1293 self.set_color(x + 2, y + 3, r, g, b)
1294 elif n == 6:
1295 self.line(x, y, x + 2, y, r, g, b)
1296 self.line(x, y + 2, x + 2, y + 2, r, g, b)
1297 self.line(x, y + 4, x + 2, y + 4, r, g, b)
1298 self.set_color(x, y + 1, r, g, b)
1299 self.set_color(x + 2, y + 3, r, g, b)
1300 self.set_color(x, y + 3, r, g, b)
1301 elif n == 7:
1302 self.line(x + 1, y + 2, x + 1, y + 4, r, g, b)
1303 self.line(x, y, x + 2, y, r, g, b)
1304 self.set_color(x + 2, y + 1, r, g, b)
1305 elif n == 8:
1306 self.line(x, y, x + 2, y, r, g, b)
1307 self.line(x, y + 2, x + 2, y + 2, r, g, b)
1308 self.line(x, y + 4, x + 2, y + 4, r, g, b)
1309 self.set_color(x, y + 1, r, g, b)
1310 self.set_color(x + 2, y + 1, r, g, b)
1311 self.set_color(x + 2, y + 3, r, g, b)
1312 self.set_color(x, y + 3, r, g, b)
1313 elif n == 9:
1314 self.line(x, y, x + 2, y, r, g, b)
1315 self.line(x, y + 2, x + 2, y + 2, r, g, b)
1316 self.line(x, y + 4, x + 2, y + 4, r, g, b)
1317 self.set_color(x, y + 1, r, g, b)
1318 self.set_color(x + 2, y + 1, r, g, b)
1319 self.set_color(x + 2, y + 3, r, g, b)
1320
1321 - def rectangle(self, x1, y1, x2, y2, r, g, b):
1322 """
1323 Draw a rectangle with it's corners at x1:y1 and x2:y2
1324
1325 @type x1: int
1326 @param x1: the x1 location in the matrix for first corner of the rectangle
1327 @type y1: int
1328 @param y1: the y1 location in the matrix for first corner of the rectangle
1329 @type x2: int
1330 @param x2: the x2 location in the matrix for second corner of the rectangle
1331 @type y2: int
1332 @param y2: the y2 location in the matrix for second corner of the rectangle
1333 @type r: int
1334 @param r: red color byte
1335 @type g: int
1336 @param g: green color byte
1337 @type b: int
1338 @param b: blue color byte
1339 """
1340
1341 self.line(x1, y1, x1, y2, r, g, b)
1342 self.line(x1, y1, x2, y1, r, g, b)
1343 self.line(x2, y1, x2, y2, r, g, b)
1344 self.line(x1, y2, x2, y2, r, g, b)
1345
1346 - def line(self, x1, y1, x2, y2, r, g, b):
1347 """
1348 Draw a line from x1:y1 and x2:y2
1349
1350 @type x1: int
1351 @param x1: the x1 location in the matrix for the start of the line
1352 @type y1: int
1353 @param y1: the y1 location in the matrix for the start of the line
1354 @type x2: int
1355 @param x2: the x2 location in the matrix for the end of the line
1356 @type y2: int
1357 @param y2: the y2 location in the matrix for the end of the line
1358 @type r: int
1359 @param r: red color byte
1360 @type g: int
1361 @param g: green color byte
1362 @type b: int
1363 @param b: blue color byte
1364 """
1365 points = []
1366 is_steep = abs(y2 - y1) > abs(x2 - x1)
1367 if is_steep:
1368 x1, y1 = y1, x1
1369 x2, y2 = y2, x2
1370 rev = False
1371 if x1 > x2:
1372 x1, x2 = x2, x1
1373 y1, y2 = y2, y1
1374 rev = True
1375 delta_x = x2 - x1
1376 delta_y = abs(y2 - y1)
1377 error = int(delta_x / 2)
1378 y = y1
1379 y_step = None
1380
1381 if y1 < y2:
1382 y_step = 1
1383 else:
1384 y_step = -1
1385 for x in range(x1, x2 + 1):
1386 if is_steep:
1387
1388 self.set_color(y, x, r, g, b)
1389 points.append((y, x))
1390 else:
1391
1392 self.set_color(x, y, r, g, b)
1393 points.append((x, y))
1394 error -= delta_y
1395 if error < 0:
1396 y += y_step
1397 error += delta_x
1398
1399 if rev:
1400 points.reverse()
1401 return points
1402
1404 """
1405 Set all pixels to black in the cached matrix
1406 """
1407 for y in range(0, self.rows):
1408 for x in range(0, self.cols):
1409 self.set_color(x, y, 0, 0, 0)
1410
1412 """
1413 Send data stored in the internal buffer to the channel.
1414
1415 @param channel:
1416 - 0 - R pin on BlinkStick Pro board
1417 - 1 - G pin on BlinkStick Pro board
1418 - 2 - B pin on BlinkStick Pro board
1419 """
1420
1421 start_col = 0
1422 end_col = 0
1423
1424 if channel == 0:
1425 start_col = 0
1426 end_col = self.r_columns
1427
1428 if channel == 1:
1429 start_col = self.r_columns
1430 end_col = start_col + self.g_columns
1431
1432 if channel == 2:
1433 start_col = self.r_columns + self.g_columns
1434 end_col = start_col + self.b_columns
1435
1436 self.data[channel] = []
1437
1438
1439 for y in range(0, self.rows):
1440 start = y * self.cols + start_col
1441 end = y * self.cols + end_col
1442
1443 self.data[channel].extend(self.matrix_data[start: end])
1444
1445 super(BlinkStickProMatrix, self).send_data(channel)
1446
1448 if sys.platform == "win32":
1449 devices = hid.HidDeviceFilter(vendor_id = VENDOR_ID, product_id = PRODUCT_ID).get_devices()
1450 if find_all:
1451 return devices
1452 elif len(devices) > 0:
1453 return devices[0]
1454 else:
1455 return None
1456
1457 else:
1458 return usb.core.find(find_all=find_all, idVendor=VENDOR_ID, idProduct=PRODUCT_ID)
1459
1460
1462 """
1463 Find all attached BlinkStick devices.
1464
1465 @rtype: BlinkStick[]
1466 @return: a list of BlinkStick objects or None if no devices found
1467 """
1468 result = []
1469 for d in _find_blicksticks():
1470 result.extend([BlinkStick(device=d)])
1471
1472 return result
1473
1474
1476 """
1477 Find first attached BlinkStick.
1478
1479 @rtype: BlinkStick
1480 @return: BlinkStick object or None if no devices are found
1481 """
1482 d = _find_blicksticks(find_all=False)
1483
1484 if d:
1485 return BlinkStick(device=d)
1486
1487
1489 """
1490 Find BlinkStick device based on serial number.
1491
1492 @rtype: BlinkStick
1493 @return: BlinkStick object or None if no devices are found
1494 """
1495
1496 devices = []
1497 if sys.platform == "win32":
1498 devices = [d for d in _find_blicksticks()
1499 if d.serial_number == serial]
1500 else:
1501 for d in _find_blicksticks():
1502 try:
1503 if usb.util.get_string(d, 256, 3) == serial:
1504 devices = [d]
1505 break
1506 except Exception as e:
1507 print "{0}".format(e)
1508
1509 if devices:
1510 return BlinkStick(device=devices[0])
1511
1512
1513 -def _remap(value, leftMin, leftMax, rightMin, rightMax):
1514
1515 leftSpan = leftMax - leftMin
1516 rightSpan = rightMax - rightMin
1517
1518
1519 valueScaled = float(value - leftMin) / float(leftSpan)
1520
1521
1522 return int(rightMin + (valueScaled * rightSpan))
1523
1525 return _remap(value, 0, 255, 0, max_value)
1526
1528 return _remap(value, 0, max_value, 0, 255)
1529
1531 return [_remap_color(rgb_val[0], max_value),
1532 _remap_color(rgb_val[1], max_value),
1533 _remap_color(rgb_val[2], max_value)]
1534
1536 return [_remap_color_reverse(rgb_val[0], max_value),
1537 _remap_color_reverse(rgb_val[1], max_value),
1538 _remap_color_reverse(rgb_val[2], max_value)]
1539
1542