lifx-async 4.4.1__tar.gz → 4.5.1__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 (155) hide show
  1. {lifx_async-4.4.1 → lifx_async-4.5.1}/.github/workflows/ci.yml +8 -8
  2. {lifx_async-4.4.1 → lifx_async-4.5.1}/.github/workflows/docs.yml +6 -6
  3. {lifx_async-4.4.1 → lifx_async-4.5.1}/PKG-INFO +1 -1
  4. lifx_async-4.5.1/context7.json +4 -0
  5. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/api/devices.md +44 -0
  6. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/changelog.md +16 -0
  7. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/user-guide/advanced-usage.md +6 -0
  8. lifx_async-4.5.1/docs/user-guide/ceiling-lights.md +293 -0
  9. {lifx_async-4.4.1 → lifx_async-4.5.1}/mkdocs.yml +2 -0
  10. {lifx_async-4.4.1 → lifx_async-4.5.1}/pyproject.toml +1 -1
  11. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/__init__.py +2 -0
  12. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/devices/__init__.py +2 -0
  13. lifx_async-4.5.1/src/lifx/devices/ceiling.py +784 -0
  14. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/devices/matrix.py +27 -7
  15. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/network/discovery.py +10 -2
  16. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/products/__init__.py +8 -0
  17. lifx_async-4.5.1/src/lifx/products/quirks.py +91 -0
  18. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/conftest.py +5 -3
  19. lifx_async-4.5.1/tests/test_devices/test_ceiling.py +1648 -0
  20. {lifx_async-4.4.1 → lifx_async-4.5.1}/uv.lock +178 -184
  21. {lifx_async-4.4.1 → lifx_async-4.5.1}/.claude/settings.json +0 -0
  22. {lifx_async-4.4.1 → lifx_async-4.5.1}/.github/dependabot.yml +0 -0
  23. {lifx_async-4.4.1 → lifx_async-4.5.1}/.github/labeler.yml +0 -0
  24. {lifx_async-4.4.1 → lifx_async-4.5.1}/.github/workflows/pr-automation.yml +0 -0
  25. {lifx_async-4.4.1 → lifx_async-4.5.1}/.gitignore +0 -0
  26. {lifx_async-4.4.1 → lifx_async-4.5.1}/.pre-commit-config.yaml +0 -0
  27. {lifx_async-4.4.1 → lifx_async-4.5.1}/CLAUDE.md +0 -0
  28. {lifx_async-4.4.1 → lifx_async-4.5.1}/LICENSE +0 -0
  29. {lifx_async-4.4.1 → lifx_async-4.5.1}/README.md +0 -0
  30. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/api/colors.md +0 -0
  31. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/api/effects.md +0 -0
  32. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/api/exceptions.md +0 -0
  33. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/api/high-level.md +0 -0
  34. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/api/index.md +0 -0
  35. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/api/network.md +0 -0
  36. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/api/protocol.md +0 -0
  37. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/api/themes.md +0 -0
  38. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/architecture/effects-architecture.md +0 -0
  39. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/architecture/overview.md +0 -0
  40. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/faq.md +0 -0
  41. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/getting-started/effects.md +0 -0
  42. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/getting-started/installation.md +0 -0
  43. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/getting-started/quickstart.md +0 -0
  44. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/getting-started/themes.md +0 -0
  45. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/index.md +0 -0
  46. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/migration/effect-api-changes.md +0 -0
  47. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/stylesheets/extra.css +0 -0
  48. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/user-guide/effects-custom.md +0 -0
  49. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/user-guide/effects-troubleshooting.md +0 -0
  50. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/user-guide/protocol-deep-dive.md +0 -0
  51. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/user-guide/themes.md +0 -0
  52. {lifx_async-4.4.1 → lifx_async-4.5.1}/docs/user-guide/troubleshooting.md +0 -0
  53. {lifx_async-4.4.1 → lifx_async-4.5.1}/examples/01_simple_discovery.py +0 -0
  54. {lifx_async-4.4.1 → lifx_async-4.5.1}/examples/02_simple_control.py +0 -0
  55. {lifx_async-4.4.1 → lifx_async-4.5.1}/examples/03_waveforms.py +0 -0
  56. {lifx_async-4.4.1 → lifx_async-4.5.1}/examples/04_logging.py +0 -0
  57. {lifx_async-4.4.1 → lifx_async-4.5.1}/examples/06_pulse_effect.py +0 -0
  58. {lifx_async-4.4.1 → lifx_async-4.5.1}/examples/07_colorloop_effect.py +0 -0
  59. {lifx_async-4.4.1 → lifx_async-4.5.1}/examples/08_custom_effect.py +0 -0
  60. {lifx_async-4.4.1 → lifx_async-4.5.1}/examples/09_background_effect.py +0 -0
  61. {lifx_async-4.4.1 → lifx_async-4.5.1}/examples/10_find_specific_devices.py +0 -0
  62. {lifx_async-4.4.1 → lifx_async-4.5.1}/examples/11_matrix_basic.py +0 -0
  63. {lifx_async-4.4.1 → lifx_async-4.5.1}/examples/12_matrix_effects.py +0 -0
  64. {lifx_async-4.4.1 → lifx_async-4.5.1}/examples/13_matrix_large.py +0 -0
  65. {lifx_async-4.4.1 → lifx_async-4.5.1}/renovate.json +0 -0
  66. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/api.py +0 -0
  67. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/color.py +0 -0
  68. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/const.py +0 -0
  69. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/devices/base.py +0 -0
  70. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/devices/hev.py +0 -0
  71. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/devices/infrared.py +0 -0
  72. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/devices/light.py +0 -0
  73. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/devices/multizone.py +0 -0
  74. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/effects/__init__.py +0 -0
  75. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/effects/base.py +0 -0
  76. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/effects/colorloop.py +0 -0
  77. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/effects/conductor.py +0 -0
  78. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/effects/const.py +0 -0
  79. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/effects/models.py +0 -0
  80. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/effects/pulse.py +0 -0
  81. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/effects/state_manager.py +0 -0
  82. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/exceptions.py +0 -0
  83. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/network/__init__.py +0 -0
  84. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/network/connection.py +0 -0
  85. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/network/message.py +0 -0
  86. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/network/transport.py +0 -0
  87. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/products/generator.py +0 -0
  88. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/products/registry.py +0 -0
  89. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/protocol/__init__.py +0 -0
  90. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/protocol/base.py +0 -0
  91. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/protocol/generator.py +0 -0
  92. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/protocol/header.py +0 -0
  93. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/protocol/models.py +0 -0
  94. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/protocol/packets.py +0 -0
  95. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/protocol/protocol_types.py +0 -0
  96. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/protocol/serializer.py +0 -0
  97. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/py.typed +0 -0
  98. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/theme/__init__.py +0 -0
  99. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/theme/canvas.py +0 -0
  100. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/theme/generators.py +0 -0
  101. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/theme/library.py +0 -0
  102. {lifx_async-4.4.1 → lifx_async-4.5.1}/src/lifx/theme/theme.py +0 -0
  103. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/__init__.py +0 -0
  104. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_api/__init__.py +0 -0
  105. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_api/test_api_apply_theme.py +0 -0
  106. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_api/test_api_batch_errors.py +0 -0
  107. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_api/test_api_batch_operations.py +0 -0
  108. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_api/test_api_discovery.py +0 -0
  109. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_api/test_api_organization.py +0 -0
  110. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_color.py +0 -0
  111. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/__init__.py +0 -0
  112. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/conftest.py +0 -0
  113. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/test_base.py +0 -0
  114. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/test_hev.py +0 -0
  115. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/test_infrared.py +0 -0
  116. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/test_light.py +0 -0
  117. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/test_mac_address.py +0 -0
  118. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/test_matrix.py +0 -0
  119. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/test_multizone.py +0 -0
  120. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/test_state_hev.py +0 -0
  121. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/test_state_infrared.py +0 -0
  122. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/test_state_light.py +0 -0
  123. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/test_state_management.py +0 -0
  124. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/test_state_matrix.py +0 -0
  125. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_devices/test_state_multizone.py +0 -0
  126. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_effects/__init__.py +0 -0
  127. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_effects/test_base.py +0 -0
  128. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_effects/test_capability_filtering.py +0 -0
  129. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_effects/test_colorloop.py +0 -0
  130. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_effects/test_integration.py +0 -0
  131. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_effects/test_models.py +0 -0
  132. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_effects/test_pulse.py +0 -0
  133. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_effects/test_state_manager.py +0 -0
  134. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_network/__init__.py +0 -0
  135. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_network/test_concurrent_requests.py +0 -0
  136. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_network/test_connection.py +0 -0
  137. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_network/test_discovery_devices.py +0 -0
  138. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_network/test_discovery_errors.py +0 -0
  139. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_network/test_message.py +0 -0
  140. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_network/test_message_advanced.py +0 -0
  141. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_network/test_transport.py +0 -0
  142. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_products/test_product_generator.py +0 -0
  143. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_products/test_registry.py +0 -0
  144. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_protocol/test_generated.py +0 -0
  145. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_protocol/test_header.py +0 -0
  146. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_protocol/test_protocol_generator.py +0 -0
  147. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_protocol/test_serializer.py +0 -0
  148. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_theme/__init__.py +0 -0
  149. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_theme/conftest.py +0 -0
  150. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_theme/test_apply_theme.py +0 -0
  151. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_theme/test_canvas.py +0 -0
  152. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_theme/test_generators.py +0 -0
  153. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_theme/test_library.py +0 -0
  154. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_theme/test_theme.py +0 -0
  155. {lifx_async-4.4.1 → lifx_async-4.5.1}/tests/test_utils.py +0 -0
@@ -32,7 +32,7 @@ jobs:
32
32
  name: Code Quality
33
33
  runs-on: ubuntu-latest
34
34
  steps:
35
- - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
35
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
36
36
 
37
37
  - name: Set up Python
38
38
  uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
@@ -40,7 +40,7 @@ jobs:
40
40
  python-version: ${{ env.PYTHON_VERSION }}
41
41
 
42
42
  - name: Install uv
43
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7
43
+ uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7
44
44
  with:
45
45
  version: ${{ env.UV_VERSION }}
46
46
  python-version: ${{ env.PYTHON_VERSION }}
@@ -74,7 +74,7 @@ jobs:
74
74
  os: [ubuntu-latest, macos-latest, windows-latest]
75
75
  python-version: ['3.11', '3.12', '3.13', '3.14']
76
76
  steps:
77
- - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
77
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
78
78
 
79
79
  - name: Set up Python ${{ matrix.python-version }}
80
80
  uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
@@ -82,7 +82,7 @@ jobs:
82
82
  python-version: ${{ matrix.python-version }}
83
83
 
84
84
  - name: Install uv
85
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7
85
+ uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7
86
86
  with:
87
87
  version: ${{ env.UV_VERSION }}
88
88
  python-version: ${{ matrix.python-version }}
@@ -126,7 +126,7 @@ jobs:
126
126
  if: github.event_name == 'push' && github.ref_name == 'main'
127
127
  runs-on: ubuntu-latest
128
128
  steps:
129
- - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
129
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
130
130
 
131
131
  - name: Set up Python
132
132
  uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
@@ -134,7 +134,7 @@ jobs:
134
134
  python-version: ${{ env.PYTHON_VERSION }}
135
135
 
136
136
  - name: Install uv
137
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7
137
+ uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7
138
138
  with:
139
139
  version: ${{ env.UV_VERSION }}
140
140
  python-version: ${{ env.PYTHON_VERSION }}
@@ -186,7 +186,7 @@ jobs:
186
186
 
187
187
  steps:
188
188
  - name: Checkout repository on release branch
189
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
189
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
190
190
  with:
191
191
  ref: ${{ github.head_ref || github.ref_name }}
192
192
  fetch-depth: 0
@@ -198,7 +198,7 @@ jobs:
198
198
  python-version: ${{ env.PYTHON_VERSION }}
199
199
 
200
200
  - name: Install uv
201
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7
201
+ uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7
202
202
  with:
203
203
  version: ${{ env.UV_VERSION }}
204
204
  python-version: ${{ env.PYTHON_VERSION }}
@@ -25,7 +25,7 @@ jobs:
25
25
  build-docs:
26
26
  runs-on: ubuntu-latest
27
27
  steps:
28
- - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
28
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
29
29
  with:
30
30
  fetch-depth: 0 # Fetch all history for git-revision-date-localized
31
31
 
@@ -35,7 +35,7 @@ jobs:
35
35
  python-version: ${{ env.PYTHON_VERSION }}
36
36
 
37
37
  - name: Install uv
38
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7
38
+ uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7
39
39
  with:
40
40
  version: ${{ env.UV_VERSION }}
41
41
  python-version: ${{ env.PYTHON_VERSION }}
@@ -59,7 +59,7 @@ jobs:
59
59
  if: github.ref == 'refs/heads/main' && github.event_name == 'push'
60
60
  needs: build-docs
61
61
  steps:
62
- - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
62
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
63
63
  with:
64
64
  fetch-depth: 0
65
65
 
@@ -69,7 +69,7 @@ jobs:
69
69
  python-version: ${{ env.PYTHON_VERSION }}
70
70
 
71
71
  - name: Install uv
72
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7
72
+ uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7
73
73
  with:
74
74
  version: ${{ env.UV_VERSION }}
75
75
  python-version: ${{ env.PYTHON_VERSION }}
@@ -89,7 +89,7 @@ jobs:
89
89
  validate-links:
90
90
  runs-on: ubuntu-latest
91
91
  steps:
92
- - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
92
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
93
93
 
94
94
  - name: Set up Python
95
95
  uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6
@@ -97,7 +97,7 @@ jobs:
97
97
  python-version: ${{ env.PYTHON_VERSION }}
98
98
 
99
99
  - name: Install uv
100
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7
100
+ uses: astral-sh/setup-uv@ed21f2f24f8dd64503750218de024bcf64c7250a # v7
101
101
  with:
102
102
  version: ${{ env.UV_VERSION }}
103
103
  python-version: ${{ env.PYTHON_VERSION }}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lifx-async
3
- Version: 4.4.1
3
+ Version: 4.5.1
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>
@@ -0,0 +1,4 @@
1
+ {
2
+ "url": "https://context7.com/djelibeybi/lifx-async",
3
+ "public_key": "pk_dKDzLMm361DFRMYHHIKSR"
4
+ }
@@ -81,6 +81,19 @@ The `MatrixLight` class controls LIFX matrix devices (tiles, candle, path) with
81
81
  filters:
82
82
  - "!^_"
83
83
 
84
+ ## Ceiling Light
85
+
86
+ The `CeilingLight` class extends `MatrixLight` with independent control over uplight and downlight components for LIFX Ceiling fixtures.
87
+
88
+ ::: lifx.devices.ceiling.CeilingLight
89
+ options:
90
+ show_root_heading: true
91
+ heading_level: 3
92
+ members_order: source
93
+ show_if_no_docstring: false
94
+ filters:
95
+ - "!^_"
96
+
84
97
  ## Device Properties
85
98
 
86
99
  ### MAC Address
@@ -268,3 +281,34 @@ async def main():
268
281
  # Stop the effect
269
282
  await light.set_effect(effect_type=FirmwareEffect.OFF)
270
283
  ```
284
+
285
+ ### Ceiling Light Control
286
+
287
+ ```python
288
+ from lifx import CeilingLight, HSBK
289
+
290
+
291
+ async def main():
292
+ async with await CeilingLight.from_ip("192.168.1.100") as ceiling:
293
+ # Set downlight to warm white
294
+ await ceiling.set_downlight_colors(
295
+ HSBK(hue=0, saturation=0, brightness=0.8, kelvin=3000)
296
+ )
297
+
298
+ # Set uplight to a dim ambient glow
299
+ await ceiling.set_uplight_color(
300
+ HSBK(hue=30, saturation=0.2, brightness=0.3, kelvin=2700)
301
+ )
302
+
303
+ # Turn uplight off (stores color for later restoration)
304
+ await ceiling.turn_uplight_off()
305
+
306
+ # Turn uplight back on (restores previous color)
307
+ await ceiling.turn_uplight_on()
308
+
309
+ # Check component state
310
+ if ceiling.downlight_is_on:
311
+ print("Downlight is currently on")
312
+ ```
313
+
314
+ For detailed CeilingLight usage, see the [Ceiling Lights User Guide](../user-guide/ceiling-lights.md).
@@ -2,6 +2,22 @@
2
2
 
3
3
  <!-- version list -->
4
4
 
5
+ ## v4.5.1 (2025-12-11)
6
+
7
+ ### Bug Fixes
8
+
9
+ - **devices**: Export CeilingLight add add user guide and API documentation
10
+ ([`10e0089`](https://github.com/Djelibeybi/lifx-async/commit/10e008983ffd8b233dd2427a4a4f64661c8f14bd))
11
+
12
+
13
+ ## v4.5.0 (2025-12-08)
14
+
15
+ ### Features
16
+
17
+ - **devices**: Add CeilingLight with independent uplight/downlight component control
18
+ ([`95fc5a6`](https://github.com/Djelibeybi/lifx-async/commit/95fc5a68c598232f5c710ad5d67f3647ba89d720))
19
+
20
+
5
21
  ## v4.4.1 (2025-12-03)
6
22
 
7
23
  ### Bug Fixes
@@ -119,6 +119,12 @@ async def use_cached_or_fetch():
119
119
  - `MatrixLight.device_chain` - Details of each tile on the chain
120
120
  - `MatrixLight.tile_effect` - Either MORPH, FLAME, SKY or OFF
121
121
 
122
+ #### CeilingLight properties:
123
+
124
+ - `CeilingLight.uplight_zone` - Zone index of the uplight component
125
+ - `CeilingLight.downlight_zones` - Slice representing downlight zones
126
+ - `CeilingLight.uplight_is_on` - True if uplight has brightness > 0 (requires recent data)
127
+ - `CeilingLight.downlight_is_on` - True if any downlight zone has brightness > 0 (requires recent data)
122
128
 
123
129
  **Note**: Volatile state properties (power, color, hev_cycle, zones, tile_colors) have been removed. Always use `get_*()` methods to fetch these values from devices as they change too frequently to benefit from caching.
124
130
 
@@ -0,0 +1,293 @@
1
+ # Ceiling Lights
2
+
3
+ LIFX Ceiling lights are unique fixtures that combine two lighting components in one device:
4
+
5
+ - **Downlight**: Main illumination with multiple addressable zones (63 or 127 zones)
6
+ - **Uplight**: Ambient/indirect lighting via a single zone
7
+
8
+ The `CeilingLight` class provides high-level control over these components while inheriting full matrix functionality from `MatrixLight`.
9
+
10
+ ## Supported Devices
11
+
12
+ | Product | Zones | Layout |
13
+ |---------|-------|--------|
14
+ | LIFX Ceiling (US/Intl) | 64 | 8x8 grid, zone 63 = uplight |
15
+ | LIFX Ceiling Capsule (US/Intl) | 128 | 16x8 grid, zone 127 = uplight |
16
+
17
+ ## Quick Start
18
+
19
+ ```python
20
+ from lifx import CeilingLight
21
+ from lifx.color import HSBK
22
+
23
+ async def main():
24
+ async with await CeilingLight.from_ip("192.168.1.100") as ceiling:
25
+ # Set downlight to warm white
26
+ await ceiling.set_downlight_colors(
27
+ HSBK(hue=0, saturation=0, brightness=1.0, kelvin=3000)
28
+ )
29
+
30
+ # Set uplight to a dim, warm ambient glow
31
+ await ceiling.set_uplight_color(
32
+ HSBK(hue=30, saturation=0.2, brightness=0.3, kelvin=2700)
33
+ )
34
+ ```
35
+
36
+ ## Component Control
37
+
38
+ ### Setting Colors
39
+
40
+ #### Downlight
41
+
42
+ Set all downlight zones to the same color:
43
+
44
+ ```python
45
+ # Single color for all zones
46
+ await ceiling.set_downlight_colors(
47
+ HSBK(hue=0, saturation=0, brightness=0.8, kelvin=4000)
48
+ )
49
+ ```
50
+
51
+ Or set each zone individually:
52
+
53
+ ```python
54
+ # Create a gradient across all zones
55
+ zone_count = len(range(*ceiling.downlight_zones.indices(256)))
56
+ colors = [
57
+ HSBK(hue=(i * 360 / zone_count), saturation=1.0, brightness=0.5, kelvin=3500)
58
+ for i in range(zone_count)
59
+ ]
60
+ await ceiling.set_downlight_colors(colors)
61
+ ```
62
+
63
+ #### Uplight
64
+
65
+ ```python
66
+ await ceiling.set_uplight_color(
67
+ HSBK(hue=30, saturation=0.1, brightness=0.4, kelvin=2700)
68
+ )
69
+ ```
70
+
71
+ ### Reading Current Colors
72
+
73
+ ```python
74
+ # Get current uplight color
75
+ uplight_color = await ceiling.get_uplight_color()
76
+ print(f"Uplight: H={uplight_color.hue}, B={uplight_color.brightness}")
77
+
78
+ # Get all downlight colors
79
+ downlight_colors = await ceiling.get_downlight_colors()
80
+ print(f"Downlight zones: {len(downlight_colors)}")
81
+ ```
82
+
83
+ ### Turning Components On/Off
84
+
85
+ The `turn_*_on()` and `turn_*_off()` methods provide smart state management:
86
+
87
+ ```python
88
+ # Turn off uplight (stores current color for later restoration)
89
+ await ceiling.turn_uplight_off()
90
+
91
+ # Turn uplight back on (restores previous color)
92
+ await ceiling.turn_uplight_on()
93
+
94
+ # Turn on with a specific color
95
+ await ceiling.turn_uplight_on(
96
+ color=HSBK(hue=0, saturation=0, brightness=1.0, kelvin=3500)
97
+ )
98
+ ```
99
+
100
+ The same pattern works for downlights:
101
+
102
+ ```python
103
+ # Turn off downlight
104
+ await ceiling.turn_downlight_off()
105
+
106
+ # Turn downlight back on
107
+ await ceiling.turn_downlight_on()
108
+
109
+ # Turn on with specific colors
110
+ await ceiling.turn_downlight_on(
111
+ colors=HSBK(hue=0, saturation=0, brightness=0.8, kelvin=4000)
112
+ )
113
+ ```
114
+
115
+ ### Checking Component State
116
+
117
+ ```python
118
+ # Check if components are on
119
+ if ceiling.uplight_is_on:
120
+ print("Uplight is on")
121
+
122
+ if ceiling.downlight_is_on:
123
+ print("Downlight is on")
124
+ ```
125
+
126
+ !!! note "State Properties Require Recent Data"
127
+ The `uplight_is_on` and `downlight_is_on` properties rely on cached data.
128
+ Call `get_uplight_color()` or `get_downlight_colors()` first to ensure
129
+ accurate state.
130
+
131
+ ## Zone Layout
132
+
133
+ Access the component zone indices directly:
134
+
135
+ ```python
136
+ async with await CeilingLight.from_ip("192.168.1.100") as ceiling:
137
+ # Get uplight zone index (63 or 127 depending on model)
138
+ uplight_idx = ceiling.uplight_zone
139
+ print(f"Uplight zone: {uplight_idx}")
140
+
141
+ # Get downlight zones as a slice
142
+ downlight_slice = ceiling.downlight_zones
143
+ print(f"Downlight zones: {downlight_slice}") # slice(0, 63) or slice(0, 127)
144
+
145
+ # Calculate number of downlight zones
146
+ zone_count = len(range(*downlight_slice.indices(256)))
147
+ print(f"Number of downlight zones: {zone_count}")
148
+ ```
149
+
150
+ ## State Persistence
151
+
152
+ CeilingLight supports optional state persistence to preserve component colors across sessions:
153
+
154
+ ```python
155
+ async with await CeilingLight.from_ip(
156
+ "192.168.1.100",
157
+ state_file="~/.lifx/ceiling_state.json"
158
+ ) as ceiling:
159
+ # Colors are automatically loaded from file on connection
160
+ # and saved when using turn_*_off() methods
161
+
162
+ await ceiling.turn_uplight_off() # Saves current color to file
163
+ # ... later ...
164
+ await ceiling.turn_uplight_on() # Restores from file if available
165
+ ```
166
+
167
+ The state file stores colors per device serial number, supporting multiple devices:
168
+
169
+ ```json
170
+ {
171
+ "d073d5123456": {
172
+ "uplight": {
173
+ "hue": 30.0,
174
+ "saturation": 0.2,
175
+ "brightness": 0.4,
176
+ "kelvin": 2700
177
+ },
178
+ "downlight": [
179
+ {"hue": 0.0, "saturation": 0.0, "brightness": 0.8, "kelvin": 4000}
180
+ ]
181
+ }
182
+ }
183
+ ```
184
+
185
+ ## Brightness Determination
186
+
187
+ When calling `turn_uplight_on()` or `turn_downlight_on()` without a color parameter, CeilingLight uses the following priority to determine brightness:
188
+
189
+ 1. **Stored state**: If a color was previously saved (via `turn_*_off()` or `set_*_color()`)
190
+ 2. **Infer from other component**: Average brightness of the other component
191
+ 3. **Default**: 80% brightness
192
+
193
+ This ensures a reasonable brightness level even when no state is available.
194
+
195
+ ## Transition Duration
196
+
197
+ All color-setting methods support smooth transitions:
198
+
199
+ ```python
200
+ # 2-second transition to new color
201
+ await ceiling.set_uplight_color(
202
+ HSBK(hue=0, saturation=0, brightness=1.0, kelvin=3500),
203
+ duration=2.0 # seconds
204
+ )
205
+
206
+ # Instant change (default)
207
+ await ceiling.set_downlight_colors(
208
+ HSBK(hue=240, saturation=1.0, brightness=0.5, kelvin=3500),
209
+ duration=0.0
210
+ )
211
+ ```
212
+
213
+ ## MatrixLight Compatibility
214
+
215
+ CeilingLight extends `MatrixLight`, so all matrix operations are available:
216
+
217
+ ```python
218
+ async with await CeilingLight.from_ip("192.168.1.100") as ceiling:
219
+ # Use MatrixLight methods directly
220
+ all_colors = await ceiling.get_all_tile_colors()
221
+ tile_chain = await ceiling.get_tile_chain()
222
+
223
+ # Set raw matrix colors (bypasses component abstraction)
224
+ await ceiling.set_matrix_colors(0, colors)
225
+
226
+ # Apply effects
227
+ from lifx.protocol.protocol_types import TileEffectType
228
+ await ceiling.set_tile_effect(
229
+ effect_type=TileEffectType.MORPH,
230
+ speed=5000,
231
+ )
232
+ ```
233
+
234
+ ## Example: Night Mode
235
+
236
+ Create a subtle night light with dim uplight and downlight off:
237
+
238
+ ```python
239
+ from lifx import CeilingLight
240
+ from lifx.color import HSBK
241
+
242
+ async def night_mode(ip: str):
243
+ async with await CeilingLight.from_ip(ip) as ceiling:
244
+ # Store current colors before turning off
245
+ await ceiling.turn_downlight_off()
246
+
247
+ # Set uplight to very dim warm glow
248
+ await ceiling.set_uplight_color(
249
+ HSBK(hue=30, saturation=0.3, brightness=0.05, kelvin=2200),
250
+ duration=2.0
251
+ )
252
+ ```
253
+
254
+ ## Example: Daytime Productivity
255
+
256
+ Bright, cool white for focus:
257
+
258
+ ```python
259
+ async def daytime_mode(ip: str):
260
+ async with await CeilingLight.from_ip(ip) as ceiling:
261
+ # Bright cool downlight for task lighting
262
+ await ceiling.set_downlight_colors(
263
+ HSBK(hue=0, saturation=0, brightness=1.0, kelvin=5500),
264
+ duration=1.0
265
+ )
266
+
267
+ # Turn off uplight during the day
268
+ await ceiling.turn_uplight_off(duration=1.0)
269
+ ```
270
+
271
+ ## Example: Evening Ambiance
272
+
273
+ Warm tones with accent uplight:
274
+
275
+ ```python
276
+ async def evening_mode(ip: str):
277
+ async with await CeilingLight.from_ip(ip) as ceiling:
278
+ # Dimmed warm downlight
279
+ await ceiling.set_downlight_colors(
280
+ HSBK(hue=30, saturation=0.1, brightness=0.4, kelvin=2700),
281
+ duration=2.0
282
+ )
283
+
284
+ # Colorful uplight accent
285
+ await ceiling.set_uplight_color(
286
+ HSBK(hue=280, saturation=0.6, brightness=0.3, kelvin=3500),
287
+ duration=2.0
288
+ )
289
+ ```
290
+
291
+ ## API Reference
292
+
293
+ See [CeilingLight API Reference](../api/devices.md#ceiling-light) for complete method documentation.
@@ -130,6 +130,7 @@ plugins:
130
130
  - getting-started/effects.md: Light effects and animations
131
131
  User Guide:
132
132
  - user-guide/themes.md: Working with color themes
133
+ - user-guide/ceiling-lights.md: Controlling LIFX Ceiling uplight and downlight components
133
134
  - user-guide/advanced-usage.md: Advanced usage patterns and best practices
134
135
  - user-guide/effects-custom.md: Creating custom light effects
135
136
  - user-guide/troubleshooting.md: Common issues and solutions
@@ -199,6 +200,7 @@ nav:
199
200
  - Light Effects: getting-started/effects.md
200
201
  - User Guide:
201
202
  - Themes: user-guide/themes.md
203
+ - Ceiling Lights: user-guide/ceiling-lights.md
202
204
  - Advanced Usage: user-guide/advanced-usage.md
203
205
  - Custom Effects: user-guide/effects-custom.md
204
206
  - Troubleshooting: user-guide/troubleshooting.md
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lifx-async"
3
- version = "4.4.1"
3
+ version = "4.5.1"
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"
@@ -16,6 +16,7 @@ from lifx.api import (
16
16
  )
17
17
  from lifx.color import HSBK, Colors
18
18
  from lifx.devices import (
19
+ CeilingLight,
19
20
  Device,
20
21
  DeviceInfo,
21
22
  DeviceVersion,
@@ -71,6 +72,7 @@ __all__ = [
71
72
  "MultiZoneLightState",
72
73
  "MatrixLight",
73
74
  "MatrixLightState",
75
+ "CeilingLight",
74
76
  # Color
75
77
  "HSBK",
76
78
  "Colors",
@@ -10,6 +10,7 @@ from lifx.devices.base import (
10
10
  FirmwareInfo,
11
11
  WifiInfo,
12
12
  )
13
+ from lifx.devices.ceiling import CeilingLight
13
14
  from lifx.devices.hev import HevLight, HevLightState
14
15
  from lifx.devices.infrared import InfraredLight, InfraredLightState
15
16
  from lifx.devices.light import Light, LightState
@@ -17,6 +18,7 @@ from lifx.devices.matrix import MatrixEffect, MatrixLight, MatrixLightState, Til
17
18
  from lifx.devices.multizone import MultiZoneEffect, MultiZoneLight, MultiZoneLightState
18
19
 
19
20
  __all__ = [
21
+ "CeilingLight",
20
22
  "CollectionInfo",
21
23
  "Device",
22
24
  "DeviceInfo",