Skip to content

Commit af4d85d

Browse files
JnyJnyclaude
andcommitted
Refactor color handling architecture across all devices
- Moved color property from ColorableMixin to abstract Light base class - Updated ColorableMixin to use property descriptors for better performance - Modified all vendor implementations to properly implement color property - Updated test files to reflect new inheritance patterns - Renamed test_vendor_muteme.py to test_vendor_muteme_muteme.py for clarity 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 2c26238 commit af4d85d

17 files changed

+174
-227
lines changed

src/busylight_core/light.py

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@
3838
NoLightsFoundError,
3939
)
4040
from .hardware import Hardware
41-
from .mixins import ColorableMixin, TaskableMixin
41+
from .mixins import TaskableMixin
4242

4343

44-
class Light(abc.ABC, ColorableMixin, TaskableMixin):
44+
class Light(abc.ABC, TaskableMixin):
4545
"""Base class for USB connected lights.
4646
4747
This base class provides a common interface for USB connected lights.
@@ -478,3 +478,23 @@ def reset(self) -> None:
478478
def __bytes__(self) -> bytes:
479479
"""Return the light's state suitable for writing to the device."""
480480
raise NotImplementedError
481+
482+
@property
483+
@abc.abstractmethod
484+
def color(self) -> tuple[int, int, int]:
485+
"""Get the current RGB color of the light."""
486+
raise NotImplementedError
487+
488+
@color.setter
489+
@abc.abstractmethod
490+
def color(self, value: tuple[int, int, int]) -> None:
491+
"""Set the RGB color of the light.
492+
493+
:param: value: tuple[int, int, int] - RGB color tuple
494+
"""
495+
raise NotImplementedError
496+
497+
@property
498+
def is_lit(self) -> bool:
499+
"""Check if the light is currently lit."""
500+
return any(self.color)

src/busylight_core/mixins/colorable.py

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,32 @@
22

33

44
class ColorableMixin:
5-
"""Mixin providing color manipulation properties for Light classes."""
6-
7-
@property
8-
def red(self) -> int:
9-
"""Red color value."""
10-
return getattr(self, "_red", 0)
11-
12-
@red.setter
13-
def red(self, value: int) -> None:
14-
self._red = value
15-
16-
@property
17-
def green(self) -> int:
18-
"""Green color value."""
19-
return getattr(self, "_green", 0)
20-
21-
@green.setter
22-
def green(self, value: int) -> None:
23-
self._green = value
24-
25-
@property
26-
def blue(self) -> int:
27-
"""Blue color value."""
28-
return getattr(self, "_blue", 0)
29-
30-
@blue.setter
31-
def blue(self, value: int) -> None:
32-
self._blue = value
5+
"""Mixin providing color properties."""
6+
7+
red = property(
8+
lambda self: getattr(self, "_red", 0),
9+
lambda self, value: setattr(self, "_red", value),
10+
doc="Red color value.",
11+
)
12+
13+
green = property(
14+
lambda self: getattr(self, "_green", 0),
15+
lambda self, value: setattr(self, "_green", value),
16+
doc="Green color value.",
17+
)
18+
19+
blue = property(
20+
lambda self: getattr(self, "_blue", 0),
21+
lambda self, value: setattr(self, "_blue", value),
22+
doc="Blue color value.",
23+
)
3324

3425
@property
3526
def color(self) -> tuple[int, int, int]:
36-
"""A tuple of red, green, and blue color values."""
37-
return self.red, self.green, self.blue
27+
"""Tuple of RGB color values."""
28+
return (self.red, self.green, self.blue)
3829

3930
@color.setter
4031
def color(self, value: tuple[int, int, int]) -> None:
32+
"""Tuple of RGB color values."""
4133
self.red, self.green, self.blue = value
42-
43-
@property
44-
def is_lit(self) -> bool:
45-
"""True if any color value is greater than 0."""
46-
return any(self.color)

src/busylight_core/vendors/compulab/fit_statusb.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
from typing import ClassVar
44

55
from busylight_core.light import Light
6+
from busylight_core.mixins import ColorableMixin
67

78

8-
class Fit_StatUSB(Light):
9+
class Fit_StatUSB(ColorableMixin, Light):
910
"""CompuLab fit-statUSB status light controller.
1011
1112
The fit-statUSB is a USB-connected RGB LED device that communicates

src/busylight_core/vendors/epos/_busylight.py

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,9 @@ def __init__(self) -> None:
4646

4747
on = OnField(0, 8)
4848

49-
def set_color(
50-
self,
51-
color: tuple[int, int, int],
52-
led: int = 0,
53-
) -> None:
54-
"""Set color for the specified LED."""
55-
self.report = Report.ONE
56-
self.action = Action.SetColor
57-
match led:
58-
case 0:
59-
self.color = color
60-
case 1:
61-
self.color0 = color
62-
case 2:
63-
self.color1 = color
64-
6549
@property
6650
def color0(self) -> tuple[int, int, int]:
67-
"""Return the first color as a tuple of RGB values."""
51+
"""Return the first LED color as a tuple of RGB values."""
6852
return (self.red0, self.green0, self.blue0)
6953

7054
@color0.setter
@@ -73,7 +57,7 @@ def color0(self, color: tuple[int, int, int]) -> None:
7357

7458
@property
7559
def color1(self) -> tuple[int, int, int]:
76-
"""Return the second color as a tuple of RGB values."""
60+
"""Return the second LED color as a tuple of RGB values."""
7761
return (self.red1, self.green1, self.blue1)
7862

7963
@color1.setter
@@ -82,21 +66,13 @@ def color1(self, color: tuple[int, int, int]) -> None:
8266

8367
@property
8468
def color(self) -> tuple[int, int, int]:
85-
return self.color0
69+
"""The first non-black LED color as a tuple of RGB values."""
70+
for color in (self.color0, self.color1):
71+
if any(color):
72+
return color
73+
return (0, 0, 0)
8674

8775
@color.setter
8876
def color(self, color: tuple[int, int, int]) -> None:
8977
self.color0 = color
9078
self.color1 = color
91-
92-
def reset(self) -> None:
93-
"""Reset state to default value."""
94-
self.report = 0
95-
self.action = 0
96-
self.red0 = 0
97-
self.green0 = 0
98-
self.blue0 = 0
99-
self.red1 = 0
100-
self.green1 = 0
101-
self.blue1 = 0
102-
self.on = 0

src/busylight_core/vendors/epos/busylight.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from busylight_core.light import Light
77

8-
from ._busylight import State
8+
from ._busylight import Action, Report, State
99

1010

1111
class Busylight(Light):
@@ -21,12 +21,12 @@ class Busylight(Light):
2121

2222
@staticmethod
2323
def vendor() -> str:
24-
"""Return the vendor name for this device."""
24+
"""The vendor name for this device."""
2525
return "EPOS"
2626

2727
@cached_property
2828
def state(self) -> State:
29-
"""Get the device state manager for controlling LED patterns."""
29+
"""The device state manager for controlling LED patterns."""
3030
return State()
3131

3232
def __bytes__(self) -> bytes:
@@ -35,16 +35,31 @@ def __bytes__(self) -> bytes:
3535
def on(self, color: tuple[int, int, int], led: int = 0) -> None:
3636
"""Turn on the EPOS Busylight with the specified color.
3737
38-
Args:
39-
color: RGB color tuple (red, green, blue) with values 0-255
40-
led: LED index for targeting specific LEDs
41-
38+
:param color: RGB color tuple (red, green, blue) with values 0-255
39+
:param led: LED index (0 for both LEDs, 1 for first LED, 2 for second LED)
4240
"""
43-
self.color = color
4441
with self.batch_update():
45-
self.state.set_color(color, led)
42+
match led:
43+
case 1:
44+
self.state.color0 = color
45+
case 2:
46+
self.state.color1 = color
47+
case _:
48+
self.state.color = color
49+
50+
self.state.report = Report.ONE
51+
self.state.action = Action.SetColor
52+
53+
@property
54+
def color(self) -> tuple[int, int, int]:
55+
"""The device color as a tuple of RGB values."""
56+
return self.state.color
57+
58+
@color.setter
59+
def color(self, color: tuple[int, int, int]) -> None:
60+
self.state.color = color
4661

4762
def reset(self) -> None:
4863
"""Reset the device to its default state."""
49-
self.state.reset()
64+
self.state.clear()
5065
super().reset()

src/busylight_core/vendors/kuando/busylight_alpha.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
from typing import ClassVar
66

77
from busylight_core.light import Light
8+
from busylight_core.mixins import ColorableMixin
89

910
from ._busylight import State
1011

1112

12-
class Busylight_Alpha(Light):
13+
class Busylight_Alpha(ColorableMixin, Light):
1314
"""Kuando Busylight Alpha status light controller.
1415
1516
The Busylight Alpha is a USB-connected RGB LED device that requires
@@ -34,10 +35,8 @@ def __bytes__(self) -> bytes:
3435
def on(self, color: tuple[int, int, int], led: int = 0) -> None:
3536
"""Turn on the Busylight Alpha with the specified color.
3637
37-
Args:
38-
color: RGB color tuple (red, green, blue) with values 0-255
39-
led: LED index (unused for Busylight Alpha)
40-
38+
:param color: RGB color tuple (red, green, blue) with values 0-255
39+
:param led: LED index (unused for Busylight Alpha)
4140
"""
4241
self.color = color
4342
with self.batch_update():
@@ -47,9 +46,7 @@ def on(self, color: tuple[int, int, int], led: int = 0) -> None:
4746
def off(self, led: int = 0) -> None:
4847
"""Turn off the Busylight Alpha.
4948
50-
Args:
51-
led: LED index (unused for Busylight Alpha)
52-
49+
:param led: LED index (unused for Busylight Alpha)
5350
"""
5451
self.color = (0, 0, 0)
5552
with self.batch_update():

src/busylight_core/vendors/luxafor/busytag.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
from typing import ClassVar
44

55
from busylight_core.light import Light
6+
from busylight_core.mixins import ColorableMixin
67

78
from ._busytag import Command
89

910

10-
class BusyTag(Light):
11+
class BusyTag(ColorableMixin, Light):
1112
"""BusyTag status light controller.
1213
1314
The BusyTag is a wireless status light that uses command strings
@@ -38,10 +39,8 @@ def __bytes__(self) -> bytes:
3839
def on(self, color: tuple[int, int, int], led: int = 0) -> None:
3940
"""Turn on the BusyTag with the specified color.
4041
41-
Args:
42-
color: RGB color tuple (red, green, blue) with values 0-255
43-
led: LED index for specific LED targeting
44-
42+
:param color: RGB color tuple (red, green, blue)
43+
:param led: LED index (default is 0, for the first LED)
4544
"""
4645
with self.batch_update():
4746
self.color = color

src/busylight_core/vendors/luxafor/flag.py

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,9 @@ class Flag(Light):
2424

2525
@classmethod
2626
def claims(cls, hardware: Hardware) -> bool:
27-
"""Check if this class can handle the given hardware device.
28-
29-
Args:
30-
hardware: Hardware device to check
31-
32-
Returns:
33-
True if this class can handle the hardware
27+
"""Check if this class claims the given hardware device.
3428
29+
:param hardware: A hardware instance
3530
"""
3631
if not super().claims(hardware):
3732
return False
@@ -48,24 +43,29 @@ def claims(cls, hardware: Hardware) -> bool:
4843

4944
@cached_property
5045
def state(self) -> State:
51-
"""Get the device state manager for controlling LED patterns."""
46+
"""The device state manager for controlling a Luxfor device."""
5247
return State()
5348

5449
def __bytes__(self) -> bytes:
55-
self.state.color = self.color
5650
return bytes(self.state)
5751

5852
def on(self, color: tuple[int, int, int], led: int = 0) -> None:
59-
"""Turn on the Luxafor Flag with the specified color.
60-
61-
Args:
62-
color: RGB color tuple (red, green, blue) with values 0-255
63-
led: LED index (0 for all LEDs, or specific LED number)
53+
"""Turn on a Luxafor device with the specified color tuple.
6454
55+
:param color: RGB color tuple (red, green, blue)
6556
"""
6657
with self.batch_update():
58+
self.color = color
6759
try:
6860
self.state.leds = LEDS(led)
6961
except ValueError:
7062
self.state.leds = LEDS.All
71-
self.color = color
63+
64+
@property
65+
def color(self) -> tuple[int, int, int]:
66+
"""The current RGB color of a Luxafor device."""
67+
return self.state.color
68+
69+
@color.setter
70+
def color(self, value: tuple[int, int, int]) -> None:
71+
self.state.color = value

src/busylight_core/vendors/luxafor/mute.py

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,12 @@ class Mute(Flag):
1818

1919
@property
2020
def is_button(self) -> bool:
21-
"""Check if this device has button functionality.
22-
23-
Returns:
24-
True, as the Mute device has a button
25-
26-
"""
21+
"""Return True if this device has button functionality."""
2722
return True
2823

2924
@property
3025
def button_on(self) -> bool:
31-
"""Check if the mute button is currently pressed.
32-
33-
Returns:
34-
True if the button is pressed, False otherwise
35-
36-
"""
26+
"""Return True if the mute button is currently pressed."""
3727
results = self.read_strategy(8, 200)
3828

3929
try:

0 commit comments

Comments
 (0)