lifx-async 4.7.0__tar.gz → 4.7.2__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 (156) hide show
  1. {lifx_async-4.7.0 → lifx_async-4.7.2}/.github/workflows/ci.yml +6 -6
  2. {lifx_async-4.7.0 → lifx_async-4.7.2}/.github/workflows/docs.yml +1 -1
  3. {lifx_async-4.7.0 → lifx_async-4.7.2}/PKG-INFO +1 -1
  4. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/api/devices.md +175 -0
  5. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/api/high-level.md +26 -0
  6. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/api/network.md +11 -0
  7. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/changelog.md +21 -0
  8. {lifx_async-4.7.0 → lifx_async-4.7.2}/mkdocs.yml +1 -4
  9. {lifx_async-4.7.0 → lifx_async-4.7.2}/pyproject.toml +1 -1
  10. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/api.py +3 -2
  11. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/devices/ceiling.py +1 -4
  12. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/devices/matrix.py +10 -2
  13. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_matrix.py +31 -0
  14. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_state_hev.py +1 -0
  15. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_state_infrared.py +1 -0
  16. {lifx_async-4.7.0 → lifx_async-4.7.2}/uv.lock +1 -1
  17. {lifx_async-4.7.0 → lifx_async-4.7.2}/.claude/settings.json +0 -0
  18. {lifx_async-4.7.0 → lifx_async-4.7.2}/.github/dependabot.yml +0 -0
  19. {lifx_async-4.7.0 → lifx_async-4.7.2}/.github/labeler.yml +0 -0
  20. {lifx_async-4.7.0 → lifx_async-4.7.2}/.github/workflows/pr-automation.yml +0 -0
  21. {lifx_async-4.7.0 → lifx_async-4.7.2}/.gitignore +0 -0
  22. {lifx_async-4.7.0 → lifx_async-4.7.2}/.pre-commit-config.yaml +0 -0
  23. {lifx_async-4.7.0 → lifx_async-4.7.2}/CLAUDE.md +0 -0
  24. {lifx_async-4.7.0 → lifx_async-4.7.2}/LICENSE +0 -0
  25. {lifx_async-4.7.0 → lifx_async-4.7.2}/README.md +0 -0
  26. {lifx_async-4.7.0 → lifx_async-4.7.2}/context7.json +0 -0
  27. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/api/colors.md +0 -0
  28. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/api/effects.md +0 -0
  29. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/api/exceptions.md +0 -0
  30. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/api/index.md +0 -0
  31. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/api/protocol.md +0 -0
  32. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/api/themes.md +0 -0
  33. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/architecture/effects-architecture.md +0 -0
  34. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/architecture/overview.md +0 -0
  35. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/faq.md +0 -0
  36. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/getting-started/effects.md +0 -0
  37. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/getting-started/installation.md +0 -0
  38. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/getting-started/quickstart.md +0 -0
  39. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/getting-started/themes.md +0 -0
  40. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/index.md +0 -0
  41. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/migration/effect-api-changes.md +0 -0
  42. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/stylesheets/extra.css +0 -0
  43. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/user-guide/advanced-usage.md +0 -0
  44. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/user-guide/ceiling-lights.md +0 -0
  45. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/user-guide/effects-custom.md +0 -0
  46. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/user-guide/effects-troubleshooting.md +0 -0
  47. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/user-guide/protocol-deep-dive.md +0 -0
  48. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/user-guide/themes.md +0 -0
  49. {lifx_async-4.7.0 → lifx_async-4.7.2}/docs/user-guide/troubleshooting.md +0 -0
  50. {lifx_async-4.7.0 → lifx_async-4.7.2}/examples/01_simple_discovery.py +0 -0
  51. {lifx_async-4.7.0 → lifx_async-4.7.2}/examples/02_simple_control.py +0 -0
  52. {lifx_async-4.7.0 → lifx_async-4.7.2}/examples/03_waveforms.py +0 -0
  53. {lifx_async-4.7.0 → lifx_async-4.7.2}/examples/04_logging.py +0 -0
  54. {lifx_async-4.7.0 → lifx_async-4.7.2}/examples/06_pulse_effect.py +0 -0
  55. {lifx_async-4.7.0 → lifx_async-4.7.2}/examples/07_colorloop_effect.py +0 -0
  56. {lifx_async-4.7.0 → lifx_async-4.7.2}/examples/08_custom_effect.py +0 -0
  57. {lifx_async-4.7.0 → lifx_async-4.7.2}/examples/09_background_effect.py +0 -0
  58. {lifx_async-4.7.0 → lifx_async-4.7.2}/examples/10_find_specific_devices.py +0 -0
  59. {lifx_async-4.7.0 → lifx_async-4.7.2}/examples/11_matrix_basic.py +0 -0
  60. {lifx_async-4.7.0 → lifx_async-4.7.2}/examples/12_matrix_effects.py +0 -0
  61. {lifx_async-4.7.0 → lifx_async-4.7.2}/examples/13_matrix_large.py +0 -0
  62. {lifx_async-4.7.0 → lifx_async-4.7.2}/renovate.json +0 -0
  63. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/__init__.py +0 -0
  64. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/color.py +0 -0
  65. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/const.py +0 -0
  66. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/devices/__init__.py +0 -0
  67. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/devices/base.py +0 -0
  68. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/devices/hev.py +0 -0
  69. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/devices/infrared.py +0 -0
  70. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/devices/light.py +0 -0
  71. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/devices/multizone.py +0 -0
  72. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/effects/__init__.py +0 -0
  73. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/effects/base.py +0 -0
  74. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/effects/colorloop.py +0 -0
  75. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/effects/conductor.py +0 -0
  76. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/effects/const.py +0 -0
  77. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/effects/models.py +0 -0
  78. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/effects/pulse.py +0 -0
  79. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/effects/state_manager.py +0 -0
  80. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/exceptions.py +0 -0
  81. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/network/__init__.py +0 -0
  82. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/network/connection.py +0 -0
  83. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/network/discovery.py +0 -0
  84. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/network/message.py +0 -0
  85. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/network/transport.py +0 -0
  86. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/products/__init__.py +0 -0
  87. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/products/generator.py +0 -0
  88. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/products/quirks.py +0 -0
  89. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/products/registry.py +0 -0
  90. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/protocol/__init__.py +0 -0
  91. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/protocol/base.py +0 -0
  92. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/protocol/generator.py +0 -0
  93. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/protocol/header.py +0 -0
  94. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/protocol/models.py +0 -0
  95. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/protocol/packets.py +0 -0
  96. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/protocol/protocol_types.py +0 -0
  97. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/protocol/serializer.py +0 -0
  98. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/py.typed +0 -0
  99. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/theme/__init__.py +0 -0
  100. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/theme/canvas.py +0 -0
  101. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/theme/generators.py +0 -0
  102. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/theme/library.py +0 -0
  103. {lifx_async-4.7.0 → lifx_async-4.7.2}/src/lifx/theme/theme.py +0 -0
  104. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/__init__.py +0 -0
  105. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/conftest.py +0 -0
  106. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_api/__init__.py +0 -0
  107. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_api/test_api_apply_theme.py +0 -0
  108. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_api/test_api_batch_errors.py +0 -0
  109. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_api/test_api_batch_operations.py +0 -0
  110. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_api/test_api_discovery.py +0 -0
  111. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_api/test_api_organization.py +0 -0
  112. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_color.py +0 -0
  113. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/__init__.py +0 -0
  114. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/conftest.py +0 -0
  115. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_base.py +0 -0
  116. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_ceiling.py +0 -0
  117. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_hev.py +0 -0
  118. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_infrared.py +0 -0
  119. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_light.py +0 -0
  120. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_mac_address.py +0 -0
  121. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_multizone.py +0 -0
  122. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_state_ceiling.py +0 -0
  123. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_state_light.py +0 -0
  124. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_state_management.py +0 -0
  125. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_state_matrix.py +0 -0
  126. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_devices/test_state_multizone.py +0 -0
  127. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_effects/__init__.py +0 -0
  128. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_effects/test_base.py +0 -0
  129. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_effects/test_capability_filtering.py +0 -0
  130. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_effects/test_colorloop.py +0 -0
  131. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_effects/test_integration.py +0 -0
  132. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_effects/test_models.py +0 -0
  133. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_effects/test_pulse.py +0 -0
  134. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_effects/test_state_manager.py +0 -0
  135. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_network/__init__.py +0 -0
  136. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_network/test_concurrent_requests.py +0 -0
  137. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_network/test_connection.py +0 -0
  138. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_network/test_discovery_devices.py +0 -0
  139. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_network/test_discovery_errors.py +0 -0
  140. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_network/test_message.py +0 -0
  141. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_network/test_message_advanced.py +0 -0
  142. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_network/test_transport.py +0 -0
  143. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_products/test_product_generator.py +0 -0
  144. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_products/test_registry.py +0 -0
  145. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_protocol/test_generated.py +0 -0
  146. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_protocol/test_header.py +0 -0
  147. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_protocol/test_protocol_generator.py +0 -0
  148. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_protocol/test_serializer.py +0 -0
  149. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_theme/__init__.py +0 -0
  150. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_theme/conftest.py +0 -0
  151. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_theme/test_apply_theme.py +0 -0
  152. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_theme/test_canvas.py +0 -0
  153. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_theme/test_generators.py +0 -0
  154. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_theme/test_library.py +0 -0
  155. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_theme/test_theme.py +0 -0
  156. {lifx_async-4.7.0 → lifx_async-4.7.2}/tests/test_utils.py +0 -0
@@ -96,7 +96,7 @@ jobs:
96
96
 
97
97
  - name: Upload coverage to Codecov
98
98
  if: matrix.os == 'ubuntu-latest'
99
- uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
99
+ uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
100
100
  with:
101
101
  env_vars: OS,PYTHON
102
102
  fail_ci_if_error: false
@@ -107,7 +107,7 @@ jobs:
107
107
 
108
108
  - name: Upload test results to Codecov
109
109
  if: matrix.os == 'ubuntu-latest'
110
- uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
110
+ uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
111
111
  with:
112
112
  env_vars: OS,PYTHON
113
113
  fail_ci_if_error: false
@@ -147,7 +147,7 @@ jobs:
147
147
  run: uv run --frozen pytest
148
148
 
149
149
  - name: Upload coverage to Codecov
150
- uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
150
+ uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
151
151
  with:
152
152
  fail_ci_if_error: false
153
153
  name: pytest-code-coverage-main
@@ -156,7 +156,7 @@ jobs:
156
156
  verbose: true
157
157
 
158
158
  - name: Upload test results to Codecov
159
- uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
159
+ uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
160
160
  with:
161
161
  fail_ci_if_error: false
162
162
  name: pytest-test-results-main
@@ -239,7 +239,7 @@ jobs:
239
239
 
240
240
  - name: Upload Distribution Artifacts
241
241
  if: github.event_name != 'workflow_dispatch'
242
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
242
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
243
243
  with:
244
244
  name: distribution-artifacts
245
245
  path: dist
@@ -287,7 +287,7 @@ jobs:
287
287
 
288
288
  - name: Download Build Artifacts from workflow
289
289
  if: github.event_name != 'workflow_dispatch'
290
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
290
+ uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
291
291
  id: artifact-download
292
292
  with:
293
293
  name: distribution-artifacts
@@ -49,7 +49,7 @@ jobs:
49
49
 
50
50
  - name: Upload docs artifact
51
51
  if: github.event_name == 'pull_request'
52
- uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
52
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
53
53
  with:
54
54
  name: docs
55
55
  path: site/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lifx-async
3
- Version: 4.7.0
3
+ Version: 4.7.2
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>
@@ -3,6 +3,87 @@
3
3
  Device classes provide direct control over LIFX devices. All device classes support async context
4
4
  managers for automatic resource cleanup.
5
5
 
6
+ ## State and Info Classes
7
+
8
+ Device state and information dataclasses returned by device methods.
9
+
10
+ ### DeviceState
11
+
12
+ Base device state dataclass returned by `Device.state`.
13
+
14
+ ::: lifx.devices.base.DeviceState
15
+ options:
16
+ show_root_heading: true
17
+ heading_level: 4
18
+ members_order: source
19
+ show_if_no_docstring: false
20
+
21
+ ### DeviceVersion
22
+
23
+ Device version information returned by `Device.get_version()`.
24
+
25
+ ::: lifx.devices.base.DeviceVersion
26
+ options:
27
+ show_root_heading: true
28
+ heading_level: 4
29
+ members_order: source
30
+ show_if_no_docstring: false
31
+
32
+ ### DeviceInfo
33
+
34
+ Device runtime information returned by `Device.get_info()`.
35
+
36
+ ::: lifx.devices.base.DeviceInfo
37
+ options:
38
+ show_root_heading: true
39
+ heading_level: 4
40
+ members_order: source
41
+ show_if_no_docstring: false
42
+
43
+ ### WifiInfo
44
+
45
+ WiFi module information returned by `Device.get_wifi_info()`.
46
+
47
+ ::: lifx.devices.base.WifiInfo
48
+ options:
49
+ show_root_heading: true
50
+ heading_level: 4
51
+ members_order: source
52
+ show_if_no_docstring: false
53
+
54
+ ### FirmwareInfo
55
+
56
+ Firmware version information returned by `Device.get_host_firmware()` and `Device.get_wifi_firmware()`.
57
+
58
+ ::: lifx.devices.base.FirmwareInfo
59
+ options:
60
+ show_root_heading: true
61
+ heading_level: 4
62
+ members_order: source
63
+ show_if_no_docstring: false
64
+
65
+ ### CollectionInfo
66
+
67
+ Location and group collection information returned by `Device.get_location()` and `Device.get_group()`.
68
+
69
+ ::: lifx.devices.base.CollectionInfo
70
+ options:
71
+ show_root_heading: true
72
+ heading_level: 4
73
+ members_order: source
74
+ show_if_no_docstring: false
75
+
76
+ ### DeviceCapabilities
77
+
78
+ Device capabilities from product registry, available via `Device.capabilities`.
79
+
80
+ ::: lifx.devices.base.DeviceCapabilities
81
+ options:
82
+ show_root_heading: true
83
+ heading_level: 4
84
+ members_order: source
85
+ show_if_no_docstring: false
86
+
6
87
  ## Base Device
7
88
 
8
89
  The `Device` class provides common operations available on all LIFX devices.
@@ -29,6 +110,17 @@ The `Light` class provides color control and effects for standard LIFX lights.
29
110
  filters:
30
111
  - "!^_"
31
112
 
113
+ ### LightState
114
+
115
+ Light device state dataclass returned by `Light.state`.
116
+
117
+ ::: lifx.devices.light.LightState
118
+ options:
119
+ show_root_heading: true
120
+ heading_level: 4
121
+ members_order: source
122
+ show_if_no_docstring: false
123
+
32
124
  ## HEV Light
33
125
 
34
126
  The `HevLight` class extends `Light` with anti-bacterial cleaning cycle control for LIFX HEV devices.
@@ -42,6 +134,17 @@ The `HevLight` class extends `Light` with anti-bacterial cleaning cycle control
42
134
  filters:
43
135
  - "!^_"
44
136
 
137
+ ### HevLightState
138
+
139
+ HEV light device state dataclass returned by `HevLight.state`.
140
+
141
+ ::: lifx.devices.hev.HevLightState
142
+ options:
143
+ show_root_heading: true
144
+ heading_level: 4
145
+ members_order: source
146
+ show_if_no_docstring: false
147
+
45
148
  ## Infrared Light
46
149
 
47
150
  The `InfraredLight` class extends `Light` with infrared LED control for night vision on LIFX A19 + Night Vision devices.
@@ -55,6 +158,17 @@ The `InfraredLight` class extends `Light` with infrared LED control for night vi
55
158
  filters:
56
159
  - "!^_"
57
160
 
161
+ ### InfraredLightState
162
+
163
+ Infrared light device state dataclass returned by `InfraredLight.state`.
164
+
165
+ ::: lifx.devices.infrared.InfraredLightState
166
+ options:
167
+ show_root_heading: true
168
+ heading_level: 4
169
+ members_order: source
170
+ show_if_no_docstring: false
171
+
58
172
  ## MultiZone Light
59
173
 
60
174
  The `MultiZoneLight` class controls LIFX strips and beams with multiple color zones.
@@ -68,6 +182,30 @@ The `MultiZoneLight` class controls LIFX strips and beams with multiple color zo
68
182
  filters:
69
183
  - "!^_"
70
184
 
185
+ ### MultiZoneLightState
186
+
187
+ MultiZone light device state dataclass returned by `MultiZoneLight.state`.
188
+
189
+ ::: lifx.devices.multizone.MultiZoneLightState
190
+ options:
191
+ show_root_heading: true
192
+ heading_level: 4
193
+ members_order: source
194
+ show_if_no_docstring: false
195
+
196
+ ### MultiZoneEffect
197
+
198
+ Configuration dataclass for multizone effects (MOVE). Used with `MultiZoneLight.set_effect()` and returned by `MultiZoneLight.get_effect()`.
199
+
200
+ ::: lifx.devices.multizone.MultiZoneEffect
201
+ options:
202
+ show_root_heading: true
203
+ heading_level: 4
204
+ members_order: source
205
+ show_if_no_docstring: false
206
+ filters:
207
+ - "!^_"
208
+
71
209
  ## Matrix Light
72
210
 
73
211
  The `MatrixLight` class controls LIFX matrix devices (tiles, candle, path) with 2D zone control.
@@ -81,6 +219,43 @@ The `MatrixLight` class controls LIFX matrix devices (tiles, candle, path) with
81
219
  filters:
82
220
  - "!^_"
83
221
 
222
+ ### MatrixLightState
223
+
224
+ Matrix light device state dataclass returned by `MatrixLight.state`.
225
+
226
+ ::: lifx.devices.matrix.MatrixLightState
227
+ options:
228
+ show_root_heading: true
229
+ heading_level: 4
230
+ members_order: source
231
+ show_if_no_docstring: false
232
+
233
+ ### TileInfo
234
+
235
+ Information dataclass for a single tile in the device chain. Returned as part of `MatrixLightState.chain`.
236
+
237
+ ::: lifx.devices.matrix.TileInfo
238
+ options:
239
+ show_root_heading: true
240
+ heading_level: 4
241
+ members_order: source
242
+ show_if_no_docstring: false
243
+ filters:
244
+ - "!^_"
245
+
246
+ ### MatrixEffect
247
+
248
+ Configuration dataclass for matrix effects (MORPH, FLAME, SKY). Used with `MatrixLight.set_effect()` and returned by `MatrixLight.get_effect()`.
249
+
250
+ ::: lifx.devices.matrix.MatrixEffect
251
+ options:
252
+ show_root_heading: true
253
+ heading_level: 4
254
+ members_order: source
255
+ show_if_no_docstring: false
256
+ filters:
257
+ - "!^_"
258
+
84
259
  ## Ceiling Light
85
260
 
86
261
  The `CeilingLight` class extends `MatrixLight` with independent control over uplight and downlight components for LIFX Ceiling fixtures.
@@ -34,6 +34,32 @@ recommended entry points for most users.
34
34
  members_order: source
35
35
  show_if_no_docstring: false
36
36
 
37
+ ## Organizational Groupings
38
+
39
+ Dataclasses for organizing devices by location or group. Returned by `DeviceGroup.organize_by_location()` and `DeviceGroup.organize_by_group()`.
40
+
41
+ ### LocationGrouping
42
+
43
+ Location-based device grouping returned by `DeviceGroup.organize_by_location()`.
44
+
45
+ ::: lifx.api.LocationGrouping
46
+ options:
47
+ show_root_heading: true
48
+ heading_level: 4
49
+ members_order: source
50
+ show_if_no_docstring: false
51
+
52
+ ### GroupGrouping
53
+
54
+ Group-based device grouping returned by `DeviceGroup.organize_by_group()`.
55
+
56
+ ::: lifx.api.GroupGrouping
57
+ options:
58
+ show_root_heading: true
59
+ heading_level: 4
60
+ members_order: source
61
+ show_if_no_docstring: false
62
+
37
63
  ## Examples
38
64
 
39
65
  ### Simple Discovery
@@ -18,6 +18,17 @@ Functions for discovering LIFX devices on the local network.
18
18
  heading_level: 3
19
19
  members_order: source
20
20
 
21
+ ### DiscoveryResponse
22
+
23
+ Response dataclass from custom discovery broadcasts (using packets other than GetService).
24
+
25
+ ::: lifx.network.discovery.DiscoveryResponse
26
+ options:
27
+ show_root_heading: true
28
+ heading_level: 4
29
+ members_order: source
30
+ show_if_no_docstring: false
31
+
21
32
  ## UDP Transport
22
33
 
23
34
  Low-level UDP transport for sending and receiving LIFX protocol messages.
@@ -2,6 +2,27 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v4.7.2 (2025-12-16)
6
+
7
+ ### Bug Fixes
8
+
9
+ - **api**: Close device connections in DeviceGroup context manager
10
+ ([`054bfee`](https://github.com/Djelibeybi/lifx-async/commit/054bfee88e548d38c1e7c49277d3bb334b55adcc))
11
+
12
+ ### Documentation
13
+
14
+ - **api**: Add dataclass documentation and improve navigation
15
+ ([`c859c87`](https://github.com/Djelibeybi/lifx-async/commit/c859c8711335bdf5357412ccf4364075ce0df535))
16
+
17
+
18
+ ## v4.7.1 (2025-12-13)
19
+
20
+ ### Bug Fixes
21
+
22
+ - **devices**: Add length parameter to copy_frame_buffer()
23
+ ([`6a74690`](https://github.com/Djelibeybi/lifx-async/commit/6a746904665d38545e534829c2c690a61e48da54))
24
+
25
+
5
26
  ## v4.7.0 (2025-12-13)
6
27
 
7
28
  ### Features
@@ -25,19 +25,16 @@ theme:
25
25
  icon: material/brightness-4
26
26
  name: Switch to light mode
27
27
  features:
28
- - navigation.instant
29
28
  - navigation.tracking
30
29
  - navigation.tabs
31
30
  - navigation.tabs.sticky
32
31
  - navigation.sections
33
- - navigation.expand
34
32
  - navigation.path
35
33
  - navigation.indexes
34
+ - navigation.top
36
35
  - toc.follow
37
- - toc.integrate
38
36
  - search.suggest
39
37
  - search.highlight
40
- - search.share
41
38
  - content.code.copy
42
39
  - content.code.annotate
43
40
  - content.tabs.link
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lifx-async"
3
- version = "4.7.0"
3
+ version = "4.7.2"
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"
@@ -126,8 +126,9 @@ class DeviceGroup:
126
126
  exc_val: BaseException | None,
127
127
  exc_tb: TracebackType | None,
128
128
  ) -> None:
129
- """Exit async context manager."""
130
- pass
129
+ """Exit async context manager and close all device connections."""
130
+ for device in self._devices:
131
+ await device.connection.close()
131
132
 
132
133
  def __iter__(
133
134
  self,
@@ -23,16 +23,13 @@ import logging
23
23
  import time
24
24
  from dataclasses import asdict, dataclass
25
25
  from pathlib import Path
26
- from typing import TYPE_CHECKING, Any, cast
26
+ from typing import Any, cast
27
27
 
28
28
  from lifx.color import HSBK
29
29
  from lifx.devices.matrix import MatrixLight, MatrixLightState
30
30
  from lifx.exceptions import LifxError
31
31
  from lifx.products import get_ceiling_layout, is_ceiling_product
32
32
 
33
- if TYPE_CHECKING:
34
- pass
35
-
36
33
  _LOGGER = logging.getLogger(__name__)
37
34
 
38
35
 
@@ -699,6 +699,7 @@ class MatrixLight(Light):
699
699
  source_fb: int = 1,
700
700
  target_fb: int = 0,
701
701
  duration: float = 0.0,
702
+ length: int = 1,
702
703
  ) -> None:
703
704
  """Copy frame buffer (for tiles with >64 zones).
704
705
 
@@ -710,6 +711,7 @@ class MatrixLight(Light):
710
711
  source_fb: Source frame buffer index (usually 1)
711
712
  target_fb: Target frame buffer index (usually 0)
712
713
  duration: time in seconds to transition if target_fb is 0
714
+ length: Number of tiles to update starting from tile_index (default 1)
713
715
 
714
716
  Example:
715
717
  >>> # For 16x8 tile (128 zones):
@@ -739,12 +741,18 @@ class MatrixLight(Light):
739
741
  >>> await matrix.copy_frame_buffer(
740
742
  ... tile_index=0, source_fb=1, target_fb=0, duration=2.0
741
743
  ... )
744
+
745
+ >>> # For a chain of 5 tiles, update all simultaneously:
746
+ >>> await matrix.copy_frame_buffer(
747
+ ... tile_index=0, source_fb=1, target_fb=0, length=5
748
+ ... )
742
749
  """
743
750
  _LOGGER.debug(
744
- "Copying frame buffer %d -> %d for tile %d on %s",
751
+ "Copying frame buffer %d -> %d for tile %d (length=%d) on %s",
745
752
  source_fb,
746
753
  target_fb,
747
754
  tile_index,
755
+ length,
748
756
  self.label or self.serial,
749
757
  )
750
758
 
@@ -761,7 +769,7 @@ class MatrixLight(Light):
761
769
  await self.connection.send_packet(
762
770
  packets.Tile.CopyFrameBuffer(
763
771
  tile_index=tile_index,
764
- length=1,
772
+ length=length,
765
773
  src_fb_index=source_fb,
766
774
  dst_fb_index=target_fb,
767
775
  src_x=0,
@@ -373,6 +373,37 @@ class TestMatrixLight:
373
373
  assert copied_colors[0].saturation == 1.0
374
374
  assert copied_colors[0].brightness == 1.0
375
375
 
376
+ async def test_copy_frame_buffer_with_length(self, emulator_devices) -> None:
377
+ """Test copy_frame_buffer() with explicit length parameter."""
378
+ matrix = emulator_devices[6]
379
+ async with matrix:
380
+ chain = await matrix.get_device_chain()
381
+ tile = chain[0]
382
+
383
+ # Set pattern on temp buffer (fb_index=1)
384
+ blue_colors = [Colors.BLUE] * 64
385
+ await matrix.set64(
386
+ tile_index=0,
387
+ length=1,
388
+ x=0,
389
+ y=0,
390
+ width=tile.width,
391
+ duration=0,
392
+ colors=blue_colors,
393
+ fb_index=1,
394
+ )
395
+
396
+ # Copy with explicit length=1 (same as default behavior)
397
+ await matrix.copy_frame_buffer(
398
+ tile_index=0, source_fb=1, target_fb=0, length=1
399
+ )
400
+
401
+ # Verify the copy worked
402
+ copied_colors = await matrix.get64()
403
+ assert copied_colors[0].hue == 240 # Blue
404
+ assert copied_colors[0].saturation == 1.0
405
+ assert copied_colors[0].brightness == 1.0
406
+
376
407
  async def test_set_effect_without_palette(self, emulator_devices) -> None:
377
408
  """Test setting effect without a palette (palette_count=0)."""
378
409
  matrix = emulator_devices[6]
@@ -217,6 +217,7 @@ class TestHevLightStateManagement:
217
217
  assert hev_light._state is None
218
218
  await hev_light.refresh_state() # type: ignore
219
219
  assert isinstance(hev_light.state, HevLightState)
220
+ await hev_light.close()
220
221
 
221
222
 
222
223
  class TestHevAcknowledgementBasedStateUpdates:
@@ -149,6 +149,7 @@ class TestInfraredLightStateManagement:
149
149
  assert infrared_light._state is None
150
150
  await infrared_light.refresh_state() # type: ignore
151
151
  assert isinstance(infrared_light.state, InfraredLightState)
152
+ await infrared_light.close()
152
153
 
153
154
 
154
155
  class TestInfraredAcknowledgementBasedStateUpdates:
@@ -337,7 +337,7 @@ wheels = [
337
337
 
338
338
  [[package]]
339
339
  name = "lifx-async"
340
- version = "4.7.0"
340
+ version = "4.7.2"
341
341
  source = { editable = "." }
342
342
 
343
343
  [package.dev-dependencies]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes