28
28
MultiColorEffects ,
29
29
)
30
30
from .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
+ )
32
36
33
37
34
38
class RemoteConfig (Enum ):
@@ -502,6 +506,13 @@ def construct_state_query(self) -> bytearray:
502
506
def is_valid_state_response (self , raw_state : bytes ) -> bool :
503
507
"""Check if a state response is valid."""
504
508
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
+
505
516
def is_checksum_correct (self , msg : bytes ) -> bool :
506
517
"""Check a checksum of a message."""
507
518
expected_sum = sum (msg [0 :- 1 ]) & 0xFF
@@ -1423,14 +1434,97 @@ def name(self) -> str:
1423
1434
"""The name of the protocol."""
1424
1435
return PROTOCOL_LEDENET_25BYTE
1425
1436
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
+
1426
1520
def construct_levels_change (
1427
1521
self ,
1428
1522
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
1434
1528
write_mode : LevelWriteMode | int ,
1435
1529
) -> list [bytearray ]:
1436
1530
"""The bytes to send for a level change request."""
@@ -1457,16 +1551,19 @@ def construct_levels_change(
1457
1551
else :
1458
1552
h = s = v = 0x00
1459
1553
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
1467
1560
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 )
1470
1567
1471
1568
return [
1472
1569
self .construct_wrapped_message (
0 commit comments