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.
- {lifx_async-4.2.0 → lifx_async-4.3.0}/PKG-INFO +1 -1
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/devices.md +42 -17
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/protocol.md +25 -4
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/changelog.md +16 -0
- lifx_async-4.3.0/docs/migration/effect-api-changes.md +245 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/mkdocs.yml +4 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/pyproject.toml +1 -1
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/__init__.py +4 -4
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/base.py +14 -11
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/matrix.py +6 -6
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/multizone.py +61 -80
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/state_manager.py +1 -2
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/base.py +3 -6
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/generator.py +105 -5
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/packets.py +2 -7
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/protocol_types.py +25 -40
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_base.py +2 -6
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_matrix.py +27 -27
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_multizone.py +110 -67
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_protocol/test_protocol_generator.py +4 -3
- {lifx_async-4.2.0 → lifx_async-4.3.0}/uv.lock +1 -1
- {lifx_async-4.2.0 → lifx_async-4.3.0}/.claude/settings.json +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/.github/dependabot.yml +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/.github/labeler.yml +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/.github/workflows/ci.yml +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/.github/workflows/docs.yml +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/.github/workflows/pr-automation.yml +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/.gitignore +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/.pre-commit-config.yaml +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/CLAUDE.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/LICENSE +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/README.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/colors.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/effects.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/exceptions.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/high-level.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/index.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/network.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/api/themes.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/architecture/effects-architecture.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/architecture/overview.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/faq.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/getting-started/effects.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/getting-started/installation.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/getting-started/quickstart.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/getting-started/themes.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/index.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/stylesheets/extra.css +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/user-guide/advanced-usage.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/user-guide/effects-custom.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/user-guide/effects-troubleshooting.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/user-guide/protocol-deep-dive.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/user-guide/themes.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/docs/user-guide/troubleshooting.md +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/01_simple_discovery.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/02_simple_control.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/03_waveforms.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/04_logging.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/06_pulse_effect.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/07_colorloop_effect.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/08_custom_effect.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/09_background_effect.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/10_find_specific_devices.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/11_matrix_basic.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/12_matrix_effects.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/examples/13_matrix_large.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/renovate.json +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/api.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/color.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/const.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/__init__.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/hev.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/infrared.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/devices/light.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/__init__.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/base.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/colorloop.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/conductor.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/const.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/models.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/effects/pulse.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/exceptions.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/network/__init__.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/network/connection.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/network/discovery.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/network/message.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/network/transport.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/products/__init__.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/products/generator.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/products/registry.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/__init__.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/header.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/models.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/protocol/serializer.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/py.typed +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/theme/__init__.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/theme/canvas.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/theme/generators.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/theme/library.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/src/lifx/theme/theme.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/__init__.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/conftest.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_api/__init__.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_api/test_api_apply_theme.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_api/test_api_batch_errors.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_api/test_api_batch_operations.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_api/test_api_discovery.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_api/test_api_organization.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_color.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/__init__.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/conftest.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_hev.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_infrared.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_light.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_devices/test_mac_address.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/__init__.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_base.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_capability_filtering.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_colorloop.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_integration.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_models.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_pulse.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_effects/test_state_manager.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/__init__.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_concurrent_requests.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_connection.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_discovery_devices.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_discovery_errors.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_message.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_message_advanced.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_network/test_transport.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_products/test_product_generator.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_products/test_registry.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_protocol/test_generated.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_protocol/test_header.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_protocol/test_serializer.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/__init__.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/conftest.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/test_apply_theme.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/test_canvas.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/test_generators.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/test_library.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_theme/test_theme.py +0 -0
- {lifx_async-4.2.0 → lifx_async-4.3.0}/tests/test_utils.py +0 -0
|
@@ -214,32 +214,57 @@ async def main():
|
|
|
214
214
|
### MultiZone Control
|
|
215
215
|
|
|
216
216
|
```python
|
|
217
|
-
from lifx import
|
|
217
|
+
from lifx import MultiZoneLight, Colors, FirmwareEffect, Direction
|
|
218
218
|
|
|
219
219
|
|
|
220
220
|
async def main():
|
|
221
|
-
async with
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
246
|
+
from lifx import MatrixLight, HSBK, FirmwareEffect
|
|
233
247
|
|
|
234
248
|
|
|
235
249
|
async def main():
|
|
236
|
-
async with
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
###
|
|
82
|
+
### Firmware Effect
|
|
83
83
|
|
|
84
|
-
|
|
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
|
-
###
|
|
92
|
+
### Direction
|
|
93
|
+
|
|
94
|
+
Direction enum for MOVE effects:
|
|
91
95
|
|
|
92
|
-
::: lifx.protocol.protocol_types.
|
|
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
|
|
|
@@ -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
|
-
"
|
|
102
|
-
"
|
|
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
|
|
64
|
-
|
|
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
|
-
|
|
70
|
-
|
|
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
|
|
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}
|
|
691
|
-
print(f"
|
|
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
|
|
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
|
|
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:
|
|
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 !=
|
|
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 ==
|
|
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:
|
|
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=
|
|
753
|
+
... effect_type=FirmwareEffect.MORPH,
|
|
754
754
|
... speed=5000,
|
|
755
755
|
... palette=rainbow,
|
|
756
756
|
... )
|