2828 MultiColorEffects ,
2929)
3030from .timer import LedTimer
31- from .utils import utils , white_levels_to_scaled_color_temp
31+ from .utils import (
32+ scaled_color_temp_to_white_levels ,
33+ utils ,
34+ white_levels_to_scaled_color_temp ,
35+ )
3236
3337
3438class RemoteConfig (Enum ):
@@ -502,6 +506,13 @@ def construct_state_query(self) -> bytearray:
502506 def is_valid_state_response (self , raw_state : bytes ) -> bool :
503507 """Check if a state response is valid."""
504508
509+ def is_valid_extended_state_response (self , raw_state : bytes ) -> bool :
510+ return False
511+
512+ @abstractmethod
513+ def extended_state_to_state (self , raw_state : bytes ) -> bytes :
514+ """Convert an extended state response to a state response."""
515+
505516 def is_checksum_correct (self , msg : bytes ) -> bool :
506517 """Check a checksum of a message."""
507518 expected_sum = sum (msg [0 :- 1 ]) & 0xFF
@@ -1423,14 +1434,97 @@ def name(self) -> str:
14231434 """The name of the protocol."""
14241435 return PROTOCOL_LEDENET_25BYTE
14251436
1437+ def is_valid_extended_state_response (self , raw_state : bytes ) -> bool :
1438+ """Check if a state response is valid."""
1439+ return raw_state [0 ] == 0xEA and raw_state [1 ] == 0x81 and len (raw_state ) >= 20
1440+
1441+ def extended_state_to_state (self , raw_state : bytes ) -> bytes :
1442+ """Convert an extended state response to a state response."""
1443+ # pos 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
1444+ # EA 81 01 10 35 0A 23 61 01 50 0F 3C 64 64 00 64 00 00 00 00
1445+ # | | | | | | | | | | | | | | | | | | | |
1446+ # | | | | | | | | | | | | | | | | | | | ??
1447+ # | | | | | | | | | | | | | | | | | | ??
1448+ # | | | | | | | | | | | | | | | | | ??
1449+ # | | | | | | | | | | | | | | | | ??
1450+ # | | | | | | | | | | | | | | | White brightness
1451+ # | | | | | | | | | | | | | | White temperature
1452+ # | | | | | | | | | | | | | Value
1453+ # | | | | | | | | | | | | Saturation
1454+ # | | | | | | | | | | | Hue / 2 (0-180)
1455+ # | | | | | | | | | | 0f white / f0 rgb
1456+ # | | | | | | | | | Speed?
1457+ # | | | | | | | | w 01 / rgb 00
1458+ # | | | | | | | ??
1459+ # | | | | | | Power state (0x23 = ON, 0x24 = OFF)
1460+ # | | | | | ??
1461+ # | | | | Version number
1462+ # | | | Model number
1463+ # | | Unknown / reserved
1464+ # | Unknown / reserved
1465+ # Extended message header (ea 81)
1466+
1467+ if len (raw_state ) < 20 :
1468+ return b""
1469+
1470+ model_num = raw_state [4 ]
1471+ version_number = raw_state [5 ]
1472+ power_state = raw_state [6 ]
1473+ preset_pattern = raw_state [7 ]
1474+ speed = raw_state [9 ]
1475+
1476+ hue = raw_state [11 ]
1477+ saturation = raw_state [12 ]
1478+ value = raw_state [13 ]
1479+
1480+ white_temp = raw_state [14 ]
1481+ white_brightness = raw_state [15 ]
1482+ levels = scaled_color_temp_to_white_levels (white_temp , white_brightness )
1483+
1484+ cool_white = levels .cool_white
1485+ warm_white = levels .warm_white
1486+
1487+ # Convert HSV to RGB
1488+ h = (hue * 2 ) / 360
1489+ s = saturation / 100
1490+ v = value / 100
1491+ r_f , g_f , b_f = colorsys .hsv_to_rgb (h , s , v )
1492+ red = min (int (max (0 , r_f ) * 255 ), 255 )
1493+ green = min (int (max (0 , g_f ) * 255 ), 255 )
1494+ blue = min (int (max (0 , b_f ) * 255 ), 255 )
1495+
1496+ # Fill standard state structure
1497+ mode = 0
1498+ color_mode = 0
1499+ check_sum = 0 # Set to 0; not critical
1500+
1501+ return bytes (
1502+ (
1503+ raw_state [1 ], # Head (second byte of EA 81)
1504+ model_num ,
1505+ power_state ,
1506+ preset_pattern ,
1507+ mode ,
1508+ speed ,
1509+ red ,
1510+ green ,
1511+ blue ,
1512+ warm_white ,
1513+ version_number ,
1514+ cool_white ,
1515+ color_mode ,
1516+ check_sum ,
1517+ )
1518+ )
1519+
14261520 def construct_levels_change (
14271521 self ,
14281522 persist : int ,
1429- red : int | None ,
1430- green : int | None ,
1431- blue : int | None ,
1432- warm_white : int | None ,
1433- cool_white : int | None ,
1523+ red : int | None , # 0-255
1524+ green : int | None , # 0-255
1525+ blue : int | None , # 0-255
1526+ warm_white : int | None , # 0-255
1527+ cool_white : int | None , # 0-255
14341528 write_mode : LevelWriteMode | int ,
14351529 ) -> list [bytearray ]:
14361530 """The bytes to send for a level change request."""
@@ -1457,16 +1551,19 @@ def construct_levels_change(
14571551 else :
14581552 h = s = v = 0x00
14591553
1460- if warm_white is not None and cool_white is not None :
1461- # warm white comes through as 0, even when temp is set to 6500
1462- white_brightness = round (((warm_white + cool_white ) / (2 * 255 )) * 64 )
1463- white_temp = round ((cool_white / (warm_white + cool_white )) * 64 )
1464- elif cool_white is not None and warm_white is None :
1465- white_temp = 0x64
1466- white_brightness = int ((cool_white / 255 ) * 100 )
1554+ if (
1555+ cool_white is None
1556+ or warm_white is None
1557+ or (cool_white == 0 and warm_white == 0 )
1558+ ):
1559+ white_temp = white_brightness = 0
14671560 else :
1468- white_temp = 0x00
1469- white_brightness = 0x00
1561+ total = warm_white + cool_white
1562+ # temperature: ratio of cool to total, scaled to 0-100
1563+ white_temp = round ((cool_white / float (total )) * 100 )
1564+ # brightness: clamp sum at 255, then scale to 0-100
1565+ clamped_sum = min (total , 255 )
1566+ white_brightness = round ((clamped_sum / 255.0 ) * 100 )
14701567
14711568 return [
14721569 self .construct_wrapped_message (
0 commit comments