lifx-async 4.3.4__py3-none-any.whl → 4.3.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lifx/devices/matrix.py +26 -19
- lifx/network/connection.py +35 -34
- {lifx_async-4.3.4.dist-info → lifx_async-4.3.6.dist-info}/METADATA +1 -1
- {lifx_async-4.3.4.dist-info → lifx_async-4.3.6.dist-info}/RECORD +6 -6
- {lifx_async-4.3.4.dist-info → lifx_async-4.3.6.dist-info}/WHEEL +0 -0
- {lifx_async-4.3.4.dist-info → lifx_async-4.3.6.dist-info}/licenses/LICENSE +0 -0
lifx/devices/matrix.py
CHANGED
|
@@ -171,11 +171,6 @@ class MatrixEffect:
|
|
|
171
171
|
|
|
172
172
|
def __post_init__(self) -> None:
|
|
173
173
|
"""Initialize defaults and validate fields."""
|
|
174
|
-
# Initialize default palette if not provided
|
|
175
|
-
if self.palette is None:
|
|
176
|
-
# Default palette: single white color
|
|
177
|
-
self.palette = [HSBK(0, 0, 1.0, 3500)]
|
|
178
|
-
|
|
179
174
|
# Validate all fields
|
|
180
175
|
# Speed can be 0 only when effect is OFF
|
|
181
176
|
if self.effect_type != FirmwareEffect.OFF:
|
|
@@ -184,7 +179,11 @@ class MatrixEffect:
|
|
|
184
179
|
raise ValueError(f"Effect speed must be non-negative, got {self.speed}")
|
|
185
180
|
|
|
186
181
|
self._validate_duration(self.duration)
|
|
187
|
-
|
|
182
|
+
|
|
183
|
+
# Only validate palette if provided
|
|
184
|
+
if self.palette is not None:
|
|
185
|
+
self._validate_palette(self.palette)
|
|
186
|
+
|
|
188
187
|
self._validate_saturation(self.cloud_saturation_min, "cloud_saturation_min")
|
|
189
188
|
self._validate_saturation(self.cloud_saturation_max, "cloud_saturation_max")
|
|
190
189
|
|
|
@@ -762,7 +761,7 @@ class MatrixLight(Light):
|
|
|
762
761
|
effect_type: Type of effect (OFF, MORPH, FLAME, SKY)
|
|
763
762
|
speed: Effect speed in seconds (default: 3)
|
|
764
763
|
duration: Total effect duration in nanoseconds (0 for infinite)
|
|
765
|
-
palette: Color palette for the effect (max 16 colors)
|
|
764
|
+
palette: Color palette for the effect (max 16 colors, None for no palette)
|
|
766
765
|
sky_type: Sky effect type (SUNRISE, SUNSET, CLOUDS)
|
|
767
766
|
cloud_saturation_min: Minimum cloud saturation (0-255, for CLOUDS)
|
|
768
767
|
cloud_saturation_max: Maximum cloud saturation (0-255, for CLOUDS)
|
|
@@ -780,6 +779,12 @@ class MatrixLight(Light):
|
|
|
780
779
|
... speed=5.0,
|
|
781
780
|
... palette=rainbow,
|
|
782
781
|
... )
|
|
782
|
+
|
|
783
|
+
>>> # Set effect without a palette
|
|
784
|
+
>>> await matrix.set_effect(
|
|
785
|
+
... effect_type=FirmwareEffect.FLAME,
|
|
786
|
+
... speed=3.0,
|
|
787
|
+
... )
|
|
783
788
|
"""
|
|
784
789
|
_LOGGER.debug(
|
|
785
790
|
"Setting matrix effect %s (speed=%d) for %s",
|
|
@@ -801,20 +806,22 @@ class MatrixLight(Light):
|
|
|
801
806
|
)
|
|
802
807
|
|
|
803
808
|
# Convert to protocol format
|
|
804
|
-
# Note: palette is guaranteed to be non-None by MatrixEffect.__post_init__
|
|
805
|
-
palette = effect.palette if effect.palette is not None else []
|
|
806
809
|
proto_palette = []
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
810
|
+
palette_count = 0
|
|
811
|
+
|
|
812
|
+
if effect.palette is not None:
|
|
813
|
+
palette_count = len(effect.palette)
|
|
814
|
+
for color in effect.palette:
|
|
815
|
+
proto_palette.append(
|
|
816
|
+
LightHsbk(
|
|
817
|
+
hue=int(color.hue / 360 * 65535),
|
|
818
|
+
saturation=int(color.saturation * 65535),
|
|
819
|
+
brightness=int(color.brightness * 65535),
|
|
820
|
+
kelvin=color.kelvin,
|
|
821
|
+
)
|
|
814
822
|
)
|
|
815
|
-
)
|
|
816
823
|
|
|
817
|
-
# Pad palette to 16 colors
|
|
824
|
+
# Pad palette to 16 colors (protocol requirement)
|
|
818
825
|
while len(proto_palette) < 16:
|
|
819
826
|
proto_palette.append(LightHsbk(0, 0, 0, 3500))
|
|
820
827
|
|
|
@@ -828,7 +835,7 @@ class MatrixLight(Light):
|
|
|
828
835
|
cloud_saturation_min=effect.cloud_saturation_min,
|
|
829
836
|
cloud_saturation_max=effect.cloud_saturation_max,
|
|
830
837
|
),
|
|
831
|
-
palette_count=
|
|
838
|
+
palette_count=palette_count,
|
|
832
839
|
palette=proto_palette,
|
|
833
840
|
)
|
|
834
841
|
|
lifx/network/connection.py
CHANGED
|
@@ -22,7 +22,6 @@ from lifx.exceptions import (
|
|
|
22
22
|
LifxConnectionError,
|
|
23
23
|
LifxProtocolError,
|
|
24
24
|
LifxTimeoutError,
|
|
25
|
-
LifxUnsupportedCommandError,
|
|
26
25
|
)
|
|
27
26
|
from lifx.network.message import create_message, parse_message
|
|
28
27
|
from lifx.network.transport import UdpTransport
|
|
@@ -443,13 +442,12 @@ class DeviceConnection:
|
|
|
443
442
|
LifxConnectionError: If connection is not open
|
|
444
443
|
LifxProtocolError: If response correlation validation fails
|
|
445
444
|
LifxTimeoutError: If no response after all retries
|
|
446
|
-
LifxUnsupportedCommandError: If device doesn't support command
|
|
447
445
|
"""
|
|
448
446
|
if not self._is_open or self._transport is None:
|
|
449
|
-
raise LifxConnectionError("Connection not open")
|
|
447
|
+
raise LifxConnectionError("Connection not open") # pragma: no cover
|
|
450
448
|
|
|
451
449
|
if timeout is None:
|
|
452
|
-
timeout = self.timeout
|
|
450
|
+
timeout = self.timeout # pragma: no cover
|
|
453
451
|
|
|
454
452
|
if max_retries is None:
|
|
455
453
|
max_retries = self.max_retries
|
|
@@ -597,12 +595,6 @@ class DeviceConnection:
|
|
|
597
595
|
f"got {header.sequence}, max expected {max_expected}"
|
|
598
596
|
)
|
|
599
597
|
|
|
600
|
-
# Check for StateUnhandled
|
|
601
|
-
if header.pkt_type == _STATE_UNHANDLED_PKT_TYPE:
|
|
602
|
-
raise LifxUnsupportedCommandError(
|
|
603
|
-
"Request unsupported by device: received StateUnhandled"
|
|
604
|
-
)
|
|
605
|
-
|
|
606
598
|
# Yield response (can be from any retry attempt)
|
|
607
599
|
has_yielded = True
|
|
608
600
|
last_response_time = time.monotonic()
|
|
@@ -640,7 +632,7 @@ class DeviceConnection:
|
|
|
640
632
|
request: Any,
|
|
641
633
|
timeout: float | None = None,
|
|
642
634
|
max_retries: int | None = None,
|
|
643
|
-
) -> AsyncGenerator[
|
|
635
|
+
) -> AsyncGenerator[bool, None]:
|
|
644
636
|
"""Internal implementation of request_ack_stream with retry logic.
|
|
645
637
|
|
|
646
638
|
This is an async generator that sends a request requiring acknowledgement
|
|
@@ -652,18 +644,17 @@ class DeviceConnection:
|
|
|
652
644
|
max_retries: Maximum retries
|
|
653
645
|
|
|
654
646
|
Yields:
|
|
655
|
-
|
|
647
|
+
True for successful ACK, False if device returned StateUnhandled
|
|
656
648
|
|
|
657
649
|
Raises:
|
|
658
650
|
LifxConnectionError: If connection is not open
|
|
659
651
|
LifxTimeoutError: If no ack after all retries
|
|
660
|
-
LifxUnsupportedCommandError: If device doesn't support command
|
|
661
652
|
"""
|
|
662
653
|
if not self._is_open or self._transport is None:
|
|
663
|
-
raise LifxConnectionError("Connection not open")
|
|
654
|
+
raise LifxConnectionError("Connection not open") # pragma: no cover
|
|
664
655
|
|
|
665
656
|
if timeout is None:
|
|
666
|
-
timeout = self.timeout
|
|
657
|
+
timeout = self.timeout # pragma: no cover
|
|
667
658
|
|
|
668
659
|
if max_retries is None:
|
|
669
660
|
max_retries = self.max_retries
|
|
@@ -729,14 +720,13 @@ class DeviceConnection:
|
|
|
729
720
|
f"{Serial.from_protocol(header.target).to_string()})"
|
|
730
721
|
)
|
|
731
722
|
|
|
732
|
-
# Check for StateUnhandled
|
|
723
|
+
# Check for StateUnhandled - return False to indicate unsupported
|
|
733
724
|
if header.pkt_type == _STATE_UNHANDLED_PKT_TYPE:
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
)
|
|
725
|
+
yield False
|
|
726
|
+
return
|
|
737
727
|
|
|
738
728
|
# ACK received successfully
|
|
739
|
-
yield
|
|
729
|
+
yield True
|
|
740
730
|
return
|
|
741
731
|
|
|
742
732
|
except TimeoutError as e:
|
|
@@ -791,14 +781,14 @@ class DeviceConnection:
|
|
|
791
781
|
timeout: Request timeout in seconds
|
|
792
782
|
|
|
793
783
|
Yields:
|
|
794
|
-
Unpacked response packet instances
|
|
795
|
-
|
|
784
|
+
Unpacked response packet instances (including StateUnhandled if device
|
|
785
|
+
doesn't support the command)
|
|
786
|
+
For SET packets: yields True (acknowledgement) or False (StateUnhandled)
|
|
796
787
|
|
|
797
788
|
Raises:
|
|
798
789
|
LifxTimeoutError: If request times out
|
|
799
790
|
LifxProtocolError: If response invalid
|
|
800
791
|
LifxConnectionError: If connection fails
|
|
801
|
-
LifxUnsupportedCommandError: If command not supported
|
|
802
792
|
|
|
803
793
|
Example:
|
|
804
794
|
```python
|
|
@@ -808,11 +798,16 @@ class DeviceConnection:
|
|
|
808
798
|
label = state.label # Already decoded to string
|
|
809
799
|
break
|
|
810
800
|
|
|
811
|
-
# SET request yields True (acknowledgement)
|
|
812
|
-
async for
|
|
801
|
+
# SET request yields True (acknowledgement) or False (StateUnhandled)
|
|
802
|
+
async for result in conn.request_stream(
|
|
813
803
|
packets.Light.SetColor(color=hsbk, duration=1000)
|
|
814
804
|
):
|
|
815
|
-
|
|
805
|
+
if result:
|
|
806
|
+
# Acknowledgement received
|
|
807
|
+
pass
|
|
808
|
+
else:
|
|
809
|
+
# Device doesn't support this command
|
|
810
|
+
pass
|
|
816
811
|
break
|
|
817
812
|
|
|
818
813
|
# Multi-response GET - stream all responses
|
|
@@ -875,9 +870,12 @@ class DeviceConnection:
|
|
|
875
870
|
|
|
876
871
|
elif packet_kind == "SET":
|
|
877
872
|
# Request acknowledgement
|
|
878
|
-
async for
|
|
873
|
+
async for ack_result in self._request_ack_stream_impl(
|
|
874
|
+
packet, timeout=timeout
|
|
875
|
+
):
|
|
879
876
|
# Log the request/ack cycle
|
|
880
877
|
request_values = packet.as_dict
|
|
878
|
+
reply_packet = "Acknowledgement" if ack_result else "StateUnhandled"
|
|
881
879
|
_LOGGER.debug(
|
|
882
880
|
{
|
|
883
881
|
"class": "DeviceConnection",
|
|
@@ -887,7 +885,7 @@ class DeviceConnection:
|
|
|
887
885
|
"values": request_values,
|
|
888
886
|
},
|
|
889
887
|
"reply": {
|
|
890
|
-
"packet":
|
|
888
|
+
"packet": reply_packet,
|
|
891
889
|
"values": {},
|
|
892
890
|
},
|
|
893
891
|
"serial": self.serial,
|
|
@@ -895,7 +893,7 @@ class DeviceConnection:
|
|
|
895
893
|
}
|
|
896
894
|
)
|
|
897
895
|
|
|
898
|
-
yield
|
|
896
|
+
yield ack_result
|
|
899
897
|
return
|
|
900
898
|
|
|
901
899
|
else:
|
|
@@ -934,7 +932,7 @@ class DeviceConnection:
|
|
|
934
932
|
yield response_packet
|
|
935
933
|
return
|
|
936
934
|
else:
|
|
937
|
-
raise
|
|
935
|
+
raise LifxProtocolError(
|
|
938
936
|
f"Cannot auto-handle packet kind: {packet_kind}"
|
|
939
937
|
)
|
|
940
938
|
else:
|
|
@@ -961,14 +959,14 @@ class DeviceConnection:
|
|
|
961
959
|
timeout: Request timeout in seconds
|
|
962
960
|
|
|
963
961
|
Returns:
|
|
964
|
-
Single unpacked response packet
|
|
965
|
-
|
|
962
|
+
Single unpacked response packet (including StateUnhandled if device
|
|
963
|
+
doesn't support the command)
|
|
964
|
+
For SET packets: True (acknowledgement) or False (StateUnhandled)
|
|
966
965
|
|
|
967
966
|
Raises:
|
|
968
967
|
LifxTimeoutError: If no response within timeout
|
|
969
968
|
LifxProtocolError: If response invalid
|
|
970
969
|
LifxConnectionError: If connection fails
|
|
971
|
-
LifxUnsupportedCommandError: If command not supported
|
|
972
970
|
|
|
973
971
|
Example:
|
|
974
972
|
```python
|
|
@@ -977,10 +975,13 @@ class DeviceConnection:
|
|
|
977
975
|
color = HSBK.from_protocol(state.color)
|
|
978
976
|
label = state.label # Already decoded to string
|
|
979
977
|
|
|
980
|
-
# SET request returns True
|
|
978
|
+
# SET request returns True or False
|
|
981
979
|
success = await conn.request(
|
|
982
980
|
packets.Light.SetColor(color=hsbk, duration=1000)
|
|
983
981
|
)
|
|
982
|
+
if not success:
|
|
983
|
+
# Device doesn't support this command (returned StateUnhandled)
|
|
984
|
+
pass
|
|
984
985
|
```
|
|
985
986
|
"""
|
|
986
987
|
async for response in self.request_stream(packet, timeout):
|
|
@@ -9,7 +9,7 @@ lifx/devices/base.py,sha256=uD3hQe2kjycRZneSptON6psOhoEgPRHVelCoLgdHbFw,41482
|
|
|
9
9
|
lifx/devices/hev.py,sha256=2zZNYm3TFrL755B4cRPNdYtcDLZEQwGl_22112WsSZc,9504
|
|
10
10
|
lifx/devices/infrared.py,sha256=TrCgJyEioIPlFumMmcSmuGYmRsSGlQ5Rllg6_9Wtg4Y,4248
|
|
11
11
|
lifx/devices/light.py,sha256=9rL24fa44Y7QrRBDSQuG6xpWsaPbQTTm4ExvrDnYWHo,27572
|
|
12
|
-
lifx/devices/matrix.py,sha256=
|
|
12
|
+
lifx/devices/matrix.py,sha256=mleYYsXkvvfXxV_pScb_D7VoX0AEJkClpzpNXOzQyGs,32643
|
|
13
13
|
lifx/devices/multizone.py,sha256=c-lXcp8c1Mhs8me6smGkqQFrOOxdoGjWrOO5HnAVooY,27209
|
|
14
14
|
lifx/effects/__init__.py,sha256=4DF31yp7RJic5JoltMlz5dCtF5KQobU6NOUtLUKkVKE,1509
|
|
15
15
|
lifx/effects/base.py,sha256=YO0Hbg2VYHKPtfYnWxmrtzYoPGOi9BUXhn8HVFKv5IM,10283
|
|
@@ -20,7 +20,7 @@ lifx/effects/models.py,sha256=MS5D-cxD0Ar8XhqbqKAc9q2sk38IP1vPkYwd8V7jCr8,2446
|
|
|
20
20
|
lifx/effects/pulse.py,sha256=t5eyjfFWG1xT-RXKghRqHYJ9CG_50tPu4jsDapJZ2mw,8721
|
|
21
21
|
lifx/effects/state_manager.py,sha256=iDfYowiCN5IJqcR1s-pM0mQEJpe-RDsMcOOSMmtPVDE,8983
|
|
22
22
|
lifx/network/__init__.py,sha256=uSyA8r8qISG7qXUHbX8uk9A2E8rvDADgCcf94QIZ9so,499
|
|
23
|
-
lifx/network/connection.py,sha256=
|
|
23
|
+
lifx/network/connection.py,sha256=hM7BxpG4udLCMWV18trbgbV_yjPsX5e_V4boCf8eZYs,38278
|
|
24
24
|
lifx/network/discovery.py,sha256=FoFoZcw3dtJs1daESiZiNXytanKQsMTdF9PjOxEgHM0,23804
|
|
25
25
|
lifx/network/message.py,sha256=jCLC9v0tbBi54g5CaHLFM_nP1Izu8kJmo2tt23HHBbA,2600
|
|
26
26
|
lifx/network/transport.py,sha256=8QS0YV32rdP0EDiPEwuvZXbplRWL08pmjKybd87mkZ0,11070
|
|
@@ -40,7 +40,7 @@ lifx/theme/canvas.py,sha256=4h7lgN8iu_OdchObGDgbxTqQLCb-FRKC-M-YCWef_i4,8048
|
|
|
40
40
|
lifx/theme/generators.py,sha256=L0X6_iApLx6XDboGlYunaVsl6nvUCqMfn23VQmRkyCk,6125
|
|
41
41
|
lifx/theme/library.py,sha256=tKlKZNqJp8lRGDnilWyDm_Qr1vCRGGwuvWVS82anNpQ,21326
|
|
42
42
|
lifx/theme/theme.py,sha256=qMEx_8E41C0Cc6f083XHiAXEglTv4YlXW0UFsG1rQKg,5521
|
|
43
|
-
lifx_async-4.3.
|
|
44
|
-
lifx_async-4.3.
|
|
45
|
-
lifx_async-4.3.
|
|
46
|
-
lifx_async-4.3.
|
|
43
|
+
lifx_async-4.3.6.dist-info/METADATA,sha256=ieTQiM2W8wpfpxTTpl2GwBCdV_9BK36iaK695yKnYac,2609
|
|
44
|
+
lifx_async-4.3.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
45
|
+
lifx_async-4.3.6.dist-info/licenses/LICENSE,sha256=eBz48GRA3gSiWn3rYZAz2Ewp35snnhV9cSqkVBq7g3k,1832
|
|
46
|
+
lifx_async-4.3.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|