Permalink
Cannot retrieve contributors at this time
Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.
Sign up| """ | |
| blink1.py -- blink(1) Python library using PyUSB | |
| Based on blink1hid-demo.py by Aaron Blondeau and the reference Python Plink(1) | |
| library by Tod E. Kurt, http://thingm.com/ | |
| Uses "PyUSB 1.0" to do direct USB HID commands | |
| See: https://github.com/walac/pyusb | |
| All platforms: | |
| % sudo pip install blink1 | |
| Windows Only | |
| You will need to manually install libusb-win32 | |
| """ | |
| import logging | |
| import time | |
| import sys | |
| from contextlib import contextmanager | |
| import usb | |
| import webcolors | |
| from .kelvin import kelvin_to_rgb, COLOR_TEMPERATURES | |
| class BlinkConnectionFailed(RuntimeError): | |
| """Raised when we cannot connect to a Blink(1) | |
| """ | |
| log = logging.getLogger(__name__) | |
| DEFAULT_GAMMA = (2, 2, 2) | |
| DEFAULT_WHITE_POINT = (255, 255, 255) | |
| REPORT_ID = 0x01 | |
| VENDOR_ID = 0x27b8 | |
| PRODUCT_ID = 0x01ed | |
| class ColorCorrect(object): | |
| """Apply a gamma correction to any selected RGB color, see: | |
| http://en.wikipedia.org/wiki/Gamma_correction | |
| """ | |
| def __init__(self, gamma, white_point): | |
| """ | |
| :param gamma: Tuple of r,g,b gamma values | |
| :param white_point: White point expressed as (r,g,b), integer color temperature (in Kelvin) or a string value. | |
| All gamma values should be 0 > x >= 1 | |
| """ | |
| self.gamma = gamma | |
| if isinstance(white_point, str): | |
| kelvin = COLOR_TEMPERATURES[white_point] | |
| self.white_point = kelvin_to_rgb(kelvin) | |
| elif isinstance(white_point,(int,float)): | |
| self.white_point = kelvin_to_rgb(white_point) | |
| else: | |
| self.white_point = white_point | |
| @staticmethod | |
| def gamma_correct(gamma, white, luminance): | |
| return round(white * (luminance / 255) ** gamma) | |
| def __call__(self, r, g, b): | |
| color = [r,g,b] | |
| return tuple(self.gamma_correct(g, w, l) for (g, w, l) in zip(self.gamma, self.white_point, color) ) | |
| class Blink1: | |
| """Light controller class, sends messages to the blink(1) and blink(1) mk2 via USB HID. | |
| """ | |
| def __init__(self, gamma=None, white_point=None): | |
| """ | |
| :param gamma: Triple of gammas for each channel e.g. (2, 2, 2) | |
| """ | |
| self.cc = ColorCorrect( | |
| gamma=(gamma or DEFAULT_GAMMA), | |
| white_point=(white_point or DEFAULT_WHITE_POINT) | |
| ) | |
| self.dev = self.find() | |
| if not self.dev: | |
| raise BlinkConnectionFailed("Could not find an attached Blink(1)") | |
| def close(self): | |
| self.dev = None | |
| @staticmethod | |
| def find(): | |
| dev = usb.core.find(idVendor=VENDOR_ID, idProduct=PRODUCT_ID) | |
| if dev == None: | |
| return None | |
| if not sys.platform.startswith('win'): | |
| if ( dev.is_kernel_driver_active(0) ): | |
| try: | |
| dev.detach_kernel_driver(0) | |
| except usb.core.USBError as e: | |
| sys.exit("Could not detatch kernel driver: %s" % str(e)) | |
| return dev | |
| def notfound(self): | |
| return None # fixme what to do here | |
| def write(self, buf): | |
| """ | |
| Write command to blink(1) | |
| Send USB Feature Report 0x01 to blink(1) with 8-byte payload | |
| Note: arg 'buf' must be 8 bytes or bad things happen | |
| """ | |
| log.debug("blink1write:" + ",".join('0x%02x' % v for v in buf)) | |
| if ( self.dev == None ): return self.notfound() | |
| bmRequestTypeOut = usb.util.build_request_type(usb.util.CTRL_OUT, usb.util.CTRL_TYPE_CLASS, | |
| usb.util.CTRL_RECIPIENT_INTERFACE) | |
| self.dev.ctrl_transfer(bmRequestTypeOut, | |
| 0x09, # == HID set_report | |
| (3 << 8) | REPORT_ID, # (3==HID feat.report) | |
| 0, | |
| buf) | |
| def read(self): | |
| """ | |
| Read command result from blink(1) | |
| Receive USB Feature Report 0x01 from blink(1) with 8-byte payload | |
| Note: buf must be 8 bytes or bad things happen | |
| """ | |
| bmRequestTypeIn = usb.util.build_request_type(usb.util.CTRL_IN, usb.util.CTRL_TYPE_CLASS, | |
| usb.util.CTRL_RECIPIENT_INTERFACE) | |
| buf = self.dev.ctrl_transfer(bmRequestTypeIn, | |
| 0x01, # == HID get_report | |
| (3 << 8) | REPORT_ID, | |
| 0, | |
| 8) # == number of bytes to read | |
| log.debug("blink1read: " + ",".join('0x%02x' % v for v in buf)) | |
| return buf | |
| def fade_to_rgb_uncorrected(self, fade_milliseconds, red, green, blue, led_number=0): | |
| """ | |
| Command blink(1) to fade to RGB color, no color correction applied. | |
| """ | |
| action = ord('c') | |
| fade_time = int(fade_milliseconds / 10) | |
| th = (fade_time & 0xff00) >> 8 | |
| tl = fade_time & 0x00ff | |
| buf = [REPORT_ID, action, red, green, blue, th, tl, led_number] | |
| return self.write(buf) | |
| def fade_to_rgb(self,fade_milliseconds, red, green, blue, led_number=0): | |
| r, g, b = self.cc(red, green, blue) | |
| return self.fade_to_rgb_uncorrected(fade_milliseconds, r, g, b, led_number=0) | |
| def fade_to_color(self, fade_milliseconds, color): | |
| """ | |
| Fade the light to a known colour in a | |
| :param fade_milliseconds: Duration of the fade in milliseconds | |
| :param color: Named color to fade to | |
| :return: None | |
| """ | |
| red, green, blue = webcolors.name_to_rgb(color) | |
| return self.fade_to_rgb(fade_milliseconds, red, green, blue) | |
| def off(self): | |
| """Switch the blink(1) off instantly | |
| """ | |
| self.fade_to_color(0, 'black') | |
| def get_version(self): | |
| """Get blink(1) firmware version | |
| """ | |
| if ( self.dev == None ): return '' | |
| buf = [0x01, ord('v'), 0, 0, 0, 0, 0, 0] | |
| self.write(buf) | |
| time.sleep(.05) | |
| version_raw = self.read() | |
| version = (version_raw[3] - ord('0')) * 100 + (version_raw[4] - ord('0')) | |
| return str(version) | |
| def get_serial_number(self): | |
| """Get blink(1) serial number | |
| """ | |
| return usb.util.get_string(self.dev, 256, 3) | |
| @contextmanager | |
| def blink1(switch_off=True, gamma=None, white_point=None): | |
| """Context manager which automatically shuts down the Blink(1) | |
| after use. | |
| """ | |
| b1 = Blink1(gamma=gamma, white_point=white_point) | |
| yield b1 | |
| if switch_off: | |
| b1.off() | |
| b1.close() |