From 0a13d28371602ef147b6f621c0131dd25e0e45e2 Mon Sep 17 00:00:00 2001 From: Andre van Dam Date: Wed, 18 May 2022 19:27:55 +0200 Subject: [PATCH 1/3] feat(state): refactored state class --- state.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 state.py diff --git a/state.py b/state.py new file mode 100644 index 0000000..e755bbf --- /dev/null +++ b/state.py @@ -0,0 +1,72 @@ +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_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 send_format(self) -> bytes: + + states = [state.byte_repr() for state in self.groups] + # 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 to_hex(self) -> list[str]: + return [hex(a) for a in self.byte_vals] + + def set_rgbw(self, byte_values: bytes): + assert len(byte_values) == 4 + self.byte_vals = byte_values From 294731adfb4a373a4c58895accc4315018b28836 Mon Sep 17 00:00:00 2001 From: Andre van Dam Date: Wed, 18 May 2022 19:47:46 +0200 Subject: [PATCH 2/3] feat(ledcontoller): using abstract state --- ledcontroller.py | 36 +++++++++++++++++------------------- state.py | 13 ++++++++++++- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/ledcontroller.py b/ledcontroller.py index 41086d4..aea107b 100644 --- a/ledcontroller.py +++ b/ledcontroller.py @@ -5,11 +5,12 @@ except ModuleNotFoundError: pass import argparse +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,14 @@ 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 parseHexColor(self, hex_color: str) -> bytes: hex_bytes = bytes.fromhex(hex_color) @@ -97,7 +95,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 @@ -114,12 +111,12 @@ def stateToHexColors(self, state: List[bytes]) -> List[str]: 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." + 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", @@ -127,9 +124,7 @@ def stateToHexColors(self, state: List[bytes]) -> List[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" + "--baud", type=int, help="the serial communication speed, defaults to 9600" ) parser.add_argument( "colors", @@ -146,4 +141,7 @@ def stateToHexColors(self, state: List[bytes]) -> List[str]: if args.colors: ledController.setState(ledController.stateFromHexColors(args.colors)) - print("Current colors: %s" % " ".join(ledController.stateToHexColors(ledController.getState()))) + print( + "Current colors: %s" + % " ".join(ledController.stateToHexColors(ledController.getState())) + ) diff --git a/state.py b/state.py index e755bbf..c74278b 100644 --- a/state.py +++ b/state.py @@ -14,14 +14,25 @@ def set_all_groups(self, byte_vals: list[bytes]): 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_all_states(self) -> list[bytes]: + return [state.byte_repr() for state in self.groups] + def send_format(self) -> bytes: - states = [state.byte_repr() for state in self.groups] + states = self.get_all_states() # prepend state with is single FF "startbyte" return b"\xff" + b"".join(states) From 3db151e97a56b24941b42c4ce9d7eb30627bf391 Mon Sep 17 00:00:00 2001 From: Andre van Dam Date: Wed, 18 May 2022 22:05:45 +0200 Subject: [PATCH 3/3] feat(all): change folder structure added app-test folder structure --- __main__.py | 37 ++++++++++++++++ app/__init__.py | 1 + .../ledcontroller-server.py | 0 ledcontroller.py => app/ledcontroller.py | 44 ++----------------- .../pixelLightServer.py | 0 state.py => app/state.py | 8 ++-- test/__init__.py | 0 7 files changed, 47 insertions(+), 43 deletions(-) create mode 100644 __main__.py create mode 100644 app/__init__.py rename ledcontroller-server.py => app/ledcontroller-server.py (100%) rename ledcontroller.py => app/ledcontroller.py (70%) rename pixelLightServer.py => app/pixelLightServer.py (100%) rename state.py => app/state.py (93%) create mode 100644 test/__init__.py 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 70% rename from ledcontroller.py rename to app/ledcontroller.py index aea107b..d75e7cf 100644 --- a/ledcontroller.py +++ b/app/ledcontroller.py @@ -5,7 +5,7 @@ except ModuleNotFoundError: pass import argparse -import state +from app import state from typing import List, Optional @@ -71,6 +71,9 @@ def setState(self, state: List[bytes]) -> None: def getState(self) -> List[bytes]: 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) hex_length = len(hex_bytes) @@ -106,42 +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/state.py b/app/state.py similarity index 93% rename from state.py rename to app/state.py index c74278b..540eb5e 100644 --- a/state.py +++ b/app/state.py @@ -27,11 +27,13 @@ def set_group(self, group: int, byte_vals: bytes): 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) @@ -75,8 +77,8 @@ def set_hex(self, hex_color: str): def byte_repr(self) -> bytes: return self.byte_vals - def to_hex(self) -> list[str]: - return [hex(a) for a in 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 diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29