diff --git a/__main__.py b/__main__.py new file mode 100644 index 0000000..d9d0230 --- /dev/null +++ b/__main__.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python3 +import argparse +from app.ledcontroller import LedController + +parser = argparse.ArgumentParser( + description="Adjust the RGBW lighting at the pixelbar.", + epilog="Either 1 or 4 colors can be specified. If 1 color is specified, the same color is used for all 4 groups. " + + "Colors can be specified as either 1, 2, 3 or 4 hexadecimal bytes. " + + "1 byte will be interpreted as the same value for all R,G,B and W led; " + + "2 bytes will be interpreted as a value for R, G, and B, and the other value for W; " + + "3 bytes will be interpreted as an R, G, B value and will turn off W; " + + "4 bytes will used for R, G, B, W as is.", +) +parser.add_argument( + "--device", + type=str, + help="the serial device to connect with, defaults to /dev/tty.usbserial", +) +parser.add_argument( + "--baud", type=int, help="the serial communication speed, defaults to 9600" +) +parser.add_argument( + "colors", + metavar="color", + type=str, + nargs="+", + help="set of either 1 or 4 space-delimited hexadecimal color values, can be specified as 1,2,3 or 4 hex-bytes", +) +args = parser.parse_args() + +ledController = LedController() +if args.device or args.baud: + ledController.setSerialOptions(device=args.device, baudrate=args.baud) +if args.colors: + ledController.setState(ledController.stateFromHexColors(args.colors)) + +print(f"Current colors: {ledController.getHexState()}") diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e5a0d9b --- /dev/null +++ b/app/__init__.py @@ -0,0 +1 @@ +#!/usr/bin/env python3 diff --git a/ledcontroller-server.py b/app/ledcontroller-server.py similarity index 100% rename from ledcontroller-server.py rename to app/ledcontroller-server.py diff --git a/ledcontroller.py b/app/ledcontroller.py similarity index 64% rename from ledcontroller.py rename to app/ledcontroller.py index 41086d4..d75e7cf 100644 --- a/ledcontroller.py +++ b/app/ledcontroller.py @@ -5,11 +5,12 @@ except ModuleNotFoundError: pass import argparse +from app import state from typing import List, Optional class LedController: - GROUP_COUNT = 4 # the number of LED groups defined by the STM32 controller + GROUP_COUNT = 4 # the number of LED groups defined by the STM32 controller def __init__(self) -> None: self._device = "/dev/tty.usbserial" # default device @@ -17,7 +18,7 @@ def __init__(self) -> None: # the state that was most recently sent to the controller, provided that the class instance stays alive # defaults to 4x full bright because that's what the controller does when it is powered on - self._state = self.stateFromHexColors(["ff"]) + self._state = state.GroupState() try: self._serial = serial.Serial(write_timeout=5) @@ -33,7 +34,6 @@ def setSerialOptions(self, device: Optional[str], baudrate: Optional[int]) -> No if self._serial.is_open: raise Exception("serial device is already open") - return if device: self._device = device @@ -62,16 +62,17 @@ def setState(self, state: List[bytes]) -> None: if self._serial and not self._serial.is_open: self.openDevice() - self._state = state + self._state.set_all_groups(state) if self._serial: - # prepend state with is single FF "startbyte" - buffer = b'\xff'+ b''.join(self._state) # this may throw its own exception if there's an error writing to the serial device - self._serial.write(buffer) + self._serial.write(self._state.send_format()) def getState(self) -> List[bytes]: - return self._state + return self._state.get_all_states() + + def getHexState(self) -> List[str]: + return self._state.get_hex_states() def parseHexColor(self, hex_color: str) -> bytes: hex_bytes = bytes.fromhex(hex_color) @@ -97,7 +98,6 @@ def parseHexColor(self, hex_color: str) -> bytes: return hex_bytes - def stateFromHexColors(self, hex_colors: List[str]) -> List[bytes]: if len(hex_colors) == 1: # use same value for all groups @@ -109,41 +109,3 @@ def stateFromHexColors(self, hex_colors: List[str]) -> List[bytes]: def stateToHexColors(self, state: List[bytes]) -> List[str]: return [value.hex() for value in state] - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Adjust the RGBW lighting at the pixelbar.", - epilog="Either 1 or 4 colors can be specified. If 1 color is specified, the same color is used for all 4 groups. " + - "Colors can be specified as either 1, 2, 3 or 4 hexadecimal bytes. " + - "1 byte will be interpreted as the same value for all R,G,B and W led; " + - "2 bytes will be interpreted as a value for R, G, and B, and the other value for W; " + - "3 bytes will be interpreted as an R, G, B value and will turn off W; " + - "4 bytes will used for R, G, B, W as is." - ) - parser.add_argument( - "--device", - type=str, - help="the serial device to connect with, defaults to /dev/tty.usbserial", - ) - parser.add_argument( - "--baud", - type=int, - help="the serial communication speed, defaults to 9600" - ) - parser.add_argument( - "colors", - metavar="color", - type=str, - nargs="+", - help="set of either 1 or 4 space-delimited hexadecimal color values, can be specified as 1,2,3 or 4 hex-bytes", - ) - args = parser.parse_args() - - ledController = LedController() - if args.device or args.baud: - ledController.setSerialOptions(device=args.device, baudrate=args.baud) - if args.colors: - ledController.setState(ledController.stateFromHexColors(args.colors)) - - print("Current colors: %s" % " ".join(ledController.stateToHexColors(ledController.getState()))) diff --git a/pixelLightServer.py b/app/pixelLightServer.py similarity index 100% rename from pixelLightServer.py rename to app/pixelLightServer.py diff --git a/app/state.py b/app/state.py new file mode 100644 index 0000000..540eb5e --- /dev/null +++ b/app/state.py @@ -0,0 +1,85 @@ +class GroupState: + GROUP_COUNT = 4 # the number of LED groups defined by the STM32 controller + + def __init__( + self, + ): + self.groups = [LedState()] * self.GROUP_COUNT + + def set_all_groups(self, byte_vals: list[bytes]): + assert len(self.groups) == len( + byte_vals + ), f"Numer of bytes to set ({len(byte_vals)}) needs to match number of groups ({len(self.groups)})" + + for group, values in zip(self.groups, byte_vals): + group.from_bytes(values) + + def set_all_groups_hex(self, hex_vals: list[str]): + assert len(self.groups) == len( + hex_vals + ), f"Numer of bytes to set ({len(hex_vals)}) needs to match number of groups ({len(self.groups)})" + + for group, values in zip(self.groups, hex_vals): + group.from_hex(values) + + def set_group(self, group: int, byte_vals: bytes): + assert group >= 0, "whats a negative group?" + assert group < self.GROUP_COUNT, "too big group" + self.groups[group].set_rgbw(byte_vals) + + def get_hex_states(self) -> list[str]: + return [state.hex_repr() for state in self.groups] + + def get_all_states(self) -> list[bytes]: + return [state.byte_repr() for state in self.groups] + + def send_format(self) -> bytes: + states = self.get_all_states() + # prepend state with is single FF "startbyte" + return b"\xff" + b"".join(states) + + +class LedState: + def __init__(self): + self.byte_vals: bytes = b"" + self.set_hex("FF") + + def from_hex(self, hex_vals: str): + self.set_hex(hex_vals) + + def from_bytes(self, byte_vals: bytes): + self.set_rgbw(byte_vals) + + def set_hex(self, hex_color: str): + hex_bytes = bytes.fromhex(hex_color) + hex_length = len(hex_bytes) + if hex_length == 1: + # RGB and W all equal value + # input: 0x88 output: 0x88888888 + self.set_rgbw(hex_bytes * 4) + elif hex_length == 2: + # RGB all equal, W separate value + # input: 0x8844 output: 0x88888840 + colors = hex_bytes[0:1] + white = hex_bytes[1:2] + self.set_rgbw(colors * 3 + white) + elif hex_length == 3: + # RGB only, turn off W + # input: 0x884422 output: 0x88442200 + self.set_rgbw(hex_bytes[0:3] + b"\x00") + elif hex_length == 4: + # RGBW as is + self.set_rgbw(hex_bytes) + + else: + raise ValueError("only 4 hex bytes are expected per value") + + def byte_repr(self) -> bytes: + return self.byte_vals + + def hex_repr(self) -> str: + return self.byte_vals.hex() + + def set_rgbw(self, byte_values: bytes): + assert len(byte_values) == 4 + self.byte_vals = byte_values diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29