lifx-async 4.3.6__py3-none-any.whl → 4.3.8__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/base.py CHANGED
@@ -19,7 +19,7 @@ from lifx.const import (
19
19
  LIFX_LOCATION_NAMESPACE,
20
20
  LIFX_UDP_PORT,
21
21
  )
22
- from lifx.exceptions import LifxDeviceNotFoundError
22
+ from lifx.exceptions import LifxDeviceNotFoundError, LifxUnsupportedCommandError
23
23
  from lifx.network.connection import DeviceConnection
24
24
  from lifx.products.registry import ProductInfo, get_product
25
25
  from lifx.protocol import packets
@@ -152,6 +152,21 @@ class Device:
152
152
  ```
153
153
  """
154
154
 
155
+ @staticmethod
156
+ def _raise_if_unhandled(response: object) -> None:
157
+ """Raise LifxUnsupportedCommandError if device doesn't support the command.
158
+
159
+ Args:
160
+ response: The response from connection.request()
161
+
162
+ Raises:
163
+ LifxUnsupportedCommandError: If response is StateUnhandled or False
164
+ """
165
+ if isinstance(response, packets.Device.StateUnhandled):
166
+ raise LifxUnsupportedCommandError(
167
+ f"Device does not support packet type {response.unhandled_type}"
168
+ )
169
+
155
170
  def __init__(
156
171
  self,
157
172
  serial: str,
@@ -456,6 +471,7 @@ class Device:
456
471
  LifxDeviceNotFoundError: If device is not connected
457
472
  LifxTimeoutError: If device does not respond
458
473
  LifxProtocolError: If response is invalid
474
+ LifxUnsupportedCommandError: If device doesn't support this command
459
475
 
460
476
  Example:
461
477
  ```python
@@ -469,6 +485,7 @@ class Device:
469
485
  """
470
486
  # Request automatically unpacks and decodes label
471
487
  state = await self.connection.request(packets.Device.GetLabel())
488
+ self._raise_if_unhandled(state)
472
489
 
473
490
  # Store label
474
491
  self._label = state.label
@@ -492,6 +509,7 @@ class Device:
492
509
  ValueError: If label is too long
493
510
  LifxDeviceNotFoundError: If device is not connected
494
511
  LifxTimeoutError: If device does not respond
512
+ LifxUnsupportedCommandError: If device doesn't support this command
495
513
 
496
514
  Example:
497
515
  ```python
@@ -508,9 +526,10 @@ class Device:
508
526
  label_bytes = label_bytes.ljust(32, b"\x00")
509
527
 
510
528
  # Request automatically handles acknowledgement
511
- await self.connection.request(
529
+ result = await self.connection.request(
512
530
  packets.Device.SetLabel(label=label_bytes),
513
531
  )
532
+ self._raise_if_unhandled(result)
514
533
 
515
534
  # Update cached state
516
535
  self._label = label
@@ -535,6 +554,7 @@ class Device:
535
554
  LifxDeviceNotFoundError: If device is not connected
536
555
  LifxTimeoutError: If device does not respond
537
556
  LifxProtocolError: If response is invalid
557
+ LifxUnsupportedCommandError: If device doesn't support this command
538
558
 
539
559
  Example:
540
560
  ```python
@@ -544,6 +564,7 @@ class Device:
544
564
  """
545
565
  # Request automatically unpacks response
546
566
  state = await self.connection.request(packets.Device.GetPower())
567
+ self._raise_if_unhandled(state)
547
568
 
548
569
  # Power level is uint16 (0 or 65535)
549
570
  _LOGGER.debug(
@@ -566,6 +587,7 @@ class Device:
566
587
  ValueError: If integer value is not 0 or 65535
567
588
  LifxDeviceNotFoundError: If device is not connected
568
589
  LifxTimeoutError: If device does not respond
590
+ LifxUnsupportedCommandError: If device doesn't support this command
569
591
 
570
592
  Example:
571
593
  ```python
@@ -591,9 +613,10 @@ class Device:
591
613
  raise TypeError(f"Expected bool or int, got {type(level).__name__}")
592
614
 
593
615
  # Request automatically handles acknowledgement
594
- await self.connection.request(
616
+ result = await self.connection.request(
595
617
  packets.Device.SetPower(level=power_level),
596
618
  )
619
+ self._raise_if_unhandled(result)
597
620
 
598
621
  _LOGGER.debug(
599
622
  {
@@ -616,6 +639,7 @@ class Device:
616
639
  LifxDeviceNotFoundError: If device is not connected
617
640
  LifxTimeoutError: If device does not respond
618
641
  LifxProtocolError: If response is invalid
642
+ LifxUnsupportedCommandError: If device doesn't support this command
619
643
 
620
644
  Example:
621
645
  ```python
@@ -625,6 +649,7 @@ class Device:
625
649
  """
626
650
  # Request automatically unpacks response
627
651
  state = await self.connection.request(packets.Device.GetVersion())
652
+ self._raise_if_unhandled(state)
628
653
 
629
654
  version = DeviceVersion(
630
655
  vendor=state.vendor,
@@ -655,6 +680,7 @@ class Device:
655
680
  LifxDeviceNotFoundError: If device is not connected
656
681
  LifxTimeoutError: If device does not respond
657
682
  LifxProtocolError: If response is invalid
683
+ LifxUnsupportedCommandError: If device doesn't support this command
658
684
 
659
685
  Example:
660
686
  ```python
@@ -665,6 +691,7 @@ class Device:
665
691
  """
666
692
  # Request automatically unpacks response
667
693
  state = await self.connection.request(packets.Device.GetInfo()) # type: ignore
694
+ self._raise_if_unhandled(state)
668
695
 
669
696
  info = DeviceInfo(time=state.time, uptime=state.uptime, downtime=state.downtime)
670
697
 
@@ -694,6 +721,7 @@ class Device:
694
721
  LifxDeviceNotFoundError: If device is not connected
695
722
  LifxTimeoutError: If device does not respond
696
723
  LifxProtocolError: If response is invalid
724
+ LifxUnsupportedCommandError: If device doesn't support this command
697
725
 
698
726
  Example:
699
727
  ```python
@@ -704,6 +732,7 @@ class Device:
704
732
  """
705
733
  # Request WiFi info from device
706
734
  state = await self.connection.request(packets.Device.GetWifiInfo())
735
+ self._raise_if_unhandled(state)
707
736
 
708
737
  # Extract WiFi info from response
709
738
  wifi_info = WifiInfo(signal=state.signal)
@@ -730,6 +759,7 @@ class Device:
730
759
  LifxDeviceNotFoundError: If device is not connected
731
760
  LifxTimeoutError: If device does not respond
732
761
  LifxProtocolError: If response is invalid
762
+ LifxUnsupportedCommandError: If device doesn't support this command
733
763
 
734
764
  Example:
735
765
  ```python
@@ -739,6 +769,7 @@ class Device:
739
769
  """
740
770
  # Request automatically unpacks response
741
771
  state = await self.connection.request(packets.Device.GetHostFirmware()) # type: ignore
772
+ self._raise_if_unhandled(state)
742
773
 
743
774
  firmware = FirmwareInfo(
744
775
  build=state.build,
@@ -778,6 +809,7 @@ class Device:
778
809
  LifxDeviceNotFoundError: If device is not connected
779
810
  LifxTimeoutError: If device does not respond
780
811
  LifxProtocolError: If response is invalid
812
+ LifxUnsupportedCommandError: If device doesn't support this command
781
813
 
782
814
  Example:
783
815
  ```python
@@ -787,6 +819,7 @@ class Device:
787
819
  """
788
820
  # Request automatically unpacks response
789
821
  state = await self.connection.request(packets.Device.GetWifiFirmware()) # type: ignore
822
+ self._raise_if_unhandled(state)
790
823
 
791
824
  firmware = FirmwareInfo(
792
825
  build=state.build,
@@ -822,6 +855,7 @@ class Device:
822
855
  LifxDeviceNotFoundError: If device is not connected
823
856
  LifxTimeoutError: If device does not respond
824
857
  LifxProtocolError: If response is invalid
858
+ LifxUnsupportedCommandError: If device doesn't support this command
825
859
 
826
860
  Example:
827
861
  ```python
@@ -832,6 +866,7 @@ class Device:
832
866
  """
833
867
  # Request automatically unpacks response
834
868
  state = await self.connection.request(packets.Device.GetLocation()) # type: ignore
869
+ self._raise_if_unhandled(state)
835
870
 
836
871
  location = LocationInfo(
837
872
  location=state.location,
@@ -873,6 +908,7 @@ class Device:
873
908
  LifxDeviceNotFoundError: If device is not connected
874
909
  LifxTimeoutError: If device does not respond
875
910
  ValueError: If label is invalid
911
+ LifxUnsupportedCommandError: If device doesn't support this command
876
912
 
877
913
  Example:
878
914
  ```python
@@ -967,11 +1003,12 @@ class Device:
967
1003
  updated_at = int(time.time() * 1e9)
968
1004
 
969
1005
  # Update this device
970
- await self.connection.request(
1006
+ result = await self.connection.request(
971
1007
  packets.Device.SetLocation(
972
1008
  location=location_uuid_to_use, label=label_bytes, updated_at=updated_at
973
1009
  ),
974
1010
  )
1011
+ self._raise_if_unhandled(result)
975
1012
 
976
1013
  # Update cached state
977
1014
  location_info = LocationInfo(
@@ -1003,6 +1040,7 @@ class Device:
1003
1040
  LifxDeviceNotFoundError: If device is not connected
1004
1041
  LifxTimeoutError: If device does not respond
1005
1042
  LifxProtocolError: If response is invalid
1043
+ LifxUnsupportedCommandError: If device doesn't support this command
1006
1044
 
1007
1045
  Example:
1008
1046
  ```python
@@ -1013,6 +1051,7 @@ class Device:
1013
1051
  """
1014
1052
  # Request automatically unpacks response
1015
1053
  state = await self.connection.request(packets.Device.GetGroup()) # type: ignore
1054
+ self._raise_if_unhandled(state)
1016
1055
 
1017
1056
  group = GroupInfo(
1018
1057
  group=state.group,
@@ -1054,6 +1093,7 @@ class Device:
1054
1093
  LifxDeviceNotFoundError: If device is not connected
1055
1094
  LifxTimeoutError: If device does not respond
1056
1095
  ValueError: If label is invalid
1096
+ LifxUnsupportedCommandError: If device doesn't support this command
1057
1097
 
1058
1098
  Example:
1059
1099
  ```python
@@ -1148,11 +1188,12 @@ class Device:
1148
1188
  updated_at = int(time.time() * 1e9)
1149
1189
 
1150
1190
  # Update this device
1151
- await self.connection.request(
1191
+ result = await self.connection.request(
1152
1192
  packets.Device.SetGroup(
1153
1193
  group=group_uuid_to_use, label=label_bytes, updated_at=updated_at
1154
1194
  ),
1155
1195
  )
1196
+ self._raise_if_unhandled(result)
1156
1197
 
1157
1198
  # Update cached state
1158
1199
  group_info = GroupInfo(
@@ -1181,6 +1222,7 @@ class Device:
1181
1222
  Raises:
1182
1223
  LifxDeviceNotFoundError: If device is not connected
1183
1224
  LifxTimeoutError: If device does not respond
1225
+ LifxUnsupportedCommandError: If device doesn't support this command
1184
1226
 
1185
1227
  Example:
1186
1228
  ```python
@@ -1194,9 +1236,10 @@ class Device:
1194
1236
  comes back online and is discoverable again.
1195
1237
  """
1196
1238
  # Send reboot request
1197
- await self.connection.request(
1239
+ result = await self.connection.request(
1198
1240
  packets.Device.SetReboot(),
1199
1241
  )
1242
+ self._raise_if_unhandled(result)
1200
1243
  _LOGGER.debug(
1201
1244
  {
1202
1245
  "class": "Device",
lifx/devices/hev.py CHANGED
@@ -70,6 +70,7 @@ class HevLight(Light):
70
70
  LifxDeviceNotFoundError: If device is not connected
71
71
  LifxTimeoutError: If device does not respond
72
72
  LifxProtocolError: If response is invalid
73
+ LifxUnsupportedCommandError: If device doesn't support this command
73
74
 
74
75
  Example:
75
76
  ```python
@@ -82,6 +83,7 @@ class HevLight(Light):
82
83
  """
83
84
  # Request HEV cycle state
84
85
  state = await self.connection.request(packets.Light.GetHevCycle())
86
+ self._raise_if_unhandled(state)
85
87
 
86
88
  # Create state object
87
89
  cycle_state = HevCycleState(
@@ -116,6 +118,7 @@ class HevLight(Light):
116
118
  ValueError: If duration is negative
117
119
  LifxDeviceNotFoundError: If device is not connected
118
120
  LifxTimeoutError: If device does not respond
121
+ LifxUnsupportedCommandError: If device doesn't support this command
119
122
 
120
123
  Example:
121
124
  ```python
@@ -130,12 +133,13 @@ class HevLight(Light):
130
133
  raise ValueError(f"Duration must be non-negative, got {duration_seconds}")
131
134
 
132
135
  # Request automatically handles acknowledgement
133
- await self.connection.request(
136
+ result = await self.connection.request(
134
137
  packets.Light.SetHevCycle(
135
138
  enable=enable,
136
139
  duration_s=duration_seconds,
137
140
  ),
138
141
  )
142
+ self._raise_if_unhandled(result)
139
143
 
140
144
  _LOGGER.debug(
141
145
  {
@@ -156,6 +160,7 @@ class HevLight(Light):
156
160
  LifxDeviceNotFoundError: If device is not connected
157
161
  LifxTimeoutError: If device does not respond
158
162
  LifxProtocolError: If response is invalid
163
+ LifxUnsupportedCommandError: If device doesn't support this command
159
164
 
160
165
  Example:
161
166
  ```python
@@ -166,6 +171,7 @@ class HevLight(Light):
166
171
  """
167
172
  # Request HEV configuration
168
173
  state = await self.connection.request(packets.Light.GetHevCycleConfiguration())
174
+ self._raise_if_unhandled(state)
169
175
 
170
176
  # Create config object
171
177
  config = HevConfig(
@@ -201,6 +207,7 @@ class HevLight(Light):
201
207
  ValueError: If duration is negative
202
208
  LifxDeviceNotFoundError: If device is not connected
203
209
  LifxTimeoutError: If device does not respond
210
+ LifxUnsupportedCommandError: If device doesn't support this command
204
211
 
205
212
  Example:
206
213
  ```python
@@ -212,12 +219,13 @@ class HevLight(Light):
212
219
  raise ValueError(f"Duration must be non-negative, got {duration_seconds}")
213
220
 
214
221
  # Request automatically handles acknowledgement
215
- await self.connection.request(
222
+ result = await self.connection.request(
216
223
  packets.Light.SetHevCycleConfiguration(
217
224
  indication=indication,
218
225
  duration_s=duration_seconds,
219
226
  ),
220
227
  )
228
+ self._raise_if_unhandled(result)
221
229
 
222
230
  # Update cached state
223
231
  self._hev_config = HevConfig(indication=indication, duration_s=duration_seconds)
@@ -242,6 +250,7 @@ class HevLight(Light):
242
250
  LifxDeviceNotFoundError: If device is not connected
243
251
  LifxTimeoutError: If device does not respond
244
252
  LifxProtocolError: If response is invalid
253
+ LifxUnsupportedCommandError: If device doesn't support this command
245
254
 
246
255
  Example:
247
256
  ```python
@@ -254,6 +263,7 @@ class HevLight(Light):
254
263
  """
255
264
  # Request last HEV result
256
265
  state = await self.connection.request(packets.Light.GetLastHevCycleResult())
266
+ self._raise_if_unhandled(state)
257
267
 
258
268
  # Store cached state
259
269
  self._hev_result = state.result
lifx/devices/infrared.py CHANGED
@@ -58,6 +58,7 @@ class InfraredLight(Light):
58
58
  LifxDeviceNotFoundError: If device is not connected
59
59
  LifxTimeoutError: If device does not respond
60
60
  LifxProtocolError: If response is invalid
61
+ LifxUnsupportedCommandError: If device doesn't support this command
61
62
 
62
63
  Example:
63
64
  ```python
@@ -68,6 +69,7 @@ class InfraredLight(Light):
68
69
  """
69
70
  # Request infrared state
70
71
  state = await self.connection.request(packets.Light.GetInfrared())
72
+ self._raise_if_unhandled(state)
71
73
 
72
74
  # Convert from uint16 (0-65535) to float (0.0-1.0)
73
75
  brightness = state.brightness / 65535.0
@@ -96,6 +98,7 @@ class InfraredLight(Light):
96
98
  ValueError: If brightness is out of range
97
99
  LifxDeviceNotFoundError: If device is not connected
98
100
  LifxTimeoutError: If device does not respond
101
+ LifxUnsupportedCommandError: If device doesn't support this command
99
102
 
100
103
  Example:
101
104
  ```python
@@ -115,9 +118,10 @@ class InfraredLight(Light):
115
118
  brightness_u16 = max(0, min(65535, int(round(brightness * 65535))))
116
119
 
117
120
  # Request automatically handles acknowledgement
118
- await self.connection.request(
121
+ result = await self.connection.request(
119
122
  packets.Light.SetInfrared(brightness=brightness_u16),
120
123
  )
124
+ self._raise_if_unhandled(result)
121
125
 
122
126
  # Update cached state
123
127
  self._infrared = brightness
lifx/devices/light.py CHANGED
@@ -83,6 +83,7 @@ class Light(Device):
83
83
  LifxDeviceNotFoundError: If device is not connected
84
84
  LifxTimeoutError: If device does not respond
85
85
  LifxProtocolError: If response is invalid
86
+ LifxUnsupportedCommandError: If device doesn't support this command
86
87
 
87
88
  Example:
88
89
  ```python
@@ -92,6 +93,7 @@ class Light(Device):
92
93
  """
93
94
  # Request automatically unpacks response and decodes labels
94
95
  state = await self.connection.request(packets.Light.GetColor())
96
+ self._raise_if_unhandled(state)
95
97
 
96
98
  # Convert from protocol HSBK to user-friendly HSBK
97
99
  color = HSBK.from_protocol(state.color)
@@ -133,6 +135,7 @@ class Light(Device):
133
135
  Raises:
134
136
  LifxDeviceNotFoundError: If device is not connected
135
137
  LifxTimeoutError: If device does not respond
138
+ LifxUnsupportedCommandError: If device doesn't support this command
136
139
 
137
140
  Example:
138
141
  ```python
@@ -150,12 +153,13 @@ class Light(Device):
150
153
  duration_ms = int(duration * 1000)
151
154
 
152
155
  # Request automatically handles acknowledgement
153
- await self.connection.request(
156
+ result = await self.connection.request(
154
157
  packets.Light.SetColor(
155
158
  color=protocol_color,
156
159
  duration=duration_ms,
157
160
  ),
158
161
  )
162
+ self._raise_if_unhandled(result)
159
163
 
160
164
  _LOGGER.debug(
161
165
  {
@@ -357,6 +361,7 @@ class Light(Device):
357
361
  LifxDeviceNotFoundError: If device is not connected
358
362
  LifxTimeoutError: If device does not respond
359
363
  LifxProtocolError: If response is invalid
364
+ LifxUnsupportedCommandError: If device doesn't support this command
360
365
 
361
366
  Example:
362
367
  ```python
@@ -366,6 +371,7 @@ class Light(Device):
366
371
  """
367
372
  # Request automatically unpacks response
368
373
  state = await self.connection.request(packets.Light.GetPower())
374
+ self._raise_if_unhandled(state)
369
375
 
370
376
  # Power level is uint16 (0 or 65535)
371
377
  _LOGGER.debug(
@@ -394,6 +400,7 @@ class Light(Device):
394
400
  LifxDeviceNotFoundError: If device is not connected
395
401
  LifxTimeoutError: If device does not respond
396
402
  LifxProtocolError: If response is invalid
403
+ LifxUnsupportedCommandError: If device doesn't support this command
397
404
 
398
405
  Example:
399
406
  ```python
@@ -406,6 +413,7 @@ class Light(Device):
406
413
  """
407
414
  # Request automatically unpacks response
408
415
  state = await self.connection.request(packets.Sensor.GetAmbientLight())
416
+ self._raise_if_unhandled(state)
409
417
 
410
418
  _LOGGER.debug(
411
419
  {
@@ -432,6 +440,7 @@ class Light(Device):
432
440
  ValueError: If integer value is not 0 or 65535
433
441
  LifxDeviceNotFoundError: If device is not connected
434
442
  LifxTimeoutError: If device does not respond
443
+ LifxUnsupportedCommandError: If device doesn't support this command
435
444
 
436
445
  Example:
437
446
  ```python
@@ -460,9 +469,10 @@ class Light(Device):
460
469
  duration_ms = int(duration * 1000)
461
470
 
462
471
  # Request automatically handles acknowledgement
463
- await self.connection.request(
472
+ result = await self.connection.request(
464
473
  packets.Light.SetPower(level=power_level, duration=duration_ms),
465
474
  )
475
+ self._raise_if_unhandled(result)
466
476
 
467
477
  _LOGGER.debug(
468
478
  {
@@ -499,6 +509,7 @@ class Light(Device):
499
509
  ValueError: If parameters are out of range
500
510
  LifxDeviceNotFoundError: If device is not connected
501
511
  LifxTimeoutError: If device does not respond
512
+ LifxUnsupportedCommandError: If device doesn't support this command
502
513
 
503
514
  Example:
504
515
  ```python
@@ -537,7 +548,7 @@ class Light(Device):
537
548
  skew_ratio_i16 = int(skew_ratio * 65535) - 32768 # Convert to int16 range
538
549
 
539
550
  # Send request
540
- await self.connection.request(
551
+ result = await self.connection.request(
541
552
  packets.Light.SetWaveform(
542
553
  transient=bool(transient),
543
554
  color=protocol_color,
@@ -547,6 +558,7 @@ class Light(Device):
547
558
  waveform=waveform,
548
559
  ),
549
560
  )
561
+ self._raise_if_unhandled(result)
550
562
  _LOGGER.debug(
551
563
  {
552
564
  "class": "Device",
@@ -602,6 +614,7 @@ class Light(Device):
602
614
  ValueError: If parameters are out of range
603
615
  LifxDeviceNotFoundError: If device is not connected
604
616
  LifxTimeoutError: If device does not respond
617
+ LifxUnsupportedCommandError: If device doesn't support this command
605
618
 
606
619
  Example:
607
620
  ```python
@@ -647,7 +660,7 @@ class Light(Device):
647
660
  skew_ratio_i16 = int(skew_ratio * 65535) - 32768 # Convert to int16 range
648
661
 
649
662
  # Send request
650
- await self.connection.request(
663
+ result = await self.connection.request(
651
664
  packets.Light.SetWaveformOptional(
652
665
  transient=bool(transient),
653
666
  color=protocol_color,
@@ -661,6 +674,7 @@ class Light(Device):
661
674
  set_kelvin=set_kelvin,
662
675
  ),
663
676
  )
677
+ self._raise_if_unhandled(result)
664
678
  _LOGGER.debug(
665
679
  {
666
680
  "class": "Device",
lifx/devices/matrix.py CHANGED
@@ -311,6 +311,11 @@ class MatrixLight(Light):
311
311
  Returns:
312
312
  List of TileInfo objects describing each tile in the chain
313
313
 
314
+ Raises:
315
+ LifxDeviceNotFoundError: If device is not connected
316
+ LifxTimeoutError: If device does not respond
317
+ LifxUnsupportedCommandError: If device doesn't support this command
318
+
314
319
  Example:
315
320
  >>> chain = await matrix.get_device_chain()
316
321
  >>> for tile in chain:
@@ -321,6 +326,7 @@ class MatrixLight(Light):
321
326
  response: packets.Tile.StateDeviceChain = await self.connection.request(
322
327
  packets.Tile.GetDeviceChain()
323
328
  )
329
+ self._raise_if_unhandled(response)
324
330
 
325
331
  # Parse tiles from response
326
332
  tiles = []
@@ -393,6 +399,11 @@ class MatrixLight(Light):
393
399
  returns the actual zone count (e.g., 64 for 8x8, 16 for 4x4). For tiles
394
400
  with >64 zones (e.g., 128 for 16x8 Ceiling), returns 64 (protocol limit).
395
401
 
402
+ Raises:
403
+ LifxDeviceNotFoundError: If device is not connected
404
+ LifxTimeoutError: If device does not respond
405
+ LifxUnsupportedCommandError: If device doesn't support this command
406
+
396
407
  Example:
397
408
  >>> # Get all colors from first tile (no parameters needed)
398
409
  >>> colors = await matrix.get64()
@@ -432,6 +443,7 @@ class MatrixLight(Light):
432
443
  rect=TileBufferRect(fb_index=0, x=x, y=y, width=width),
433
444
  )
434
445
  )
446
+ self._raise_if_unhandled(response)
435
447
 
436
448
  max_colors = device_chain[0].width * device_chain[0].height
437
449
 
@@ -714,6 +726,11 @@ class MatrixLight(Light):
714
726
  Returns:
715
727
  MatrixEffect describing the current effect state
716
728
 
729
+ Raises:
730
+ LifxDeviceNotFoundError: If device is not connected
731
+ LifxTimeoutError: If device does not respond
732
+ LifxUnsupportedCommandError: If device doesn't support this command
733
+
717
734
  Example:
718
735
  >>> effect = await matrix.get_effect()
719
736
  >>> print(f"Effect type: {effect.effect_type}")
@@ -723,6 +740,7 @@ class MatrixLight(Light):
723
740
  response: packets.Tile.StateEffect = await self.connection.request(
724
741
  packets.Tile.GetEffect()
725
742
  )
743
+ self._raise_if_unhandled(response)
726
744
 
727
745
  # Convert protocol effect to MatrixEffect
728
746
  palette = [
lifx/devices/multizone.py CHANGED
@@ -189,6 +189,7 @@ class MultiZoneLight(Light):
189
189
  LifxDeviceNotFoundError: If device is not connected
190
190
  LifxTimeoutError: If device does not respond
191
191
  LifxProtocolError: If response is invalid
192
+ LifxUnsupportedCommandError: If device doesn't support this command
192
193
 
193
194
  Example:
194
195
  ```python
@@ -205,6 +206,7 @@ class MultiZoneLight(Light):
205
206
  state = await self.connection.request(
206
207
  packets.MultiZone.GetColorZones(start_index=0, end_index=0)
207
208
  )
209
+ self._raise_if_unhandled(state)
208
210
 
209
211
  count = state.count
210
212
 
@@ -245,6 +247,7 @@ class MultiZoneLight(Light):
245
247
  LifxDeviceNotFoundError: If device is not connected
246
248
  LifxTimeoutError: If device does not respond
247
249
  LifxProtocolError: If response is invalid
250
+ LifxUnsupportedCommandError: If device doesn't support this command
248
251
 
249
252
  Example:
250
253
  ```python
@@ -279,6 +282,7 @@ class MultiZoneLight(Light):
279
282
  start_index=current_start, end_index=current_end
280
283
  )
281
284
  ):
285
+ self._raise_if_unhandled(state)
282
286
  # Extract colors from response (up to 8 colors)
283
287
  zones_in_response = min(8, current_end - current_start + 1)
284
288
  for i in range(zones_in_response):
@@ -336,6 +340,7 @@ class MultiZoneLight(Light):
336
340
  LifxDeviceNotFoundError: If device is not connected
337
341
  LifxTimeoutError: If device does not respond
338
342
  LifxProtocolError: If response is invalid
343
+ LifxUnsupportedCommandError: If device doesn't support this command
339
344
 
340
345
  Example:
341
346
  ```python
@@ -361,6 +366,7 @@ class MultiZoneLight(Light):
361
366
  packets.MultiZone.GetExtendedColorZones(),
362
367
  timeout=2.0, # Allow time for multiple responses
363
368
  ):
369
+ self._raise_if_unhandled(packet)
364
370
  # Only process valid colors based on colors_count
365
371
  for i in range(packet.colors_count):
366
372
  if i >= len(packet.colors):
@@ -449,6 +455,7 @@ class MultiZoneLight(Light):
449
455
  ValueError: If zone indices are invalid
450
456
  LifxDeviceNotFoundError: If device is not connected
451
457
  LifxTimeoutError: If device does not respond
458
+ LifxUnsupportedCommandError: If device doesn't support this command
452
459
 
453
460
  Example:
454
461
  ```python
@@ -477,7 +484,7 @@ class MultiZoneLight(Light):
477
484
  duration_ms = int(duration * 1000)
478
485
 
479
486
  # Send request
480
- await self.connection.request(
487
+ result = await self.connection.request(
481
488
  packets.MultiZone.SetColorZones(
482
489
  start_index=start,
483
490
  end_index=end,
@@ -486,6 +493,7 @@ class MultiZoneLight(Light):
486
493
  apply=apply,
487
494
  ),
488
495
  )
496
+ self._raise_if_unhandled(result)
489
497
 
490
498
  _LOGGER.debug(
491
499
  {
@@ -529,6 +537,7 @@ class MultiZoneLight(Light):
529
537
  ValueError: If colors list is too long or zone index is invalid
530
538
  LifxDeviceNotFoundError: If device is not connected
531
539
  LifxTimeoutError: If device does not respond
540
+ LifxUnsupportedCommandError: If device doesn't support this command
532
541
 
533
542
  Example:
534
543
  ```python
@@ -556,7 +565,7 @@ class MultiZoneLight(Light):
556
565
  duration_ms = int(duration * 1000)
557
566
 
558
567
  # Send request
559
- await self.connection.request(
568
+ result = await self.connection.request(
560
569
  packets.MultiZone.SetExtendedColorZones(
561
570
  duration=duration_ms,
562
571
  apply=apply,
@@ -565,6 +574,7 @@ class MultiZoneLight(Light):
565
574
  colors=protocol_colors,
566
575
  ),
567
576
  )
577
+ self._raise_if_unhandled(result)
568
578
 
569
579
  _LOGGER.debug(
570
580
  {
@@ -602,6 +612,7 @@ class MultiZoneLight(Light):
602
612
  LifxDeviceNotFoundError: If device is not connected
603
613
  LifxTimeoutError: If device does not respond
604
614
  LifxProtocolError: If response is invalid
615
+ LifxUnsupportedCommandError: If device doesn't support this command
605
616
 
606
617
  Example:
607
618
  ```python
@@ -616,6 +627,7 @@ class MultiZoneLight(Light):
616
627
  """
617
628
  # Request automatically unpacks response
618
629
  state = await self.connection.request(packets.MultiZone.GetEffect())
630
+ self._raise_if_unhandled(state)
619
631
 
620
632
  settings = state.settings
621
633
  effect_type = settings.effect_type
@@ -672,6 +684,7 @@ class MultiZoneLight(Light):
672
684
  Raises:
673
685
  LifxDeviceNotFoundError: If device is not connected
674
686
  LifxTimeoutError: If device does not respond
687
+ LifxUnsupportedCommandError: If device doesn't support this command
675
688
 
676
689
  Example:
677
690
  ```python
@@ -701,7 +714,7 @@ class MultiZoneLight(Light):
701
714
  parameters = parameters[:8]
702
715
 
703
716
  # Send request
704
- await self.connection.request(
717
+ result = await self.connection.request(
705
718
  packets.MultiZone.SetEffect(
706
719
  settings=MultiZoneEffectSettings(
707
720
  instanceid=0, # 0 for new effect
@@ -721,10 +734,11 @@ class MultiZoneLight(Light):
721
734
  ),
722
735
  ),
723
736
  )
737
+ self._raise_if_unhandled(result)
724
738
 
725
739
  # Update cached state
726
- result = effect if effect.effect_type != FirmwareEffect.OFF else None
727
- self._multizone_effect = result
740
+ cached_effect = effect if effect.effect_type != FirmwareEffect.OFF else None
741
+ self._multizone_effect = cached_effect
728
742
 
729
743
  _LOGGER.debug(
730
744
  {
@@ -22,6 +22,7 @@ from lifx.exceptions import (
22
22
  LifxConnectionError,
23
23
  LifxProtocolError,
24
24
  LifxTimeoutError,
25
+ LifxUnsupportedCommandError,
25
26
  )
26
27
  from lifx.network.message import create_message, parse_message
27
28
  from lifx.network.transport import UdpTransport
@@ -722,8 +723,9 @@ class DeviceConnection:
722
723
 
723
724
  # Check for StateUnhandled - return False to indicate unsupported
724
725
  if header.pkt_type == _STATE_UNHANDLED_PKT_TYPE:
725
- yield False
726
- return
726
+ raise LifxUnsupportedCommandError(
727
+ "Device does not support this command"
728
+ )
727
729
 
728
730
  # ACK received successfully
729
731
  yield True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lifx-async
3
- Version: 4.3.6
3
+ Version: 4.3.8
4
4
  Summary: A modern, type-safe, async Python library for controlling LIFX lights
5
5
  Author-email: Avi Miller <me@dje.li>
6
6
  Maintainer-email: Avi Miller <me@dje.li>
@@ -5,12 +5,12 @@ lifx/const.py,sha256=dW64lf_jwAD40GSd6hkFkrni5j-w2qkV3pl6YNdCxv4,3426
5
5
  lifx/exceptions.py,sha256=pikAMppLn7gXyjiQVWM_tSvXKNh-g366nG_UWyqpHhc,815
6
6
  lifx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  lifx/devices/__init__.py,sha256=V7hW8sM_RwFgbR4Hv1ByR1JLhYq7Ft1X9pylQjCXYB8,777
8
- lifx/devices/base.py,sha256=uD3hQe2kjycRZneSptON6psOhoEgPRHVelCoLgdHbFw,41482
9
- lifx/devices/hev.py,sha256=2zZNYm3TFrL755B4cRPNdYtcDLZEQwGl_22112WsSZc,9504
10
- lifx/devices/infrared.py,sha256=TrCgJyEioIPlFumMmcSmuGYmRsSGlQ5Rllg6_9Wtg4Y,4248
11
- lifx/devices/light.py,sha256=9rL24fa44Y7QrRBDSQuG6xpWsaPbQTTm4ExvrDnYWHo,27572
12
- lifx/devices/matrix.py,sha256=mleYYsXkvvfXxV_pScb_D7VoX0AEJkClpzpNXOzQyGs,32643
13
- lifx/devices/multizone.py,sha256=c-lXcp8c1Mhs8me6smGkqQFrOOxdoGjWrOO5HnAVooY,27209
8
+ lifx/devices/base.py,sha256=dVTjzIz_h5dd2tTJkWUTpu5HmQ-H1R2hcTqqzw5_84c,43790
9
+ lifx/devices/hev.py,sha256=ow4AU3eOVAcMK2KKAyQUTB7z6EDoRz7StwVOvwwS4Sk,10124
10
+ lifx/devices/infrared.py,sha256=q8q_cpjdRwojk76jBEdBeIYmqAA4FuTy7ZUquy2yEdg,4498
11
+ lifx/devices/light.py,sha256=EvUeCtjMS23PUoj3cOshFJ8SYT517ksH_3p27J3Sr2o,28452
12
+ lifx/devices/matrix.py,sha256=8VI02LtL_hzIzyXMu1be6QEY8W1e7-jtuitKJ6clFW8,33426
13
+ lifx/devices/multizone.py,sha256=JaKpMvpxz7-RnhkJ1gS6uYrXwEFdJUpyldQsGtXZb_g,28106
14
14
  lifx/effects/__init__.py,sha256=4DF31yp7RJic5JoltMlz5dCtF5KQobU6NOUtLUKkVKE,1509
15
15
  lifx/effects/base.py,sha256=YO0Hbg2VYHKPtfYnWxmrtzYoPGOi9BUXhn8HVFKv5IM,10283
16
16
  lifx/effects/colorloop.py,sha256=kuuyENJS2irAN8vZAFsDa2guQdDbmmc4PJNiyZTfFPE,15840
@@ -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=hM7BxpG4udLCMWV18trbgbV_yjPsX5e_V4boCf8eZYs,38278
23
+ lifx/network/connection.py,sha256=8CAcmTFScW5P4lO6GQTnhb-SnPLiqa5fiztvSfQgs7g,38392
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.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,,
43
+ lifx_async-4.3.8.dist-info/METADATA,sha256=h4E8r3J8RxQbmI028hzTWzb9uW0vBe0nwzMycFlNZZE,2609
44
+ lifx_async-4.3.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
+ lifx_async-4.3.8.dist-info/licenses/LICENSE,sha256=eBz48GRA3gSiWn3rYZAz2Ewp35snnhV9cSqkVBq7g3k,1832
46
+ lifx_async-4.3.8.dist-info/RECORD,,