lifx-async 4.3.1__tar.gz → 4.3.3__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.3.1 → lifx_async-4.3.3}/.github/workflows/pr-automation.yml +2 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/PKG-INFO +1 -1
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/changelog.md +19 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/examples/11_matrix_basic.py +1 -3
- {lifx_async-4.3.1 → lifx_async-4.3.3}/pyproject.toml +1 -1
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/devices/matrix.py +75 -31
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/effects/base.py +10 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/effects/colorloop.py +9 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/effects/pulse.py +9 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_devices/test_matrix.py +70 -109
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_effects/test_base.py +5 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_effects/test_colorloop.py +1 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_effects/test_integration.py +5 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_effects/test_models.py +5 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_effects/test_pulse.py +1 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/uv.lock +1 -1
- {lifx_async-4.3.1 → lifx_async-4.3.3}/.claude/settings.json +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/.github/dependabot.yml +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/.github/labeler.yml +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/.github/workflows/ci.yml +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/.github/workflows/docs.yml +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/.gitignore +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/.pre-commit-config.yaml +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/CLAUDE.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/LICENSE +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/README.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/api/colors.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/api/devices.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/api/effects.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/api/exceptions.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/api/high-level.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/api/index.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/api/network.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/api/protocol.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/api/themes.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/architecture/effects-architecture.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/architecture/overview.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/faq.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/getting-started/effects.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/getting-started/installation.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/getting-started/quickstart.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/getting-started/themes.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/index.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/migration/effect-api-changes.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/stylesheets/extra.css +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/user-guide/advanced-usage.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/user-guide/effects-custom.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/user-guide/effects-troubleshooting.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/user-guide/protocol-deep-dive.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/user-guide/themes.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/docs/user-guide/troubleshooting.md +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/examples/01_simple_discovery.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/examples/02_simple_control.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/examples/03_waveforms.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/examples/04_logging.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/examples/06_pulse_effect.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/examples/07_colorloop_effect.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/examples/08_custom_effect.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/examples/09_background_effect.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/examples/10_find_specific_devices.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/examples/12_matrix_effects.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/examples/13_matrix_large.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/mkdocs.yml +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/renovate.json +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/__init__.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/api.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/color.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/const.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/devices/__init__.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/devices/base.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/devices/hev.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/devices/infrared.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/devices/light.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/devices/multizone.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/effects/__init__.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/effects/conductor.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/effects/const.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/effects/models.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/effects/state_manager.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/exceptions.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/network/__init__.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/network/connection.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/network/discovery.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/network/message.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/network/transport.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/products/__init__.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/products/generator.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/products/registry.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/protocol/__init__.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/protocol/base.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/protocol/generator.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/protocol/header.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/protocol/models.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/protocol/packets.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/protocol/protocol_types.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/protocol/serializer.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/py.typed +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/theme/__init__.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/theme/canvas.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/theme/generators.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/theme/library.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/src/lifx/theme/theme.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/__init__.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/conftest.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_api/__init__.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_api/test_api_apply_theme.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_api/test_api_batch_errors.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_api/test_api_batch_operations.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_api/test_api_discovery.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_api/test_api_organization.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_color.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_devices/__init__.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_devices/conftest.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_devices/test_base.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_devices/test_hev.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_devices/test_infrared.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_devices/test_light.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_devices/test_mac_address.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_devices/test_multizone.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_effects/__init__.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_effects/test_capability_filtering.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_effects/test_state_manager.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_network/__init__.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_network/test_concurrent_requests.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_network/test_connection.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_network/test_discovery_devices.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_network/test_discovery_errors.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_network/test_message.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_network/test_message_advanced.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_network/test_transport.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_products/test_product_generator.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_products/test_registry.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_protocol/test_generated.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_protocol/test_header.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_protocol/test_protocol_generator.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_protocol/test_serializer.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_theme/__init__.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_theme/conftest.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_theme/test_apply_theme.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_theme/test_canvas.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_theme/test_generators.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_theme/test_library.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_theme/test_theme.py +0 -0
- {lifx_async-4.3.1 → lifx_async-4.3.3}/tests/test_utils.py +0 -0
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v4.3.3 (2025-11-22)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Give MatrixLight.get64() some default parameters
|
|
10
|
+
([`a69a49c`](https://github.com/Djelibeybi/lifx-async/commit/a69a49c93488c79c8c3be58a9304fd01b4b12231))
|
|
11
|
+
|
|
12
|
+
- **themes**: Apply theme colors to all zones via proper canvas interpolation
|
|
13
|
+
([`f1628c4`](https://github.com/Djelibeybi/lifx-async/commit/f1628c4a071d257d7db79a7945d1516c783d8d52))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## v4.3.2 (2025-11-22)
|
|
17
|
+
|
|
18
|
+
### Bug Fixes
|
|
19
|
+
|
|
20
|
+
- **effects**: Add name property to LIFXEffect and subclasses
|
|
21
|
+
([`deb8a54`](https://github.com/Djelibeybi/lifx-async/commit/deb8a54f674d2d4cd9b8dce519dc6ca8678e048a))
|
|
22
|
+
|
|
23
|
+
|
|
5
24
|
## v4.3.1 (2025-11-22)
|
|
6
25
|
|
|
7
26
|
### Bug Fixes
|
|
@@ -35,9 +35,7 @@ async def main(ip: str):
|
|
|
35
35
|
|
|
36
36
|
# Get current colors from first tile
|
|
37
37
|
print("Getting current colors from tile 0...")
|
|
38
|
-
tile_colors = await matrix.get64(
|
|
39
|
-
tile_index=0, length=1, x=0, y=0, width=device_chain[0].width
|
|
40
|
-
)
|
|
38
|
+
tile_colors = await matrix.get64()
|
|
41
39
|
print(f"Retrieved {len(tile_colors)} colors\n")
|
|
42
40
|
|
|
43
41
|
if power == 0:
|
|
@@ -371,45 +371,58 @@ class MatrixLight(Light):
|
|
|
371
371
|
|
|
372
372
|
async def get64(
|
|
373
373
|
self,
|
|
374
|
-
tile_index: int,
|
|
375
|
-
length: int,
|
|
376
|
-
x: int,
|
|
377
|
-
y: int,
|
|
378
|
-
width: int,
|
|
379
|
-
fb_index: int = 0,
|
|
374
|
+
tile_index: int = 0,
|
|
375
|
+
length: int = 1,
|
|
376
|
+
x: int = 0,
|
|
377
|
+
y: int = 0,
|
|
378
|
+
width: int | None = None,
|
|
380
379
|
) -> list[HSBK]:
|
|
381
380
|
"""Get up to 64 zones of color state from a tile.
|
|
382
381
|
|
|
382
|
+
For devices with ≤64 zones, returns all zones. For devices with >64 zones,
|
|
383
|
+
returns up to 64 zones due to protocol limitations.
|
|
384
|
+
|
|
383
385
|
Args:
|
|
384
|
-
tile_index: Index of the tile (0-based)
|
|
385
|
-
length: Number of tiles to query (usually 1)
|
|
386
|
-
x: X coordinate of the rectangle (0-based)
|
|
387
|
-
y: Y coordinate of the rectangle (0-based)
|
|
388
|
-
width: Width of the rectangle in zones
|
|
389
|
-
fb_index: Frame buffer index (0 for display, 1 for temp buffer)
|
|
386
|
+
tile_index: Index of the tile (0-based). Defaults to 0.
|
|
387
|
+
length: Number of tiles to query (usually 1). Defaults to 1.
|
|
388
|
+
x: X coordinate of the rectangle (0-based). Defaults to 0.
|
|
389
|
+
y: Y coordinate of the rectangle (0-based). Defaults to 0.
|
|
390
|
+
width: Width of the rectangle in zones. Defaults to tile width.
|
|
390
391
|
|
|
391
392
|
Returns:
|
|
392
|
-
List of HSBK colors for the requested zones
|
|
393
|
+
List of HSBK colors for the requested zones. For tiles with ≤64 zones,
|
|
394
|
+
returns the actual zone count (e.g., 64 for 8x8, 16 for 4x4). For tiles
|
|
395
|
+
with >64 zones (e.g., 128 for 16x8 Ceiling), returns 64 (protocol limit).
|
|
393
396
|
|
|
394
397
|
Example:
|
|
395
|
-
>>> # Get colors from
|
|
396
|
-
>>> colors = await matrix.get64(
|
|
398
|
+
>>> # Get all colors from first tile (no parameters needed)
|
|
399
|
+
>>> colors = await matrix.get64()
|
|
400
|
+
>>>
|
|
401
|
+
>>> # Get colors from specific region
|
|
402
|
+
>>> colors = await matrix.get64(y=4) # Start at row 4
|
|
397
403
|
"""
|
|
398
404
|
# Validate parameters
|
|
399
405
|
if x < 0:
|
|
400
406
|
raise ValueError(f"x coordinate must be non-negative, got {x}")
|
|
401
407
|
if y < 0:
|
|
402
408
|
raise ValueError(f"y coordinate must be non-negative, got {y}")
|
|
403
|
-
if width <= 0:
|
|
409
|
+
if width is not None and width <= 0:
|
|
404
410
|
raise ValueError(f"width must be positive, got {width}")
|
|
405
411
|
|
|
412
|
+
if self._device_chain is None:
|
|
413
|
+
device_chain = await self.get_device_chain()
|
|
414
|
+
else:
|
|
415
|
+
device_chain = self._device_chain
|
|
416
|
+
|
|
417
|
+
if width is None:
|
|
418
|
+
width = device_chain[0].width
|
|
419
|
+
|
|
406
420
|
_LOGGER.debug(
|
|
407
|
-
"Getting 64 zones from tile %d (x=%d, y=%d, width=%d
|
|
421
|
+
"Getting 64 zones from tile %d (x=%d, y=%d, width=%d) for %s",
|
|
408
422
|
tile_index,
|
|
409
423
|
x,
|
|
410
424
|
y,
|
|
411
425
|
width,
|
|
412
|
-
fb_index,
|
|
413
426
|
self.label or self.serial,
|
|
414
427
|
)
|
|
415
428
|
|
|
@@ -417,12 +430,17 @@ class MatrixLight(Light):
|
|
|
417
430
|
packets.Tile.Get64(
|
|
418
431
|
tile_index=tile_index,
|
|
419
432
|
length=length,
|
|
420
|
-
rect=TileBufferRect(fb_index=
|
|
433
|
+
rect=TileBufferRect(fb_index=0, x=x, y=y, width=width),
|
|
421
434
|
)
|
|
422
435
|
)
|
|
423
436
|
|
|
437
|
+
max_colors = device_chain[0].width * device_chain[0].height
|
|
438
|
+
|
|
424
439
|
# Convert protocol colors to HSBK
|
|
425
|
-
return [
|
|
440
|
+
return [
|
|
441
|
+
HSBK.from_protocol(proto_color)
|
|
442
|
+
for proto_color in response.colors[:max_colors]
|
|
443
|
+
]
|
|
426
444
|
|
|
427
445
|
async def set64(
|
|
428
446
|
self,
|
|
@@ -504,7 +522,11 @@ class MatrixLight(Light):
|
|
|
504
522
|
)
|
|
505
523
|
|
|
506
524
|
async def copy_frame_buffer(
|
|
507
|
-
self,
|
|
525
|
+
self,
|
|
526
|
+
tile_index: int,
|
|
527
|
+
source_fb: int = 1,
|
|
528
|
+
target_fb: int = 0,
|
|
529
|
+
duration: float = 0.0,
|
|
508
530
|
) -> None:
|
|
509
531
|
"""Copy frame buffer (for tiles with >64 zones).
|
|
510
532
|
|
|
@@ -515,6 +537,7 @@ class MatrixLight(Light):
|
|
|
515
537
|
tile_index: Index of the tile (0-based)
|
|
516
538
|
source_fb: Source frame buffer index (usually 1)
|
|
517
539
|
target_fb: Target frame buffer index (usually 0)
|
|
540
|
+
duration: time in seconds to transition if target_fb is 0
|
|
518
541
|
|
|
519
542
|
Example:
|
|
520
543
|
>>> # For 16x8 tile (128 zones):
|
|
@@ -541,7 +564,9 @@ class MatrixLight(Light):
|
|
|
541
564
|
... fb_index=1,
|
|
542
565
|
... )
|
|
543
566
|
>>> # 3. Copy buffer 1 to buffer 0 (display)
|
|
544
|
-
>>> await matrix.copy_frame_buffer(
|
|
567
|
+
>>> await matrix.copy_frame_buffer(
|
|
568
|
+
... tile_index=0, source_fb=1, target_fb=0, duration=2.0
|
|
569
|
+
... )
|
|
545
570
|
"""
|
|
546
571
|
_LOGGER.debug(
|
|
547
572
|
"Copying frame buffer %d -> %d for tile %d on %s",
|
|
@@ -559,6 +584,7 @@ class MatrixLight(Light):
|
|
|
559
584
|
raise ValueError(f"Invalid tile_index {tile_index}")
|
|
560
585
|
|
|
561
586
|
tile = self._device_chain[tile_index]
|
|
587
|
+
duration_ms = round(duration * 1000 if duration else 0)
|
|
562
588
|
|
|
563
589
|
await self.connection.send_packet(
|
|
564
590
|
packets.Tile.CopyFrameBuffer(
|
|
@@ -572,7 +598,7 @@ class MatrixLight(Light):
|
|
|
572
598
|
dst_y=0,
|
|
573
599
|
width=tile.width,
|
|
574
600
|
height=tile.height,
|
|
575
|
-
duration=
|
|
601
|
+
duration=duration_ms,
|
|
576
602
|
)
|
|
577
603
|
)
|
|
578
604
|
|
|
@@ -723,7 +749,7 @@ class MatrixLight(Light):
|
|
|
723
749
|
async def set_effect(
|
|
724
750
|
self,
|
|
725
751
|
effect_type: FirmwareEffect,
|
|
726
|
-
speed:
|
|
752
|
+
speed: float = 3.0,
|
|
727
753
|
duration: int = 0,
|
|
728
754
|
palette: list[HSBK] | None = None,
|
|
729
755
|
sky_type: TileEffectSkyType = TileEffectSkyType.SUNRISE,
|
|
@@ -734,7 +760,7 @@ class MatrixLight(Light):
|
|
|
734
760
|
|
|
735
761
|
Args:
|
|
736
762
|
effect_type: Type of effect (OFF, MORPH, FLAME, SKY)
|
|
737
|
-
speed: Effect speed in
|
|
763
|
+
speed: Effect speed in seconds (default: 3)
|
|
738
764
|
duration: Total effect duration in nanoseconds (0 for infinite)
|
|
739
765
|
palette: Color palette for the effect (max 16 colors)
|
|
740
766
|
sky_type: Sky effect type (SUNRISE, SUNSET, CLOUDS)
|
|
@@ -751,7 +777,7 @@ class MatrixLight(Light):
|
|
|
751
777
|
... ]
|
|
752
778
|
>>> await matrix.set_effect(
|
|
753
779
|
... effect_type=FirmwareEffect.MORPH,
|
|
754
|
-
... speed=
|
|
780
|
+
... speed=5.0,
|
|
755
781
|
... palette=rainbow,
|
|
756
782
|
... )
|
|
757
783
|
"""
|
|
@@ -761,11 +787,12 @@ class MatrixLight(Light):
|
|
|
761
787
|
speed,
|
|
762
788
|
self.label or self.serial,
|
|
763
789
|
)
|
|
790
|
+
speed_ms = round(speed * 1000) if speed else 3000
|
|
764
791
|
|
|
765
792
|
# Create and validate MatrixEffect
|
|
766
793
|
effect = MatrixEffect(
|
|
767
794
|
effect_type=effect_type,
|
|
768
|
-
speed=
|
|
795
|
+
speed=speed_ms,
|
|
769
796
|
duration=duration,
|
|
770
797
|
palette=palette,
|
|
771
798
|
sky_type=sky_type,
|
|
@@ -843,9 +870,23 @@ class MatrixLight(Light):
|
|
|
843
870
|
# Create canvas and populate with theme colors
|
|
844
871
|
canvas = Canvas()
|
|
845
872
|
for tile in tiles:
|
|
846
|
-
canvas.add_points_for_tile(
|
|
847
|
-
|
|
848
|
-
|
|
873
|
+
canvas.add_points_for_tile((int(tile.user_x), int(tile.user_y)), theme)
|
|
874
|
+
canvas.shuffle_points()
|
|
875
|
+
canvas.blur_by_distance()
|
|
876
|
+
|
|
877
|
+
# Create tile canvas and fill in gaps for smooth interpolation
|
|
878
|
+
tile_canvas = Canvas()
|
|
879
|
+
for tile in tiles:
|
|
880
|
+
tile_canvas.fill_in_points(
|
|
881
|
+
canvas,
|
|
882
|
+
int(tile.user_x),
|
|
883
|
+
int(tile.user_y),
|
|
884
|
+
tile.width,
|
|
885
|
+
tile.height,
|
|
886
|
+
)
|
|
887
|
+
|
|
888
|
+
# Final blur for smooth gradients
|
|
889
|
+
tile_canvas.blur()
|
|
849
890
|
|
|
850
891
|
# Check if light is on
|
|
851
892
|
is_on = await self.get_power()
|
|
@@ -853,7 +894,10 @@ class MatrixLight(Light):
|
|
|
853
894
|
# Apply colors to each tile
|
|
854
895
|
for tile in tiles:
|
|
855
896
|
# Extract tile colors from canvas as 1D list
|
|
856
|
-
|
|
897
|
+
tile_coords = (int(tile.user_x), int(tile.user_y))
|
|
898
|
+
colors = tile_canvas.points_for_tile(
|
|
899
|
+
tile_coords, width=tile.width, height=tile.height
|
|
900
|
+
)
|
|
857
901
|
|
|
858
902
|
# Apply with appropriate timing
|
|
859
903
|
if power_on and not is_on:
|
|
@@ -59,6 +59,16 @@ class LIFXEffect(ABC):
|
|
|
59
59
|
self.conductor: Conductor | None = None
|
|
60
60
|
self.participants: list[Light] = []
|
|
61
61
|
|
|
62
|
+
@property
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def name(self) -> str:
|
|
65
|
+
"""Return the name of the effect.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
The effect name as a string
|
|
69
|
+
"""
|
|
70
|
+
raise NotImplementedError("Subclasses must implement name property")
|
|
71
|
+
|
|
62
72
|
async def async_perform(self, participants: list[Light]) -> None:
|
|
63
73
|
"""Perform common setup and play the effect.
|
|
64
74
|
|
|
@@ -127,6 +127,15 @@ class EffectColorloop(LIFXEffect):
|
|
|
127
127
|
self._running = False
|
|
128
128
|
self._stop_event = asyncio.Event()
|
|
129
129
|
|
|
130
|
+
@property
|
|
131
|
+
def name(self) -> str:
|
|
132
|
+
"""Return the name of the effect.
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
The effect name 'colorloop'
|
|
136
|
+
"""
|
|
137
|
+
return "colorloop"
|
|
138
|
+
|
|
130
139
|
async def async_play(self) -> None:
|
|
131
140
|
"""Execute the colorloop effect continuously."""
|
|
132
141
|
self._running = True
|
|
@@ -136,6 +136,15 @@ class EffectPulse(LIFXEffect):
|
|
|
136
136
|
if self.cycles < 1:
|
|
137
137
|
raise ValueError(f"Cycles must be 1 or higher, got {self.cycles}")
|
|
138
138
|
|
|
139
|
+
@property
|
|
140
|
+
def name(self) -> str:
|
|
141
|
+
"""Return the name of the effect.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
The effect name 'pulse'
|
|
145
|
+
"""
|
|
146
|
+
return "pulse"
|
|
147
|
+
|
|
139
148
|
async def async_play(self) -> None:
|
|
140
149
|
"""Execute the pulse effect on all participants."""
|
|
141
150
|
# Determine colors for each light
|
|
@@ -4,7 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
|
-
from lifx.color import HSBK
|
|
7
|
+
from lifx.color import HSBK, Colors
|
|
8
8
|
from lifx.devices.matrix import MatrixEffect, MatrixLight, TileInfo
|
|
9
9
|
from lifx.protocol.protocol_types import FirmwareEffect, TileEffectSkyType
|
|
10
10
|
|
|
@@ -74,23 +74,22 @@ class TestMatrixLight:
|
|
|
74
74
|
assert matrix.tile_count == len(await matrix.get_device_chain())
|
|
75
75
|
|
|
76
76
|
async def test_get64_single_tile(self, emulator_devices) -> None:
|
|
77
|
-
"""Test getting colors from 8x8 tile (64 zones)."""
|
|
77
|
+
"""Test getting colors from 8x8 tile (64 zones) with default parameters."""
|
|
78
78
|
matrix = emulator_devices[6]
|
|
79
79
|
async with matrix:
|
|
80
80
|
chain = await matrix.get_device_chain()
|
|
81
81
|
tile = chain[0]
|
|
82
82
|
|
|
83
|
-
#
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
)
|
|
83
|
+
# Verify tile dimensions
|
|
84
|
+
assert tile.width == 8
|
|
85
|
+
assert tile.height == 8
|
|
86
|
+
assert tile.total_zones == 64
|
|
87
|
+
|
|
88
|
+
# Get colors using default parameters (no args needed)
|
|
89
|
+
colors = await matrix.get64()
|
|
91
90
|
|
|
92
91
|
assert isinstance(colors, list)
|
|
93
|
-
assert len(colors)
|
|
92
|
+
assert len(colors) == 64 # Returns actual number of zones
|
|
94
93
|
assert all(isinstance(color, HSBK) for color in colors)
|
|
95
94
|
|
|
96
95
|
async def test_set64_single_tile(self, emulator_devices) -> None:
|
|
@@ -102,7 +101,7 @@ class TestMatrixLight:
|
|
|
102
101
|
|
|
103
102
|
# Set all zones to red
|
|
104
103
|
zone_count = tile.width * tile.height
|
|
105
|
-
red_colors = [
|
|
104
|
+
red_colors = [Colors.RED] * min(zone_count, 64)
|
|
106
105
|
|
|
107
106
|
await matrix.set64(
|
|
108
107
|
tile_index=0,
|
|
@@ -115,13 +114,7 @@ class TestMatrixLight:
|
|
|
115
114
|
)
|
|
116
115
|
|
|
117
116
|
# Verify colors were set (read back)
|
|
118
|
-
colors = await matrix.get64(
|
|
119
|
-
tile_index=0,
|
|
120
|
-
length=1,
|
|
121
|
-
x=0,
|
|
122
|
-
y=0,
|
|
123
|
-
width=tile.width,
|
|
124
|
-
)
|
|
117
|
+
colors = await matrix.get64()
|
|
125
118
|
|
|
126
119
|
# First color should be red (allow protocol conversion tolerance)
|
|
127
120
|
assert colors[0].hue < 10 or colors[0].hue > 350 # Red ~0 deg
|
|
@@ -144,13 +137,7 @@ class TestMatrixLight:
|
|
|
144
137
|
await matrix.set_matrix_colors(tile_index=0, colors=gradient, duration=0)
|
|
145
138
|
|
|
146
139
|
# Verify first few colors (partial verification to avoid test complexity)
|
|
147
|
-
colors = await matrix.get64(
|
|
148
|
-
tile_index=0,
|
|
149
|
-
length=1,
|
|
150
|
-
x=0,
|
|
151
|
-
y=0,
|
|
152
|
-
width=tile.width,
|
|
153
|
-
)
|
|
140
|
+
colors = await matrix.get64()
|
|
154
141
|
assert len(colors) > 0
|
|
155
142
|
|
|
156
143
|
async def test_set_matrix_colors_solid_color(self, emulator_devices) -> None:
|
|
@@ -167,24 +154,18 @@ class TestMatrixLight:
|
|
|
167
154
|
tile = chain[0]
|
|
168
155
|
|
|
169
156
|
# Create solid red across all zones
|
|
170
|
-
red_colors = [
|
|
157
|
+
red_colors = [Colors.RED] * tile.total_zones
|
|
171
158
|
|
|
172
159
|
# Set all zones to red (should use SetColor packet)
|
|
173
160
|
await matrix.set_matrix_colors(tile_index=0, colors=red_colors, duration=0)
|
|
174
161
|
|
|
175
162
|
# Verify the color was set by reading back
|
|
176
|
-
colors = await matrix.get64(
|
|
177
|
-
tile_index=0,
|
|
178
|
-
length=1,
|
|
179
|
-
x=0,
|
|
180
|
-
y=0,
|
|
181
|
-
width=tile.width,
|
|
182
|
-
)
|
|
163
|
+
colors = await matrix.get64()
|
|
183
164
|
|
|
184
165
|
# Verify first zone is red
|
|
185
|
-
assert colors[0].hue
|
|
186
|
-
assert colors[0].saturation
|
|
187
|
-
assert colors[0].brightness
|
|
166
|
+
assert colors[0].hue == 0
|
|
167
|
+
assert colors[0].saturation == 1.0
|
|
168
|
+
assert colors[0].brightness == 1.0
|
|
188
169
|
|
|
189
170
|
async def test_get_effect(self, emulator_devices) -> None:
|
|
190
171
|
"""Test getting current tile effect."""
|
|
@@ -217,10 +198,10 @@ class TestMatrixLight:
|
|
|
217
198
|
async with matrix:
|
|
218
199
|
# Create rainbow palette
|
|
219
200
|
rainbow = [
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
201
|
+
Colors.RED, # Red
|
|
202
|
+
Colors.YELLOW, # Yellow
|
|
203
|
+
Colors.GREEN, # Green
|
|
204
|
+
Colors.BLUE, # Blue
|
|
224
205
|
]
|
|
225
206
|
|
|
226
207
|
await matrix.set_effect(
|
|
@@ -240,10 +221,10 @@ class TestMatrixLight:
|
|
|
240
221
|
async with matrix:
|
|
241
222
|
# Flame effect with fire colors
|
|
242
223
|
fire_palette = [
|
|
243
|
-
|
|
244
|
-
HSBK
|
|
245
|
-
|
|
246
|
-
HSBK
|
|
224
|
+
Colors.RED, # Red
|
|
225
|
+
HSBK(hue=16, saturation=1.0, brightness=1.0, kelvin=3500),
|
|
226
|
+
Colors.ORANGE, # Orange
|
|
227
|
+
HSBK(hue=51, saturation=1.0, brightness=1.0, kelvin=3500),
|
|
247
228
|
]
|
|
248
229
|
|
|
249
230
|
await matrix.set_effect(
|
|
@@ -341,11 +322,11 @@ class TestMatrixLight:
|
|
|
341
322
|
assert effect.effect_type == FirmwareEffect.OFF
|
|
342
323
|
|
|
343
324
|
async def test_get64_large_tile(self, ceiling_device) -> None:
|
|
344
|
-
"""Test getting colors from 16x8 tile (128 zones).
|
|
325
|
+
"""Test getting colors from 16x8 tile (128 zones) with default parameters.
|
|
345
326
|
|
|
346
|
-
Ceiling devices have 16x8 tiles with 128 zones
|
|
347
|
-
|
|
348
|
-
|
|
327
|
+
Ceiling devices have 16x8 tiles with 128 zones. The get64() method returns
|
|
328
|
+
up to 64 colors due to protocol limitations, so we have to send two get64
|
|
329
|
+
requests.
|
|
349
330
|
"""
|
|
350
331
|
matrix = ceiling_device
|
|
351
332
|
async with matrix:
|
|
@@ -357,17 +338,13 @@ class TestMatrixLight:
|
|
|
357
338
|
assert tile.height == 8
|
|
358
339
|
assert tile.total_zones == 128
|
|
359
340
|
|
|
360
|
-
# Get
|
|
361
|
-
colors =
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
x=0,
|
|
365
|
-
y=0,
|
|
366
|
-
width=tile.width,
|
|
367
|
-
)
|
|
341
|
+
# Get zones using two get64 requests, 64 zones per request.
|
|
342
|
+
colors: list[HSBK] = []
|
|
343
|
+
colors.extend(await matrix.get64())
|
|
344
|
+
colors.extend(await matrix.get64(y=4))
|
|
368
345
|
|
|
369
346
|
assert isinstance(colors, list)
|
|
370
|
-
assert len(colors) ==
|
|
347
|
+
assert len(colors) == 128
|
|
371
348
|
assert all(isinstance(color, HSBK) for color in colors)
|
|
372
349
|
|
|
373
350
|
async def test_set64_large_tile(self, ceiling_device) -> None:
|
|
@@ -385,9 +362,12 @@ class TestMatrixLight:
|
|
|
385
362
|
assert tile.total_zones == 128
|
|
386
363
|
|
|
387
364
|
# Create 64 blue colors for first 64 zones
|
|
388
|
-
blue_colors = [
|
|
365
|
+
blue_colors = [Colors.BLUE] * 64
|
|
366
|
+
|
|
367
|
+
# Create 64 red colors for the second 64 zones
|
|
368
|
+
red_colors = [Colors.RED] * 64
|
|
389
369
|
|
|
390
|
-
# Set first 64 zones to blue (
|
|
370
|
+
# Set first 64 zones to blue (on frame buffer 1)
|
|
391
371
|
await matrix.set64(
|
|
392
372
|
tile_index=0,
|
|
393
373
|
length=1,
|
|
@@ -399,32 +379,39 @@ class TestMatrixLight:
|
|
|
399
379
|
fb_index=1, # Write to temp buffer
|
|
400
380
|
)
|
|
401
381
|
|
|
402
|
-
#
|
|
403
|
-
await matrix.
|
|
404
|
-
|
|
405
|
-
# Read back the first 64 zones from display buffer
|
|
406
|
-
colors = await matrix.get64(
|
|
382
|
+
# Set the second 64 zones to red (on frame buffer 1)
|
|
383
|
+
await matrix.set64(
|
|
407
384
|
tile_index=0,
|
|
408
385
|
length=1,
|
|
409
386
|
x=0,
|
|
410
|
-
y=
|
|
387
|
+
y=4,
|
|
411
388
|
width=tile.width,
|
|
412
|
-
|
|
389
|
+
duration=0,
|
|
390
|
+
colors=red_colors,
|
|
391
|
+
fb_index=1, # Write to temp buffer
|
|
413
392
|
)
|
|
414
393
|
|
|
394
|
+
# Copy frame buffer 1 to frame buffer 0 (display)
|
|
395
|
+
await matrix.copy_frame_buffer(tile_index=0, source_fb=1, target_fb=0)
|
|
396
|
+
|
|
397
|
+
# Get the updated colors
|
|
398
|
+
colors: list[HSBK] = []
|
|
399
|
+
colors.extend(await matrix.get64())
|
|
400
|
+
colors.extend(await matrix.get64(y=4))
|
|
401
|
+
|
|
415
402
|
# Verify the colors were set correctly
|
|
416
|
-
assert len(colors) ==
|
|
403
|
+
assert len(colors) == 128
|
|
417
404
|
# Blue is hue ~240
|
|
418
405
|
assert 230 < colors[0].hue < 250
|
|
419
406
|
assert colors[0].saturation > 0.9 # High saturation
|
|
420
407
|
assert colors[0].brightness > 0.9 # Full brightness
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
408
|
+
# Red is hue ~0
|
|
409
|
+
assert colors[64].hue == 0
|
|
410
|
+
assert colors[64].saturation == 1.0
|
|
411
|
+
assert colors[64].brightness == 1.0
|
|
425
412
|
|
|
426
413
|
async def test_set_matrix_colors_large_tile(self, ceiling_device) -> None:
|
|
427
|
-
"""Test setting all colors on 16x8 tile (128 zones)
|
|
414
|
+
"""Test setting all colors on 16x8 tile (128 zones) set_matrix_colors()
|
|
428
415
|
|
|
429
416
|
This tests the automatic frame buffer strategy for tiles with >64 zones.
|
|
430
417
|
The method should automatically batch the colors and use the frame buffer.
|
|
@@ -439,7 +426,7 @@ class TestMatrixLight:
|
|
|
439
426
|
assert tile.total_zones == 128
|
|
440
427
|
|
|
441
428
|
# Create a gradient (different colors, so uses set64 not SetColor)
|
|
442
|
-
colors = [HSBK(i * 360.0 / 128, 1.0, 1.0, 3500) for i in range(128)]
|
|
429
|
+
colors = [HSBK(round(i * 360.0 / 128), 1.0, 1.0, 3500) for i in range(128)]
|
|
443
430
|
|
|
444
431
|
# Set all 128 zones at once (should use frame buffer strategy)
|
|
445
432
|
# This requires:
|
|
@@ -449,13 +436,7 @@ class TestMatrixLight:
|
|
|
449
436
|
await matrix.set_matrix_colors(tile_index=0, colors=colors, duration=0)
|
|
450
437
|
|
|
451
438
|
# Verify first 64 zones
|
|
452
|
-
first_half = await matrix.get64(
|
|
453
|
-
tile_index=0,
|
|
454
|
-
length=1,
|
|
455
|
-
x=0,
|
|
456
|
-
y=0,
|
|
457
|
-
width=tile.width,
|
|
458
|
-
)
|
|
439
|
+
first_half = await matrix.get64()
|
|
459
440
|
|
|
460
441
|
assert len(first_half) == 64
|
|
461
442
|
# First zone should be hue 0 (red)
|
|
@@ -464,13 +445,7 @@ class TestMatrixLight:
|
|
|
464
445
|
assert first_half[0].brightness > 0.9 # Full brightness
|
|
465
446
|
|
|
466
447
|
# Verify second 64 zones
|
|
467
|
-
second_half = await matrix.get64(
|
|
468
|
-
tile_index=0,
|
|
469
|
-
length=1,
|
|
470
|
-
x=0,
|
|
471
|
-
y=4, # Start at row 4
|
|
472
|
-
width=tile.width,
|
|
473
|
-
)
|
|
448
|
+
second_half = await matrix.get64(y=4) # Start at row 4
|
|
474
449
|
|
|
475
450
|
assert len(second_half) == 64
|
|
476
451
|
# Zone 64 should be hue ~180 (cyan)
|
|
@@ -508,9 +483,9 @@ class TestMatrixLight:
|
|
|
508
483
|
tile_index=0, colors=white_colors, duration=0
|
|
509
484
|
)
|
|
510
485
|
|
|
511
|
-
# Create
|
|
512
|
-
blue_colors = [
|
|
513
|
-
green_colors = [
|
|
486
|
+
# Create colors: first 64 zones blue, second 64 zones green
|
|
487
|
+
blue_colors = [Colors.BLUE] * 64
|
|
488
|
+
green_colors = [Colors.GREEN] * 64
|
|
514
489
|
|
|
515
490
|
# Step 1: Set first 64 zones (rows 0-3) to blue in frame buffer 1
|
|
516
491
|
await matrix.set64(
|
|
@@ -540,25 +515,11 @@ class TestMatrixLight:
|
|
|
540
515
|
# This should copy all 128 zones using the tile's width and height
|
|
541
516
|
await matrix.copy_frame_buffer(tile_index=0, source_fb=1, target_fb=0)
|
|
542
517
|
|
|
543
|
-
# Step 4: Read back first 64 zones
|
|
544
|
-
first_64_colors = await matrix.get64(
|
|
545
|
-
tile_index=0,
|
|
546
|
-
length=1,
|
|
547
|
-
x=0,
|
|
548
|
-
y=0,
|
|
549
|
-
width=tile.width,
|
|
550
|
-
fb_index=0, # Read from display buffer
|
|
551
|
-
)
|
|
518
|
+
# Step 4: Read back first 64 zones
|
|
519
|
+
first_64_colors = await matrix.get64()
|
|
552
520
|
|
|
553
|
-
# Step 5: Read back second 64 zones
|
|
554
|
-
second_64_colors = await matrix.get64(
|
|
555
|
-
tile_index=0,
|
|
556
|
-
length=1,
|
|
557
|
-
x=0,
|
|
558
|
-
y=4,
|
|
559
|
-
width=tile.width,
|
|
560
|
-
fb_index=0, # Read from display buffer
|
|
561
|
-
)
|
|
521
|
+
# Step 5: Read back second 64 zones
|
|
522
|
+
second_64_colors = await matrix.get64(y=4)
|
|
562
523
|
|
|
563
524
|
# Verify all 128 zones were retrieved
|
|
564
525
|
assert len(first_64_colors) == 64
|
|
@@ -13,6 +13,11 @@ from lifx.effects.const import DEFAULT_BRIGHTNESS
|
|
|
13
13
|
class ConcreteEffect(LIFXEffect):
|
|
14
14
|
"""Concrete implementation for testing abstract base class."""
|
|
15
15
|
|
|
16
|
+
@property
|
|
17
|
+
def name(self) -> str:
|
|
18
|
+
"""Return the name of the effect."""
|
|
19
|
+
return "test"
|
|
20
|
+
|
|
16
21
|
async def async_play(self) -> None:
|
|
17
22
|
"""Minimal implementation for testing."""
|
|
18
23
|
pass
|