From 54a597e918db5c96ded39ebe7453e370472fb7ce Mon Sep 17 00:00:00 2001 From: jath03 Date: Fri, 5 Jun 2020 14:39:17 -0700 Subject: [PATCH] added mode setting, fixed style, cleaned up extra dependancies. --- README.md | 8 ++--- docs/intro.md | 8 ++--- openrgb/network.py | 39 +++++++-------------- openrgb/orgb.py | 71 ++++++++++++++++++++++++++++++-------- openrgb/utils.py | 86 ++++++++++++++++++++++++++++++++++------------ setup.py | 2 +- 6 files changed, 142 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index 3a77fa1..333b652 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,15 @@ SDK Feature Support: - [x] Setting color by device - [x] Setting color by zone - [x] Setting color by led - - [ ] Setting mode - - [ ] Setting custom modes + - [x] Setting mode + - [x] Setting custom mode - [ ] resizing zones # Installation requires python >= 3.7 -Use this method for the newest, but probably buggy, package: +Use this method for the newest, but possibly buggy, package: `pip3 install git+https://github.com/jath03/openrgb-python#egg=openrgb-python` @@ -51,4 +51,4 @@ motherboard.zones[1].leds[0].set_color(RGBColor.fromHSV(0, 100, 100)) https://openrgb-python.readthedocs.io/en/stable/ -For a more fully-featured python implementation, check out [B Horn](https://github.com/bahorn)'s [OpenRGB-PyClient](https://github.com/bahorn/OpenRGB-PyClient) +For an alternative python implementation, check out [B Horn](https://github.com/bahorn)'s [OpenRGB-PyClient](https://github.com/bahorn/OpenRGB-PyClient) diff --git a/docs/intro.md b/docs/intro.md index f317416..ad1ecef 100644 --- a/docs/intro.md +++ b/docs/intro.md @@ -12,15 +12,15 @@ SDK Feature Support: - [x] Setting color by device - [x] Setting color by zone - [x] Setting color by led - - [ ] Setting mode - - [ ] Setting custom modes + - [x] Setting mode + - [x] Setting custom mode - [ ] resizing zones # Installation requires python >= 3.7 -Use this method for the newest, but probably buggy, package: +Use this method for the newest, but possibly buggy, package: `pip3 install git+https://github.com/jath03/openrgb-python#egg=openrgb-python` @@ -46,4 +46,4 @@ motherboard.zones[1].leds[0].set_color(RGBColor.fromHSV(0, 100, 100)) ``` -For a more fully-featured python implementation, check out [B Horn](https://github.com/bahorn)'s [OpenRGB-PyClient](https://github.com/bahorn/OpenRGB-PyClient) +For an alternative python implementation, check out [B Horn](https://github.com/bahorn)'s [OpenRGB-PyClient](https://github.com/bahorn/OpenRGB-PyClient) diff --git a/openrgb/network.py b/openrgb/network.py index f6f65fc..e292282 100644 --- a/openrgb/network.py +++ b/openrgb/network.py @@ -3,7 +3,7 @@ import struct import threading from openrgb import utils -from typing import Callable, List, Union, Tuple +from typing import Callable from time import sleep @@ -11,6 +11,7 @@ class NetworkClient(object): ''' A class for interfacing with the OpenRGB SDK ''' + def __init__(self, update_callback: Callable, address: str = "127.0.0.1", port: int = 1337, name: str = "openrgb-python"): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) for x in range(5): @@ -87,7 +88,7 @@ def parseDeviceDescription(self, data: bytearray) -> utils.ControllerData: device_type = buff[1] metadata = [] for x in range(5): - location, val = self.parseSizeAndString(data, location) + location, val = utils.parse_string(data, location) metadata.append(val) buff = struct.unpack("=Hi", data[location:location + struct.calcsize("=Hi")]) location += struct.calcsize("=Hi") @@ -95,21 +96,19 @@ def parseDeviceDescription(self, data: bytearray) -> utils.ControllerData: active_mode = buff[-1] modes = [] for x in range(num_modes): - location, val = self.parseSizeAndString(data, location) + location, val = utils.parse_string(data, location) buff = list(struct.unpack("i8IH", data[location:location + struct.calcsize("i8IH")])) location += struct.calcsize("i8IH") - buff[4] = utils.intToRGB(buff[4]) - buff[5] = utils.intToRGB(buff[5]) colors = [] - for x in range(buff[-1]): - colors.append(utils.RGBColor.unpack(data[location:location + struct.calcsize("I")])) - location += struct.calcsize('BBBx') - modes.append(utils.ModeData(val.strip('\x00'), buff[0], utils.ModeFlags(buff[1]), *buff[2:7], utils.ModeDirections(buff[8]), utils.ModeColors(buff[9]), colors)) + for i in range(buff[-1]): + location, color = utils.RGBColor.unpack(data, location) + colors.append(color) + modes.append(utils.ModeData(x, val.strip('\x00'), buff[0], utils.ModeFlags(buff[1]), *buff[2:7], utils.ModeDirections(buff[8]), utils.ModeColors(buff[9]), colors)) num_zones = struct.unpack("H", data[location:location + struct.calcsize("H")])[0] location += struct.calcsize("H") zones = [] for x in range(num_zones): - location, val = self.parseSizeAndString(data, location) + location, val = utils.parse_string(data, location) buff = list(struct.unpack("iIIIH", data[location:location + struct.calcsize("iIIIH")])) location += struct.calcsize("iIIIH") @@ -128,7 +127,7 @@ def parseDeviceDescription(self, data: bytearray) -> utils.ControllerData: location += struct.calcsize("H") leds = [] for x in range(num_leds): - location, name = self.parseSizeAndString(data, location) + location, name = utils.parse_string(data, location) value = struct.unpack("I", data[location:location + struct.calcsize("I")])[0] location += struct.calcsize("I") leds.append(utils.LEDData(name.strip("\x00"), value)) @@ -136,8 +135,8 @@ def parseDeviceDescription(self, data: bytearray) -> utils.ControllerData: location += struct.calcsize("H") colors = [] for x in range(num_colors): - colors.append(utils.RGBColor.unpack(data[location:location + struct.calcsize("BBBx")])) - location += struct.calcsize("BBBx") + location, color = utils.RGBColor.unpack(data, location) + colors.append(color) for zone in zones: zone.leds = [] zone.colors = [] @@ -167,20 +166,6 @@ def parseDeviceDescription(self, data: bytearray) -> utils.ControllerData: active_mode ) - def parseSizeAndString(self, data: bytes, start: int = 0) -> Tuple[int, str]: - ''' - Parses a string based on a size. - - :param data: the raw data to parse - :param start: the location in the data to start parsing at - :returns: the location in the data of the end of the string and the string itself - ''' - size = struct.unpack('H', data[start:start + struct.calcsize('H')])[0] - start += struct.calcsize("H") - val = struct.unpack(f"{size}s", data[start:start + size])[0].decode() - start += size - return start, val.strip("\x00") - def send_header(self, device_id: int, packet_type: int, packet_size: int): ''' Sends a header to the SDK diff --git a/openrgb/orgb.py b/openrgb/orgb.py index 125de8b..282c442 100644 --- a/openrgb/orgb.py +++ b/openrgb/orgb.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import struct from openrgb import utils -from typing import Callable, List, Union, Tuple +from typing import List, Union from openrgb.network import NetworkClient # from dataclasses import dataclass from time import sleep @@ -11,6 +11,7 @@ class LED(utils.RGBObject): ''' A class to represent individual LEDs ''' + def __init__(self, data: utils.LEDData, led_id: int, device_id: int, network_client: NetworkClient): self.name = data.name self.id = led_id @@ -32,6 +33,7 @@ class Zone(utils.RGBObject): ''' A class to represent a zone ''' + def __init__(self, data: utils.ZoneData, zone_id: int, device_id: int, network_client: NetworkClient): self.name = data.name self.type = data.zone_type @@ -81,6 +83,7 @@ class Device(utils.RGBObject): ''' A class to represent a RGB Device ''' + def __init__(self, data: utils.ControllerData, device_id: int, network_client: NetworkClient): self.name = data.name self.metadata = data.metadata @@ -101,13 +104,16 @@ def set_color(self, color: utils.RGBColor, start: int = 0, end: int = 0): :param start: the first LED to change :param end: the last LED to change ''' - self._set_color( - self.leds, + if end == 0: + end = len(self.leds) + self.comms.send_header( + self.id, utils.PacketType.NET_PACKET_ID_RGBCONTROLLER_UPDATELEDS, - color, - start, - end + struct.calcsize(f"IH{3*(end - start)}b{(end - start)}x") ) + buff = struct.pack("H", end - start) + (color.pack())*(end - start) + buff = struct.pack("I", len(buff)) + buff + self.comms.sock.send(buff) def set_colors(self, colors: List[utils.RGBColor], start: int = 0, end: int = 0): ''' @@ -117,20 +123,55 @@ def set_colors(self, colors: List[utils.RGBColor], start: int = 0, end: int = 0) :param start: the first LED to change :param end: the last LED to change ''' - self._set_colors( - self.leds, + if end == 0: + end = len(self.leds) + if len(colors) != (end - start): + raise IndexError("Number of colors doesn't match number of LEDs") + self.comms.send_header( + self.id, utils.PacketType.NET_PACKET_ID_RGBCONTROLLER_UPDATELEDS, - colors, - start, - end + struct.calcsize(f"IH{3*(end - start)}b{(end - start)}x") + ) + buff = struct.pack("H", end - start) + b''.join((color.pack() for color in colors)) + buff = struct.pack("I", len(buff)) + buff + self.comms.sock.send(buff) + + def set_mode(self, mode: Union[int, str, utils.ModeData]): + ''' + Sets the device's mode + + :param mode: the id, name, or the ModeData object itself to set as the mode + ''' + if type(mode) == utils.ModeData: + pass + elif type(mode) == int: + mode = self.modes[mode] + elif type(mode) == str: + mode = next((m for m in self.modes if m.name.lower() == mode.lower())) + # print(mode) + size, data = mode.pack() + self.comms.send_header( + self.id, + utils.PacketType.NET_PACKET_ID_RGBCONTROLLER_UPDATEMODE, + size + ) + self.comms.sock.send(data) + + def set_custom_mode(self): + self.comms.send_header( + self.id, + utils.PacketType.NET_PACKET_ID_RGBCONTROLLER_SETCUSTOMMODE, + 0 ) - # def set_mode(self, ) class OpenRGBClient(object): ''' - This is the only class you should ever need to instantiate. It initializes the communication, gets the device information, and creates Devices, Zones, and LEDs for you. + This is the only class you should ever need to instantiate. It initializes + the communication, gets the device information, sets the devices to the + custom mode and creates Devices, Zones, and LEDs for you. ''' + def __init__(self, address: str = "127.0.0.1", port: int = 1337, name: str = "openrgb-python"): self.comms = NetworkClient(self._callback, address, port, name) self.address = address @@ -142,7 +183,9 @@ def __init__(self, address: str = "127.0.0.1", port: int = 1337, name: str = "op self.devices = [None for x in range(self.device_num)] for x in range(self.device_num): self.comms.requestDeviceData(x) - sleep(1) # Giving the client time to recieve the device data + sleep(1) # Giving the client time to recieve the device data + for dev in self.devices: + dev.set_custom_mode() def __repr__(self): return f"OpenRGBClient(address={self.address}, port={self.port}, name={self.name})" diff --git a/openrgb/utils.py b/openrgb/utils.py index f9e6ca4..1d8bce5 100644 --- a/openrgb/utils.py +++ b/openrgb/utils.py @@ -1,5 +1,5 @@ from enum import IntEnum, IntFlag -from typing import List, TypeVar, Type +from typing import List, TypeVar, Type, Tuple from dataclasses import dataclass import struct import colorsys @@ -64,9 +64,25 @@ class PacketType(IntEnum): NET_PACKET_ID_RGBCONTROLLER_SETCUSTOMMODE = 1100 NET_PACKET_ID_RGBCONTROLLER_UPDATEMODE = 1101 + CT = TypeVar("CT", bound="RGBColor") +def parse_string(data: bytes, start: int = 0) -> Tuple[int, str]: + ''' + Parses a string based on a size. + + :param data: the raw data to parse + :param start: the location in the data to start parsing at + :returns: the location in the data of the end of the string and the string itself + ''' + size = struct.unpack('H', data[start:start + struct.calcsize('H')])[0] + start += struct.calcsize("H") + val = struct.unpack(f"{size}s", data[start:start + size])[0].decode() + start += size + return start, val.strip("\x00") + + @dataclass class RGBColor(object): red: int @@ -82,9 +98,14 @@ def pack(self) -> bytearray: return struct.pack("BBBx", self.red, self.green, self.blue) @classmethod - def unpack(cls: Type[CT], data: bytearray) -> CT: - r, g, b = struct.unpack("BBBx", data) - return RGBColor(r, g, b) + def unpack(cls: Type[CT], data: bytearray, start: int = 0) -> Tuple[int, CT]: + size = struct.calcsize("BBBx") + if start == 0: + r, g, b = struct.unpack("BBBx", data[:size]) + return size, RGBColor(r, g, b) + else: + r, g, b = struct.unpack("BBBx", data[start:start + size]) + return (start + size), RGBColor(r, g, b) @classmethod def fromHSV(cls: Type[CT], hue: int, saturation: int, value: int) -> CT: @@ -103,6 +124,7 @@ class LEDData(object): @dataclass class ModeData(object): + id: int name: str value: int flags: ModeFlags @@ -115,6 +137,39 @@ class ModeData(object): color_mode: ModeColors colors: List[RGBColor] + def pack(self) -> Tuple[int, bytearray]: + ''' + Packs itself into bytes ready to be sent to the SDK + + :returns: data ready to be sent and its size + ''' + # IDK why size = struct.calcsize(f"IiH{len(self.name)}si8IH{len(self.colors)}I") + # doesn't work without the if, but when self.colors is empty it still adds 2 bytes on for it + if len(self.colors) == 0: + size = struct.calcsize(f"IiH{len(self.name)}si8IH") + else: + size = struct.calcsize(f"IiH{len(self.name)}si8IH{len(self.colors)}I") + data = struct.pack( + f"IiH{len(self.name)}si8IH", + size, + self.id, + len(self.name), + self.name.encode('utf-8'), + self.value, + self.flags, + self.speed_min, + self.speed_max, + self.colors_min, + self.colors_max, + self.speed, + self.direction, + self.color_mode, + len(self.colors) + ) + data += bytearray((color.pack() for color in self.colors)) + + return size, data + @dataclass class ZoneData(object): @@ -152,6 +207,11 @@ class ControllerData(object): class RGBObject(object): + ''' + A parent object that includes a few generic functions that use the + implementation provided by the children. + ''' + def __init__(self, comms, name: str, device_id: int): self.comms = comms self.name = name @@ -163,27 +223,9 @@ def __repr__(self): def set_color(self, color: RGBColor, start: int = 0, end: int = 0): pass - def _set_color(self, children: list, type: PacketType, color: RGBColor, start: int = 0, end: int = 0): - if end == 0: - end = len(children) - self.comms.send_header(self.id, type, struct.calcsize(f"IH{3*(end - start)}b{(end - start)}x")) - buff = struct.pack("H", end - start) + (color.pack())*(end - start) - buff = struct.pack("I", len(buff)) + buff - self.comms.sock.send(buff) - def set_colors(self, colors: List[RGBColor], start: int = 0, end: int = 0): pass - def _set_colors(self, children: list, type: PacketType, colors: List[RGBColor], start: int = 0, end: int = 0): - if end == 0: - end = len(children) - if len(colors) != (end - start): - raise IndexError("Number of colors doesn't match number of LEDs") - self.comms.send_header(self.id, type, struct.calcsize(f"IH{3*(end - start)}b{(end - start)}x")) - buff = struct.pack("H", end - start) + b''.join((color.pack() for color in colors)) - buff = struct.pack("I", len(buff)) + buff - self.comms.sock.send(buff) - def clear(self): self.set_color(RGBColor(0, 0, 0)) diff --git a/setup.py b/setup.py index 82d104e..b1fd5d2 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="openrgb-python", - version='0.0.2', + version='0.0.3', author="jath03", description="A python client for the OpenRGB SDK", long_description=long_description,