lifx-async 4.2.0__tar.gz → 4.3.0__tar.gz

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.
Files changed (144) hide show
  1. {lifx_async-4.2.0 → lifx_async-4.3.0}/PKG-INFO +1 -1
  2. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/devices.md +42 -17
  3. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/protocol.md +25 -4
  4. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/changelog.md +16 -0
  5. lifx_async-4.3.0/docs/migration/effect-api-changes.md +245 -0
  6. {lifx_async-4.2.0 → lifx_async-4.3.0}/mkdocs.yml +4 -0
  7. {lifx_async-4.2.0 → lifx_async-4.3.0}/pyproject.toml +1 -1
  8. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/__init__.py +4 -4
  9. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/base.py +14 -11
  10. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/matrix.py +6 -6
  11. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/multizone.py +61 -80
  12. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/state_manager.py +1 -2
  13. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/base.py +3 -6
  14. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/generator.py +105 -5
  15. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/packets.py +2 -7
  16. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/protocol_types.py +25 -40
  17. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_base.py +2 -6
  18. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_matrix.py +27 -27
  19. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_multizone.py +110 -67
  20. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_protocol/test_protocol_generator.py +4 -3
  21. {lifx_async-4.2.0 → lifx_async-4.3.0}/uv.lock +1 -1
  22. {lifx_async-4.2.0 → lifx_async-4.3.0}/.claude/settings.json +0 -0
  23. {lifx_async-4.2.0 → lifx_async-4.3.0}/.github/dependabot.yml +0 -0
  24. {lifx_async-4.2.0 → lifx_async-4.3.0}/.github/labeler.yml +0 -0
  25. {lifx_async-4.2.0 → lifx_async-4.3.0}/.github/workflows/ci.yml +0 -0
  26. {lifx_async-4.2.0 → lifx_async-4.3.0}/.github/workflows/docs.yml +0 -0
  27. {lifx_async-4.2.0 → lifx_async-4.3.0}/.github/workflows/pr-automation.yml +0 -0
  28. {lifx_async-4.2.0 → lifx_async-4.3.0}/.gitignore +0 -0
  29. {lifx_async-4.2.0 → lifx_async-4.3.0}/.pre-commit-config.yaml +0 -0
  30. {lifx_async-4.2.0 → lifx_async-4.3.0}/CLAUDE.md +0 -0
  31. {lifx_async-4.2.0 → lifx_async-4.3.0}/LICENSE +0 -0
  32. {lifx_async-4.2.0 → lifx_async-4.3.0}/README.md +0 -0
  33. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/colors.md +0 -0
  34. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/effects.md +0 -0
  35. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/exceptions.md +0 -0
  36. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/high-level.md +0 -0
  37. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/index.md +0 -0
  38. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/network.md +0 -0
  39. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/themes.md +0 -0
  40. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/architecture/effects-architecture.md +0 -0
  41. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/architecture/overview.md +0 -0
  42. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/faq.md +0 -0
  43. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/getting-started/effects.md +0 -0
  44. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/getting-started/installation.md +0 -0
  45. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/getting-started/quickstart.md +0 -0
  46. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/getting-started/themes.md +0 -0
  47. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/index.md +0 -0
  48. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/stylesheets/extra.css +0 -0
  49. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/user-guide/advanced-usage.md +0 -0
  50. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/user-guide/effects-custom.md +0 -0
  51. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/user-guide/effects-troubleshooting.md +0 -0
  52. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/user-guide/protocol-deep-dive.md +0 -0
  53. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/user-guide/themes.md +0 -0
  54. {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/user-guide/troubleshooting.md +0 -0
  55. {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/01_simple_discovery.py +0 -0
  56. {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/02_simple_control.py +0 -0
  57. {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/03_waveforms.py +0 -0
  58. {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/04_logging.py +0 -0
  59. {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/06_pulse_effect.py +0 -0
  60. {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/07_colorloop_effect.py +0 -0
  61. {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/08_custom_effect.py +0 -0
  62. {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/09_background_effect.py +0 -0
  63. {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/10_find_specific_devices.py +0 -0
  64. {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/11_matrix_basic.py +0 -0
  65. {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/12_matrix_effects.py +0 -0
  66. {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/13_matrix_large.py +0 -0
  67. {lifx_async-4.2.0 → lifx_async-4.3.0}/renovate.json +0 -0
  68. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/api.py +0 -0
  69. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/color.py +0 -0
  70. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/const.py +0 -0
  71. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/__init__.py +0 -0
  72. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/hev.py +0 -0
  73. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/infrared.py +0 -0
  74. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/light.py +0 -0
  75. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/__init__.py +0 -0
  76. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/base.py +0 -0
  77. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/colorloop.py +0 -0
  78. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/conductor.py +0 -0
  79. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/const.py +0 -0
  80. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/models.py +0 -0
  81. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/pulse.py +0 -0
  82. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/exceptions.py +0 -0
  83. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/network/__init__.py +0 -0
  84. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/network/connection.py +0 -0
  85. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/network/discovery.py +0 -0
  86. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/network/message.py +0 -0
  87. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/network/transport.py +0 -0
  88. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/products/__init__.py +0 -0
  89. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/products/generator.py +0 -0
  90. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/products/registry.py +0 -0
  91. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/__init__.py +0 -0
  92. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/header.py +0 -0
  93. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/models.py +0 -0
  94. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/serializer.py +0 -0
  95. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/py.typed +0 -0
  96. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/theme/__init__.py +0 -0
  97. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/theme/canvas.py +0 -0
  98. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/theme/generators.py +0 -0
  99. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/theme/library.py +0 -0
  100. {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/theme/theme.py +0 -0
  101. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/__init__.py +0 -0
  102. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/conftest.py +0 -0
  103. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_api/__init__.py +0 -0
  104. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_api/test_api_apply_theme.py +0 -0
  105. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_api/test_api_batch_errors.py +0 -0
  106. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_api/test_api_batch_operations.py +0 -0
  107. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_api/test_api_discovery.py +0 -0
  108. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_api/test_api_organization.py +0 -0
  109. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_color.py +0 -0
  110. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/__init__.py +0 -0
  111. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/conftest.py +0 -0
  112. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_hev.py +0 -0
  113. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_infrared.py +0 -0
  114. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_light.py +0 -0
  115. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_mac_address.py +0 -0
  116. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/__init__.py +0 -0
  117. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_base.py +0 -0
  118. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_capability_filtering.py +0 -0
  119. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_colorloop.py +0 -0
  120. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_integration.py +0 -0
  121. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_models.py +0 -0
  122. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_pulse.py +0 -0
  123. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_state_manager.py +0 -0
  124. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/__init__.py +0 -0
  125. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_concurrent_requests.py +0 -0
  126. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_connection.py +0 -0
  127. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_discovery_devices.py +0 -0
  128. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_discovery_errors.py +0 -0
  129. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_message.py +0 -0
  130. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_message_advanced.py +0 -0
  131. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_transport.py +0 -0
  132. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_products/test_product_generator.py +0 -0
  133. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_products/test_registry.py +0 -0
  134. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_protocol/test_generated.py +0 -0
  135. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_protocol/test_header.py +0 -0
  136. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_protocol/test_serializer.py +0 -0
  137. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/__init__.py +0 -0
  138. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/conftest.py +0 -0
  139. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/test_apply_theme.py +0 -0
  140. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/test_canvas.py +0 -0
  141. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/test_generators.py +0 -0
  142. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/test_library.py +0 -0
  143. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/test_theme.py +0 -0
  144. {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lifx-async
3
- Version: 4.2.0
3
+ Version: 4.3.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>
@@ -214,32 +214,57 @@ async def main():
214
214
  ### MultiZone Control
215
215
 
216
216
  ```python
217
- from lifx import find_lights, Colors
217
+ from lifx import MultiZoneLight, Colors, FirmwareEffect, Direction
218
218
 
219
219
 
220
220
  async def main():
221
- async with find_lights() as lights:
222
- for light in lights:
223
- # Get all zones - automatically uses best method
224
- colors = await light.get_all_color_zones()
225
- print(f"Device has {len(colors)} zones")
226
-
221
+ async with await MultiZoneLight.from_ip("192.168.1.100") as light:
222
+ # Get all zones - automatically uses best method
223
+ colors = await light.get_all_color_zones()
224
+ print(f"Device has {len(colors)} zones")
225
+
226
+ # Set a MOVE effect
227
+ await light.set_effect(
228
+ effect_type=FirmwareEffect.MOVE,
229
+ speed=5.0, # seconds per cycle
230
+ direction=Direction.FORWARD,
231
+ )
232
+
233
+ # Get current effect
234
+ effect = await light.get_effect()
235
+ print(f"Effect: {effect.effect_type.name}")
236
+ if effect.effect_type == FirmwareEffect.MOVE:
237
+ print(f"Direction: {effect.direction.name}")
238
+
239
+ # Stop the effect
240
+ await light.set_effect(effect_type=FirmwareEffect.OFF)
227
241
  ```
228
242
 
229
243
  ### Tile Control
230
244
 
231
245
  ```python
232
- from lifx import find_lights, HSBK
246
+ from lifx import MatrixLight, HSBK, FirmwareEffect
233
247
 
234
248
 
235
249
  async def main():
236
- async with find_lights() as lights:
237
- for light in lights:
238
- if light.has_matrix:
239
- # Set a gradient across the tile
240
- colors = [
241
- HSBK(hue=h, saturation=1.0, brightness=0.5, kelvin=3500)
242
- for h in range(0, 360, 10)
243
- ]
244
- await light.set_tile_colors(colors)
250
+ async with await MatrixLight.from_ip("192.168.1.100") as light:
251
+ # Set a gradient across the tile
252
+ colors = [
253
+ HSBK(hue=h, saturation=1.0, brightness=0.5, kelvin=3500)
254
+ for h in range(0, 360, 10)
255
+ ]
256
+ await light.set_tile_colors(colors)
257
+
258
+ # Set a tile effect (MORPH, FLAME, or SKY)
259
+ await light.set_effect(
260
+ effect_type=FirmwareEffect.FLAME,
261
+ speed=5.0, # seconds per cycle
262
+ )
263
+
264
+ # Get current effect
265
+ effect = await light.get_effect()
266
+ print(f"Tile effect: {effect.effect_type.name}")
267
+
268
+ # Stop the effect
269
+ await light.set_effect(effect_type=FirmwareEffect.OFF)
245
270
  ```
@@ -79,17 +79,21 @@ Common protocol type definitions and enums.
79
79
  heading_level: 4
80
80
  members_order: source
81
81
 
82
- ### MultiZone Effect Type
82
+ ### Firmware Effect
83
83
 
84
- ::: lifx.protocol.protocol_types.MultiZoneEffectType
84
+ Unified enum for all firmware effects (multizone and matrix devices):
85
+
86
+ ::: lifx.protocol.protocol_types.FirmwareEffect
85
87
  options:
86
88
  show_root_heading: true
87
89
  heading_level: 4
88
90
  members_order: source
89
91
 
90
- ### Tile Effect Type
92
+ ### Direction
93
+
94
+ Direction enum for MOVE effects:
91
95
 
92
- ::: lifx.protocol.protocol_types.TileEffectType
96
+ ::: lifx.protocol.protocol_types.Direction
93
97
  options:
94
98
  show_root_heading: true
95
99
  heading_level: 4
@@ -313,6 +317,23 @@ LightWaveform.TRIANGLE
313
317
  LightWaveform.PULSE
314
318
  ```
315
319
 
320
+ ### Firmware Effects
321
+
322
+ ```python
323
+ from lifx.protocol.protocol_types import FirmwareEffect, Direction
324
+
325
+ # Available firmware effects (for multizone and matrix devices)
326
+ FirmwareEffect.OFF
327
+ FirmwareEffect.MOVE # MultiZone only
328
+ FirmwareEffect.MORPH # Tile/Matrix only
329
+ FirmwareEffect.FLAME # Tile/Matrix only
330
+ FirmwareEffect.SKY # Tile/Matrix only
331
+
332
+ # Direction for MOVE effects
333
+ Direction.FORWARD # Move forward through zones
334
+ Direction.REVERSED # Move backward through zones
335
+ ```
336
+
316
337
  ## Product Registry
317
338
 
318
339
  The product registry provides automatic device type detection and capability information:
@@ -2,6 +2,22 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v4.3.0 (2025-11-22)
6
+
7
+ ### Features
8
+
9
+ - **effects**: Unify effect enums and simplify API
10
+ ([`df1c3c8`](https://github.com/Djelibeybi/lifx-async/commit/df1c3c8ba63dbf6cbfa5b973cdfe648c100a1371))
11
+
12
+
13
+ ## v4.2.1 (2025-11-21)
14
+
15
+ ### Bug Fixes
16
+
17
+ - Get_wifi_info now returns signal and rssi correctly
18
+ ([`6db03b3`](https://github.com/Djelibeybi/lifx-async/commit/6db03b334a36de6faa1b9749f545f3775a01d7dd))
19
+
20
+
5
21
  ## v4.2.0 (2025-11-21)
6
22
 
7
23
  ### Documentation
@@ -0,0 +1,245 @@
1
+ # Effect API Changes (v4.3.0)
2
+
3
+ This document describes changes to the effect handling API introduced in version 4.3.0.
4
+
5
+ ## Overview
6
+
7
+ The effect handling API has been simplified and unified to provide a cleaner, more consistent interface:
8
+
9
+ 1. **Unified Effect Enum**: `MultiZoneEffectType` and `TileEffectType` merged into `FirmwareEffect`
10
+ 2. **Direction Enum**: New `Direction` enum for MOVE effect direction control
11
+ 3. **Simplified Methods**: Effect methods renamed for clarity (`set_effect`, `get_effect`)
12
+ 4. **Unified Application Request**: `MultiZoneExtendedApplicationRequest` removed in favor of single `MultiZoneApplicationRequest`
13
+
14
+ ## Changes
15
+
16
+ ### 1. Effect Type Enums Consolidated
17
+
18
+ **Before:**
19
+ ```python
20
+ from lifx import MultiZoneEffectType, TileEffectType
21
+
22
+ # MultiZone effects
23
+ effect = MultiZoneEffectType.MOVE
24
+
25
+ # Tile effects
26
+ effect = TileEffectType.MORPH
27
+ ```
28
+
29
+ **After:**
30
+ ```python
31
+ from lifx import FirmwareEffect
32
+
33
+ # All firmware effects (multizone and matrix)
34
+ effect = FirmwareEffect.MOVE # MultiZone
35
+ effect = FirmwareEffect.MORPH # Matrix/Tile
36
+ effect = FirmwareEffect.FLAME # Matrix/Tile
37
+ effect = FirmwareEffect.SKY # Matrix/Tile
38
+ ```
39
+
40
+ ### 2. Direction Control for MOVE Effects
41
+
42
+ **Before:**
43
+ ```python
44
+ # Direction was embedded in specialized methods
45
+ await light.set_move_effect(speed=5.0, direction=1) # 0=reversed, 1=forward
46
+ ```
47
+
48
+ **After:**
49
+ ```python
50
+ from lifx import FirmwareEffect, Direction
51
+
52
+ # Direction is a proper enum with named values
53
+ await light.set_effect(
54
+ effect_type=FirmwareEffect.MOVE,
55
+ speed=5.0,
56
+ direction=Direction.FORWARD, # or Direction.REVERSED
57
+ )
58
+
59
+ # Direction can also be accessed as a property on MultiZoneEffect
60
+ effect = await light.get_effect()
61
+ if effect.effect_type == FirmwareEffect.MOVE:
62
+ print(f"Direction: {effect.direction.name}") # FORWARD or REVERSED
63
+ ```
64
+
65
+ ### 3. Method Naming Simplified
66
+
67
+ **Before:**
68
+ ```python
69
+ # MultiZone devices
70
+ await multizone_light.set_multizone_effect(...)
71
+ effect = await multizone_light.get_multizone_effect()
72
+
73
+ # Tile/Matrix devices
74
+ await matrix_light.set_tile_effect(...)
75
+ effect = await matrix_light.get_tile_effect()
76
+
77
+ # Specialized MOVE method
78
+ await multizone_light.set_move_effect(speed=5.0, direction=1)
79
+ ```
80
+
81
+ **After:**
82
+ ```python
83
+ # Unified naming across all device types
84
+ await multizone_light.set_effect(effect_type=FirmwareEffect.MOVE, ...)
85
+ effect = await multizone_light.get_effect()
86
+
87
+ await matrix_light.set_effect(effect_type=FirmwareEffect.FLAME, ...)
88
+ effect = await matrix_light.get_effect()
89
+
90
+ # No more specialized methods - use set_effect with Direction enum
91
+ ```
92
+
93
+ ### 4. Application Request Enum Unified
94
+
95
+ **Before:**
96
+ ```python
97
+ from lifx import MultiZoneApplicationRequest, MultiZoneExtendedApplicationRequest
98
+
99
+ # Different enums for different packet types
100
+ await light.set_color_zones(..., apply=MultiZoneApplicationRequest.APPLY)
101
+ await light.set_extended_color_zones(..., apply=MultiZoneExtendedApplicationRequest.APPLY)
102
+ ```
103
+
104
+ **After:**
105
+ ```python
106
+ from lifx import MultiZoneApplicationRequest
107
+
108
+ # Single enum for all multizone application control
109
+ await light.set_color_zones(..., apply=MultiZoneApplicationRequest.APPLY)
110
+ await light.set_extended_color_zones(..., apply=MultiZoneApplicationRequest.APPLY)
111
+ ```
112
+
113
+ ## Migration Guide
114
+
115
+ ### Updating MultiZone Effect Code
116
+
117
+ **Old Code:**
118
+ ```python
119
+ from lifx import MultiZoneLight, MultiZoneEffectType
120
+
121
+ async with await MultiZoneLight.from_ip("192.168.1.100") as light:
122
+ # Old API
123
+ await light.set_multizone_effect(
124
+ effect_type=MultiZoneEffectType.MOVE,
125
+ speed=5.0,
126
+ )
127
+
128
+ # Or using specialized method
129
+ await light.set_move_effect(speed=5.0, direction=1)
130
+
131
+ effect = await light.get_multizone_effect()
132
+ ```
133
+
134
+ **New Code:**
135
+ ```python
136
+ from lifx import MultiZoneLight, FirmwareEffect, Direction
137
+
138
+ async with await MultiZoneLight.from_ip("192.168.1.100") as light:
139
+ # New unified API
140
+ await light.set_effect(
141
+ effect_type=FirmwareEffect.MOVE,
142
+ speed=5.0,
143
+ direction=Direction.FORWARD,
144
+ )
145
+
146
+ effect = await light.get_effect()
147
+ if effect.effect_type == FirmwareEffect.MOVE:
148
+ print(f"Direction: {effect.direction.name}")
149
+ ```
150
+
151
+ ### Updating Matrix/Tile Effect Code
152
+
153
+ **Old Code:**
154
+ ```python
155
+ from lifx import MatrixLight, TileEffectType
156
+
157
+ async with await MatrixLight.from_ip("192.168.1.100") as light:
158
+ # Old API
159
+ await light.set_tile_effect(
160
+ effect_type=TileEffectType.FLAME,
161
+ speed=5.0,
162
+ )
163
+
164
+ effect = await light.get_tile_effect()
165
+ ```
166
+
167
+ **New Code:**
168
+ ```python
169
+ from lifx import MatrixLight, FirmwareEffect
170
+
171
+ async with await MatrixLight.from_ip("192.168.1.100") as light:
172
+ # New unified API
173
+ await light.set_effect(
174
+ effect_type=FirmwareEffect.FLAME,
175
+ speed=5.0,
176
+ )
177
+
178
+ effect = await light.get_effect()
179
+ ```
180
+
181
+ ### Updating Application Request Code
182
+
183
+ **Old Code:**
184
+ ```python
185
+ from lifx import MultiZoneApplicationRequest, MultiZoneExtendedApplicationRequest
186
+
187
+ # Standard zones
188
+ await light.set_color_zones(
189
+ start=0,
190
+ end=9,
191
+ color=color,
192
+ apply=MultiZoneApplicationRequest.APPLY,
193
+ )
194
+
195
+ # Extended zones
196
+ await light.set_extended_color_zones(
197
+ zone_index=0,
198
+ colors=colors,
199
+ apply=MultiZoneExtendedApplicationRequest.APPLY,
200
+ )
201
+ ```
202
+
203
+ **New Code:**
204
+ ```python
205
+ from lifx import MultiZoneApplicationRequest
206
+
207
+ # Standard zones
208
+ await light.set_color_zones(
209
+ start=0,
210
+ end=9,
211
+ color=color,
212
+ apply=MultiZoneApplicationRequest.APPLY,
213
+ )
214
+
215
+ # Extended zones
216
+ await light.set_extended_color_zones(
217
+ zone_index=0,
218
+ colors=colors,
219
+ apply=MultiZoneApplicationRequest.APPLY, # Same enum
220
+ )
221
+ ```
222
+
223
+ ## Summary of Removals
224
+
225
+ The following have been **removed** in v4.3.0:
226
+
227
+ - `lifx.protocol.protocol_types.MultiZoneEffectType` → use `FirmwareEffect`
228
+ - `lifx.protocol.protocol_types.TileEffectType` → use `FirmwareEffect`
229
+ - `lifx.protocol.protocol_types.MultiZoneExtendedApplicationRequest` → use `MultiZoneApplicationRequest`
230
+ - `MultiZoneLight.set_multizone_effect()` → use `set_effect()`
231
+ - `MultiZoneLight.get_multizone_effect()` → use `get_effect()`
232
+ - `MultiZoneLight.set_move_effect()` → use `set_effect(effect_type=FirmwareEffect.MOVE, direction=Direction.FORWARD)`
233
+ - `MultiZoneLight.get_move_effect()` → use `get_effect()` and access `effect.direction`
234
+ - `MatrixLight.set_tile_effect()` → use `set_effect()`
235
+ - `MatrixLight.get_tile_effect()` → use `get_effect()`
236
+
237
+ ## Benefits
238
+
239
+ These changes provide several improvements:
240
+
241
+ 1. **Consistency**: All firmware effects use the same enum and method names
242
+ 2. **Type Safety**: Direction is now a proper enum instead of integer values (0/1)
243
+ 3. **Discoverability**: Cleaner API with fewer specialized methods
244
+ 4. **Simplicity**: One enum for application requests instead of two identical ones
245
+ 5. **Maintainability**: Easier to extend with new effect types in the future
@@ -148,6 +148,8 @@ plugins:
148
148
  - api/network.md: Network layer components
149
149
  - api/protocol.md: Protocol layer and packet structures
150
150
  - api/exceptions.md: Exception hierarchy
151
+ Migration:
152
+ - migration/effect-api-changes.md: Effect API breaking changes and migration guide
151
153
  Additional Resources:
152
154
  - faq.md: Frequently asked questions
153
155
  - changelog.md: Version history and release notes
@@ -215,6 +217,8 @@ nav:
215
217
  - Network Layer: api/network.md
216
218
  - Protocol Layer: api/protocol.md
217
219
  - Exceptions: api/exceptions.md
220
+ - Migration:
221
+ - Effect API Changes: migration/effect-api-changes.md
218
222
  - FAQ: faq.md
219
223
  - Changelog: changelog.md
220
224
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lifx-async"
3
- version = "4.2.0"
3
+ version = "4.3.0"
4
4
  description = "A modern, type-safe, async Python library for controlling LIFX lights"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -43,9 +43,9 @@ from lifx.exceptions import (
43
43
  from lifx.network.discovery import DiscoveredDevice, discover_devices
44
44
  from lifx.products import ProductCapability, ProductInfo, ProductRegistry
45
45
  from lifx.protocol.protocol_types import (
46
+ Direction,
47
+ FirmwareEffect,
46
48
  LightWaveform,
47
- MultiZoneEffectType,
48
- TileEffectType,
49
49
  )
50
50
  from lifx.theme import Theme, ThemeLibrary, get_theme
51
51
 
@@ -98,8 +98,8 @@ __all__ = [
98
98
  "ProductCapability",
99
99
  # Protocol types
100
100
  "LightWaveform",
101
- "MultiZoneEffectType",
102
- "TileEffectType",
101
+ "FirmwareEffect",
102
+ "Direction",
103
103
  # Exceptions
104
104
  "LifxError",
105
105
  "LifxDeviceNotFoundError",
@@ -7,7 +7,8 @@ import ipaddress
7
7
  import logging
8
8
  import time
9
9
  import uuid
10
- from dataclasses import dataclass
10
+ from dataclasses import dataclass, field
11
+ from math import floor, log10
11
12
  from typing import Self
12
13
 
13
14
  from lifx.const import (
@@ -60,14 +61,16 @@ class WifiInfo:
60
61
  """Device WiFi module information.
61
62
 
62
63
  Attributes:
63
- signal: WiFi signal strength (mW)
64
- tx: Bytes transmitted since power on
65
- rx: Bytes received since power on
64
+ signal: WiFi signal strength
65
+ rssi: WiFi RSSI
66
66
  """
67
67
 
68
68
  signal: float
69
- tx: int
70
- rx: int
69
+ rssi: int = field(init=False)
70
+
71
+ def __post_init__(self) -> None:
72
+ """Calculate RSSI from signal."""
73
+ self.rssi = int(floor(10 * log10(self.signal) + 0.5))
71
74
 
72
75
 
73
76
  @dataclass
@@ -677,7 +680,7 @@ class Device:
677
680
  Always fetches from device.
678
681
 
679
682
  Returns:
680
- WifiInfo with signal strength and network stats
683
+ WifiInfo with signal strength and RSSI
681
684
 
682
685
  Raises:
683
686
  LifxDeviceNotFoundError: If device is not connected
@@ -687,22 +690,22 @@ class Device:
687
690
  Example:
688
691
  ```python
689
692
  wifi_info = await device.get_wifi_info()
690
- print(f"WiFi signal: {wifi_info.signal} mW")
691
- print(f"TX: {wifi_info.tx} bytes, RX: {wifi_info.rx} bytes")
693
+ print(f"WiFi signal: {wifi_info.signal}")
694
+ print(f"WiFi RSSI: {wifi_info.rssi}")
692
695
  ```
693
696
  """
694
697
  # Request WiFi info from device
695
698
  state = await self.connection.request(packets.Device.GetWifiInfo())
696
699
 
697
700
  # Extract WiFi info from response
698
- wifi_info = WifiInfo(signal=state.signal, tx=state.tx, rx=state.rx)
701
+ wifi_info = WifiInfo(signal=state.signal)
699
702
 
700
703
  _LOGGER.debug(
701
704
  {
702
705
  "class": "Device",
703
706
  "method": "get_wifi_info",
704
707
  "action": "query",
705
- "reply": {"signal": state.signal, "tx": state.tx, "rx": state.rx},
708
+ "reply": {"signal": state.signal},
706
709
  }
707
710
  )
708
711
  return wifi_info
@@ -23,12 +23,12 @@ if TYPE_CHECKING:
23
23
  from lifx.devices.light import Light
24
24
  from lifx.protocol import packets
25
25
  from lifx.protocol.protocol_types import (
26
+ FirmwareEffect,
26
27
  LightHsbk,
27
28
  TileBufferRect,
28
29
  TileEffectParameter,
29
30
  TileEffectSettings,
30
31
  TileEffectSkyType,
31
- TileEffectType,
32
32
  )
33
33
  from lifx.protocol.protocol_types import (
34
34
  TileStateDevice as LifxProtocolTileDevice,
@@ -161,7 +161,7 @@ class MatrixEffect:
161
161
  cloud_saturation_max: Maximum cloud saturation (0-255, for CLOUDS sky type)
162
162
  """
163
163
 
164
- effect_type: TileEffectType
164
+ effect_type: FirmwareEffect
165
165
  speed: int
166
166
  duration: int = 0
167
167
  palette: list[HSBK] | None = None
@@ -178,7 +178,7 @@ class MatrixEffect:
178
178
 
179
179
  # Validate all fields
180
180
  # Speed can be 0 only when effect is OFF
181
- if self.effect_type != TileEffectType.OFF:
181
+ if self.effect_type != FirmwareEffect.OFF:
182
182
  self._validate_speed_active(self.speed)
183
183
  elif self.speed < 0:
184
184
  raise ValueError(f"Effect speed must be non-negative, got {self.speed}")
@@ -190,7 +190,7 @@ class MatrixEffect:
190
190
 
191
191
  # Apply cloud saturation defaults only for CLOUDS sky type
192
192
  if (
193
- self.effect_type == TileEffectType.SKY
193
+ self.effect_type == FirmwareEffect.SKY
194
194
  and self.sky_type == TileEffectSkyType.CLOUDS
195
195
  ):
196
196
  # Apply sensible defaults for cloud saturation if not specified
@@ -722,7 +722,7 @@ class MatrixLight(Light):
722
722
 
723
723
  async def set_tile_effect(
724
724
  self,
725
- effect_type: TileEffectType,
725
+ effect_type: FirmwareEffect,
726
726
  speed: int = 3000,
727
727
  duration: int = 0,
728
728
  palette: list[HSBK] | None = None,
@@ -750,7 +750,7 @@ class MatrixLight(Light):
750
750
  ... HSBK(240, 1.0, 1.0, 3500), # Blue
751
751
  ... ]
752
752
  >>> await matrix.set_tile_effect(
753
- ... effect_type=TileEffectType.MORPH,
753
+ ... effect_type=FirmwareEffect.MORPH,
754
754
  ... speed=5000,
755
755
  ... palette=rainbow,
756
756
  ... )