#! /usr/bin/env python # -*- coding: utf-8 -*- # vim:fenc=utf-8:ts=8:et:sw=4:sts=4 # # Copyright © 2018 Julian Knauer # # Distributed under terms of the MIT license. """ """ from struct import (pack, unpack, calcsize) from enum import Enum import socket from collections import namedtuple _LW12_PKT_LEN = 9 _LW12_PKT_FMT = 'c6scc' _LW12_PKT_PAYLOAD_FMT = 'cccccc' _LW12_PKT_HEAD = b'\x7e' _LW12_PKT_TAIL = b'\xef' class LW12_MODE(Enum): LIGHT = b'\x04' EFFECT = b'\x05' COLOR = b'\x07' IGNORE = b'\xff' class LW12_CONTROL(Enum): RGB = b'\x05' SCAN_CTRL = b'\x09' IGNORE = b'\xff' class LW12_LIGHT(Enum): BRIGHTNESS = b'\x01' FLASH = b'\x02' SET = b'\x03' POWER = b'\x04' DIM = b'\x05' # Unused: To be used with LW12_MODE.EFFECT IGNORE = b'\xff' class LW12_POWER(Enum): ON = b'\x01' OFF = b'\x00' IGNORE = b'\xff' class LW12_EFFECT(Enum): STATIC_RED = b'\x80' STATIC_BLUE = b'\x81' STATIC_GREEN = b'\x82' STATIC_CYAN = b'\x83' STATIC_YELLOW = b'\x84' STATIC_PURPLE = b'\x85' STATIC_WHITE = b'\x86' JUMP_TRICOLOR = b'\x87' JUMP_SEVENCOLOR = b'\x88' GRADIENT_TRICOLOR = b'\x89' GRADIENT_SEVENCOLOR = b'\x8a' GRADIENT_RED = b'\x8b' GRADIENT_GREEN = b'\x8c' GRADIENT_BLUE = b'\x8d' GRADIENT_YELLOW = b'\x8e' GRADIENT_CYAN = b'\x8f' GRADIENT_PURPLE = b'\x90' GRADIENT_WHITE = b'\x91' GRADIENT_RED_GREEN = b'\x92' GRADIENT_RED_BLUE = b'\x93' GRADIENT_GREEN_BLUE = b'\x94' FLASH_SEVENCOLOR = b'\x95' FLASH_RED = b'\x96' FLASH_GREEN = b'\x97' FLASH_BLUE = b'\x98' FLASH_YELLOW = b'\x99' FLASH_CYAN = b'\x9a' FLASH_PURPLE = b'\x9b' FLASH_WHITE = b'\x9c' class Color(object): def __init__(self, value): self._value = value @property def value(self): return bytes([self._value]) def __repr__(self): return ''.format(self.value) LW12_Packet = namedtuple('Packet', 'head data pad tail') LW12_Payload = namedtuple('Payload', 'mode option value r g b') sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) class LW12Controller(object): def __init__(self, host, port, detect=False): self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.host = host self.port = port self._detect_controllers = detect if detect: self.socket_receiver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.socket_receiver.bind(('', 6000)) def get_remote_socket(self): return (self.host, self.port) def send(self, payload, sock=None): data = self._compile_packet(payload) if payload.option == LW12_CONTROL.SCAN_CTRL: pad = b'\xff' else: pad = b'\x00' packet = pack(_LW12_PKT_FMT, _LW12_PKT_HEAD, data, pad, _LW12_PKT_TAIL) if len(packet) != 9: raise Exception('Invalid data length. Packet malformed') try: return self.socket.sendto(packet, sock or self.get_remote_socket()) except OSError as oserr: if oserr.errno == 101: print('Network is unreachable') def read(self): if not self._detect_controllers: return None buffer_, sender = self.socket_receiver.recvfrom(_LW12_PKT_LEN) print(buffer_) print(sender) def _compile_packet(self, payload): if len(payload) != 6: raise Exception('Invalid payload length. Packet malformed') try: raw_value = payload.value.value except AttributeError: raw_value = payload.value return pack(_LW12_PKT_PAYLOAD_FMT, payload.mode.value, payload.option.value, raw_value, payload.r.value, payload.g.value, payload.b.value) def light_off(self): payload = LW12_Payload(r=LW12_LIGHT.IGNORE, g=LW12_LIGHT.IGNORE, b=LW12_LIGHT.IGNORE, mode=LW12_MODE.LIGHT, option=LW12_LIGHT.POWER, value=LW12_POWER.OFF) self.send(payload) def light_on(self): payload = LW12_Payload(r=LW12_LIGHT.IGNORE, g=LW12_LIGHT.IGNORE, b=LW12_LIGHT.IGNORE, mode=LW12_MODE.LIGHT, option=LW12_LIGHT.POWER, value=LW12_POWER.ON) self.send(payload) def set_effect(self, effect): payload = LW12_Payload(r=LW12_LIGHT.SET, g=LW12_LIGHT.IGNORE, b=LW12_LIGHT.IGNORE, mode=LW12_MODE.LIGHT, option=LW12_LIGHT.SET, value=effect) self.send(payload) def set_light_option(self, option, value): # Fail safe handling for integers > 100 if 0 < value > 100: value = int(value / 255 * 100) payload = LW12_Payload(r=LW12_LIGHT.IGNORE, g=LW12_LIGHT.IGNORE, b=LW12_LIGHT.IGNORE, mode=LW12_MODE.LIGHT, option=option, value=bytes([value])) self.send(payload) def set_color(self, red, green, blue): payload = LW12_Payload(r=Color(red), g=Color(green), b=Color(blue), mode=LW12_MODE.COLOR, option=LW12_CONTROL.RGB, value=LW12_LIGHT.SET) self.send(payload) def scan(self, broadcast_addr='255.255.255.255', broadcast_port=5000): if not self._detect_controllers: return None # Fail safe handling for integers > 100 payload = LW12_Payload(r=LW12_DETECT.IGNORE, g=LW12_DETECT.IGNORE, b=LW12_DETECT.IGNORE, mode=LW12_MODE.DETECT, option=LW12_DETECT.SCAN_CTRL, value=LW12_DETECT.IGNORE) self.send(payload, (broadcast_addr, broadcast_port)) self.read()