Skip to content

Draft: Refactored abstract state #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions __main__.py
Original file line number Diff line number Diff line change
@@ -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()}")
1 change: 1 addition & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#!/usr/bin/env python3
File renamed without changes.
56 changes: 9 additions & 47 deletions ledcontroller.py → app/ledcontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@
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
self._baudrate = 9600 # default baudrate

# 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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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())))
File renamed without changes.
85 changes: 85 additions & 0 deletions app/state.py
Original file line number Diff line number Diff line change
@@ -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
Empty file added test/__init__.py
Empty file.