lifx-async 4.5.1__py3-none-any.whl → 4.6.0__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/__init__.py CHANGED
@@ -17,6 +17,7 @@ from lifx.api import (
17
17
  from lifx.color import HSBK, Colors
18
18
  from lifx.devices import (
19
19
  CeilingLight,
20
+ CeilingLightState,
20
21
  Device,
21
22
  DeviceInfo,
22
23
  DeviceVersion,
@@ -73,6 +74,7 @@ __all__ = [
73
74
  "MatrixLight",
74
75
  "MatrixLightState",
75
76
  "CeilingLight",
77
+ "CeilingLightState",
76
78
  # Color
77
79
  "HSBK",
78
80
  "Colors",
lifx/devices/__init__.py CHANGED
@@ -10,7 +10,7 @@ from lifx.devices.base import (
10
10
  FirmwareInfo,
11
11
  WifiInfo,
12
12
  )
13
- from lifx.devices.ceiling import CeilingLight
13
+ from lifx.devices.ceiling import CeilingLight, CeilingLightState
14
14
  from lifx.devices.hev import HevLight, HevLightState
15
15
  from lifx.devices.infrared import InfraredLight, InfraredLightState
16
16
  from lifx.devices.light import Light, LightState
@@ -19,6 +19,7 @@ from lifx.devices.multizone import MultiZoneEffect, MultiZoneLight, MultiZoneLig
19
19
 
20
20
  __all__ = [
21
21
  "CeilingLight",
22
+ "CeilingLightState",
22
23
  "CollectionInfo",
23
24
  "Device",
24
25
  "DeviceInfo",
lifx/devices/base.py CHANGED
@@ -26,7 +26,14 @@ from lifx.protocol import packets
26
26
  from lifx.protocol.models import Serial
27
27
 
28
28
  if TYPE_CHECKING:
29
- from lifx.devices import HevLight, InfraredLight, Light, MatrixLight, MultiZoneLight
29
+ from lifx.devices import (
30
+ CeilingLight,
31
+ HevLight,
32
+ InfraredLight,
33
+ Light,
34
+ MatrixLight,
35
+ MultiZoneLight,
36
+ )
30
37
 
31
38
  _LOGGER = logging.getLogger(__name__)
32
39
 
@@ -509,7 +516,7 @@ class Device(Generic[StateT]):
509
516
  port: int = LIFX_UDP_PORT,
510
517
  timeout: float = DEFAULT_REQUEST_TIMEOUT,
511
518
  max_retries: int = DEFAULT_MAX_RETRIES,
512
- ) -> Light | HevLight | InfraredLight | MultiZoneLight | MatrixLight:
519
+ ) -> Light | HevLight | InfraredLight | MultiZoneLight | MatrixLight | CeilingLight:
513
520
  """Create and return a fully initialized device instance.
514
521
 
515
522
  This factory method creates the appropriate device type (Light, etc)
@@ -610,7 +617,14 @@ class Device(Generic[StateT]):
610
617
 
611
618
  device_class: type[Device] = cls
612
619
 
613
- if product_info.has_matrix:
620
+ # Check for ceiling products first (subset of matrix devices)
621
+ from lifx.products import is_ceiling_product
622
+
623
+ if is_ceiling_product(version.product):
624
+ from lifx.devices.ceiling import CeilingLight
625
+
626
+ device_class = CeilingLight
627
+ elif product_info.has_matrix:
614
628
  from lifx.devices.matrix import MatrixLight
615
629
 
616
630
  device_class = MatrixLight
lifx/devices/ceiling.py CHANGED
@@ -20,11 +20,13 @@ from __future__ import annotations
20
20
 
21
21
  import json
22
22
  import logging
23
+ import time
24
+ from dataclasses import asdict, dataclass
23
25
  from pathlib import Path
24
- from typing import TYPE_CHECKING
26
+ from typing import TYPE_CHECKING, Any, cast
25
27
 
26
28
  from lifx.color import HSBK
27
- from lifx.devices.matrix import MatrixLight
29
+ from lifx.devices.matrix import MatrixLight, MatrixLightState
28
30
  from lifx.exceptions import LifxError
29
31
  from lifx.products import get_ceiling_layout, is_ceiling_product
30
32
 
@@ -34,6 +36,81 @@ if TYPE_CHECKING:
34
36
  _LOGGER = logging.getLogger(__name__)
35
37
 
36
38
 
39
+ @dataclass
40
+ class CeilingLightState(MatrixLightState):
41
+ """Ceiling light device state with uplight/downlight component control.
42
+
43
+ Extends MatrixLightState with ceiling-specific component information.
44
+
45
+ Attributes:
46
+ uplight_color: Current HSBK color of the uplight component
47
+ downlight_colors: List of HSBK colors for each downlight zone
48
+ uplight_is_on: Whether uplight component is on (brightness > 0)
49
+ downlight_is_on: Whether downlight component is on (any zone brightness > 0)
50
+ uplight_zone: Zone index for the uplight component
51
+ downlight_zones: Slice representing downlight component zones
52
+ """
53
+
54
+ uplight_color: HSBK
55
+ downlight_colors: list[HSBK]
56
+ uplight_is_on: bool
57
+ downlight_is_on: bool
58
+ uplight_zone: int
59
+ downlight_zones: slice
60
+
61
+ @property
62
+ def as_dict(self) -> Any:
63
+ """Return CeilingLightState as dict."""
64
+ return asdict(self)
65
+
66
+ @classmethod
67
+ def from_matrix_state(
68
+ cls,
69
+ matrix_state: MatrixLightState,
70
+ uplight_color: HSBK,
71
+ downlight_colors: list[HSBK],
72
+ uplight_zone: int,
73
+ downlight_zones: slice,
74
+ ) -> CeilingLightState:
75
+ """Create CeilingLightState from MatrixLightState.
76
+
77
+ Args:
78
+ matrix_state: Base MatrixLightState to extend
79
+ uplight_color: Current uplight zone color
80
+ downlight_colors: Current downlight zone colors
81
+ uplight_zone: Zone index for uplight component
82
+ downlight_zones: Slice representing downlight component zones
83
+
84
+ Returns:
85
+ CeilingLightState with all matrix state plus ceiling components
86
+ """
87
+ return cls(
88
+ model=matrix_state.model,
89
+ label=matrix_state.label,
90
+ serial=matrix_state.serial,
91
+ mac_address=matrix_state.mac_address,
92
+ power=matrix_state.power,
93
+ capabilities=matrix_state.capabilities,
94
+ host_firmware=matrix_state.host_firmware,
95
+ wifi_firmware=matrix_state.wifi_firmware,
96
+ location=matrix_state.location,
97
+ group=matrix_state.group,
98
+ color=matrix_state.color,
99
+ chain=matrix_state.chain,
100
+ tile_orientations=matrix_state.tile_orientations,
101
+ tile_colors=matrix_state.tile_colors,
102
+ tile_count=matrix_state.tile_count,
103
+ effect=matrix_state.effect,
104
+ uplight_color=uplight_color,
105
+ downlight_colors=downlight_colors,
106
+ uplight_is_on=uplight_color.brightness > 0,
107
+ downlight_is_on=any(c.brightness > 0 for c in downlight_colors),
108
+ uplight_zone=uplight_zone,
109
+ downlight_zones=downlight_zones,
110
+ last_updated=time.time(),
111
+ )
112
+
113
+
37
114
  class CeilingLight(MatrixLight):
38
115
  """LIFX Ceiling Light with independent uplight and downlight control.
39
116
 
@@ -111,6 +188,63 @@ class CeilingLight(MatrixLight):
111
188
 
112
189
  return self
113
190
 
191
+ async def _initialize_state(self) -> CeilingLightState:
192
+ """Initialize ceiling light state transactionally.
193
+
194
+ Extends MatrixLight implementation to add ceiling-specific component state.
195
+
196
+ Returns:
197
+ CeilingLightState instance with all device, light, matrix,
198
+ and ceiling component information.
199
+
200
+ Raises:
201
+ LifxTimeoutError: If device does not respond within timeout
202
+ LifxDeviceNotFoundError: If device cannot be reached
203
+ LifxProtocolError: If responses are invalid
204
+ """
205
+ matrix_state = await super()._initialize_state()
206
+
207
+ # Get ceiling component colors
208
+ uplight_color = await self.get_uplight_color()
209
+ downlight_colors = await self.get_downlight_colors()
210
+
211
+ # Create ceiling state from matrix state
212
+ ceiling_state = CeilingLightState.from_matrix_state(
213
+ matrix_state=matrix_state,
214
+ uplight_color=uplight_color,
215
+ downlight_colors=downlight_colors,
216
+ uplight_zone=self.uplight_zone,
217
+ downlight_zones=self.downlight_zones,
218
+ )
219
+
220
+ # Store in _state - cast is used in state property to access ceiling fields
221
+ self._state = ceiling_state
222
+
223
+ return ceiling_state
224
+
225
+ async def refresh_state(self) -> None:
226
+ """Refresh ceiling light state from hardware.
227
+
228
+ Fetches color, tiles, tile colors, effect, and ceiling component state.
229
+
230
+ Raises:
231
+ RuntimeError: If state has not been initialized
232
+ LifxTimeoutError: If device does not respond
233
+ LifxDeviceNotFoundError: If device cannot be reached
234
+ """
235
+ await super().refresh_state()
236
+
237
+ # Get ceiling component colors
238
+ uplight_color = await self.get_uplight_color()
239
+ downlight_colors = await self.get_downlight_colors()
240
+
241
+ # Update ceiling-specific state fields
242
+ state = cast(CeilingLightState, self._state)
243
+ state.uplight_color = uplight_color
244
+ state.downlight_colors = downlight_colors
245
+ state.uplight_is_on = uplight_color.brightness > 0
246
+ state.downlight_is_on = any(c.brightness > 0 for c in downlight_colors)
247
+
114
248
  @classmethod
115
249
  async def from_ip(
116
250
  cls,
@@ -148,6 +282,20 @@ class CeilingLight(MatrixLight):
148
282
  ceiling.connection = device.connection
149
283
  return ceiling
150
284
 
285
+ @property
286
+ def state(self) -> CeilingLightState:
287
+ """Get Ceiling light state.
288
+
289
+ Returns:
290
+ CeilingLightState with current state information.
291
+
292
+ Raises:
293
+ RuntimeError: If accessed before state initialization.
294
+ """
295
+ if self._state is None:
296
+ raise RuntimeError("State not found.")
297
+ return cast(CeilingLightState, self._state)
298
+
151
299
  @property
152
300
  def uplight_zone(self) -> int:
153
301
  """Zone index of the uplight component.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lifx-async
3
- Version: 4.5.1
3
+ Version: 4.6.0
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>
@@ -1,12 +1,12 @@
1
- lifx/__init__.py,sha256=Kn1QXDD2xcT5rgqGLfXFtnVF8ATtlg0FVxOvpaG4Z1M,2562
1
+ lifx/__init__.py,sha256=aiMLKLhmcXADJyeISNHIZ_Nuc6jEhPG8-I_R3peWpwo,2610
2
2
  lifx/api.py,sha256=PFS2b28ow40kCQvT_MKvBLZD6fKCbvsoOQFtiODDrPE,33861
3
3
  lifx/color.py,sha256=wcmeeiBmOAjunInERNd6rslKvBEpV4vfjwwiZ8v7H8A,17877
4
4
  lifx/const.py,sha256=cf_O_3TqJjIBXF1tI35PkJ1JOhmy4tRt14PSa63pilA,3471
5
5
  lifx/exceptions.py,sha256=pikAMppLn7gXyjiQVWM_tSvXKNh-g366nG_UWyqpHhc,815
6
6
  lifx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- lifx/devices/__init__.py,sha256=QQxJ6FewWEbihnjayjXwWPESsrwOWmylpwXKillQkuY,1017
8
- lifx/devices/base.py,sha256=x2RlGeCv60QLKklP6kA9wbCc3rmcHHhmvkSJHqTow5s,63151
9
- lifx/devices/ceiling.py,sha256=hpzSDxUzPvvgr3RSiqljek6u5IYTocbLcqTt82pr_18,27542
7
+ lifx/devices/__init__.py,sha256=4b5QtO0EFWxIqN2lUYgM8uLjWyHI5hUcReiF9QCjCGw,1061
8
+ lifx/devices/base.py,sha256=0G2PCJRNeIPkMCIw68x0ijn6gUIwh2jFlex8SN4Hs1Y,63530
9
+ lifx/devices/ceiling.py,sha256=Y4lwLWyQr8KtrwX3FMo3R_WKBn7CBxk-pNUsjfPXLgk,32981
10
10
  lifx/devices/hev.py,sha256=T5hvt2q_vdgPBvThx_-M7n5pZu9pL0y9Fs3Zz_KL0NM,15588
11
11
  lifx/devices/infrared.py,sha256=ePk9qxX_s-hv5gQMvio1Vv8FYiCd68HF0ySbWgSrvuU,8130
12
12
  lifx/devices/light.py,sha256=gk92lhViUWINGaxDWbs4qn8Stnn2fGCfRkC5Kk0Q-hI,34087
@@ -42,7 +42,7 @@ lifx/theme/canvas.py,sha256=4h7lgN8iu_OdchObGDgbxTqQLCb-FRKC-M-YCWef_i4,8048
42
42
  lifx/theme/generators.py,sha256=nq3Yvntq_h-eFHbmmow3LcAdA_hEbRRaP5mv9Bydrjk,6435
43
43
  lifx/theme/library.py,sha256=tKlKZNqJp8lRGDnilWyDm_Qr1vCRGGwuvWVS82anNpQ,21326
44
44
  lifx/theme/theme.py,sha256=qMEx_8E41C0Cc6f083XHiAXEglTv4YlXW0UFsG1rQKg,5521
45
- lifx_async-4.5.1.dist-info/METADATA,sha256=OjmlAONCtPMp3Ur3aNJB8HHmkSDq8wKmu9I00zsXvnk,2609
46
- lifx_async-4.5.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
47
- lifx_async-4.5.1.dist-info/licenses/LICENSE,sha256=eBz48GRA3gSiWn3rYZAz2Ewp35snnhV9cSqkVBq7g3k,1832
48
- lifx_async-4.5.1.dist-info/RECORD,,
45
+ lifx_async-4.6.0.dist-info/METADATA,sha256=MONJFD1mggSLhTieobasroj1mECAgXBS2IndKq9Poq0,2609
46
+ lifx_async-4.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
47
+ lifx_async-4.6.0.dist-info/licenses/LICENSE,sha256=eBz48GRA3gSiWn3rYZAz2Ewp35snnhV9cSqkVBq7g3k,1832
48
+ lifx_async-4.6.0.dist-info/RECORD,,