lifx-async 4.3.6__tar.gz → 4.3.8__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.6 → lifx_async-4.3.8}/.github/workflows/ci.yml +10 -10
- {lifx_async-4.3.6 → lifx_async-4.3.8}/.github/workflows/docs.yml +6 -6
- {lifx_async-4.3.6 → lifx_async-4.3.8}/PKG-INFO +1 -1
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/changelog.md +16 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/pyproject.toml +1 -1
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/devices/base.py +49 -6
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/devices/hev.py +12 -2
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/devices/infrared.py +5 -1
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/devices/light.py +18 -4
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/devices/matrix.py +18 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/devices/multizone.py +19 -5
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/network/connection.py +4 -2
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_devices/test_light.py +64 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_network/test_connection.py +7 -8
- {lifx_async-4.3.6 → lifx_async-4.3.8}/uv.lock +1 -1
- {lifx_async-4.3.6 → lifx_async-4.3.8}/.claude/settings.json +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/.github/dependabot.yml +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/.github/labeler.yml +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/.github/workflows/pr-automation.yml +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/.gitignore +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/.pre-commit-config.yaml +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/CLAUDE.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/LICENSE +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/README.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/api/colors.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/api/devices.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/api/effects.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/api/exceptions.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/api/high-level.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/api/index.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/api/network.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/api/protocol.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/api/themes.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/architecture/effects-architecture.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/architecture/overview.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/faq.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/getting-started/effects.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/getting-started/installation.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/getting-started/quickstart.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/getting-started/themes.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/index.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/migration/effect-api-changes.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/stylesheets/extra.css +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/user-guide/advanced-usage.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/user-guide/effects-custom.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/user-guide/effects-troubleshooting.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/user-guide/protocol-deep-dive.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/user-guide/themes.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/docs/user-guide/troubleshooting.md +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/examples/01_simple_discovery.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/examples/02_simple_control.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/examples/03_waveforms.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/examples/04_logging.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/examples/06_pulse_effect.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/examples/07_colorloop_effect.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/examples/08_custom_effect.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/examples/09_background_effect.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/examples/10_find_specific_devices.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/examples/11_matrix_basic.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/examples/12_matrix_effects.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/examples/13_matrix_large.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/mkdocs.yml +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/renovate.json +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/__init__.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/api.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/color.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/const.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/devices/__init__.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/effects/__init__.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/effects/base.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/effects/colorloop.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/effects/conductor.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/effects/const.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/effects/models.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/effects/pulse.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/effects/state_manager.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/exceptions.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/network/__init__.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/network/discovery.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/network/message.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/network/transport.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/products/__init__.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/products/generator.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/products/registry.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/protocol/__init__.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/protocol/base.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/protocol/generator.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/protocol/header.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/protocol/models.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/protocol/packets.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/protocol/protocol_types.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/protocol/serializer.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/py.typed +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/theme/__init__.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/theme/canvas.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/theme/generators.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/theme/library.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/src/lifx/theme/theme.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/__init__.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/conftest.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_api/__init__.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_api/test_api_apply_theme.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_api/test_api_batch_errors.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_api/test_api_batch_operations.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_api/test_api_discovery.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_api/test_api_organization.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_color.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_devices/__init__.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_devices/conftest.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_devices/test_base.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_devices/test_hev.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_devices/test_infrared.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_devices/test_mac_address.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_devices/test_matrix.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_devices/test_multizone.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_effects/__init__.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_effects/test_base.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_effects/test_capability_filtering.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_effects/test_colorloop.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_effects/test_integration.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_effects/test_models.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_effects/test_pulse.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_effects/test_state_manager.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_network/__init__.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_network/test_concurrent_requests.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_network/test_discovery_devices.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_network/test_discovery_errors.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_network/test_message.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_network/test_message_advanced.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_network/test_transport.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_products/test_product_generator.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_products/test_registry.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_protocol/test_generated.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_protocol/test_header.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_protocol/test_protocol_generator.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_protocol/test_serializer.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_theme/__init__.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_theme/conftest.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_theme/test_apply_theme.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_theme/test_canvas.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_theme/test_generators.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_theme/test_library.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/tests/test_theme/test_theme.py +0 -0
- {lifx_async-4.3.6 → lifx_async-4.3.8}/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@
|
|
35
|
+
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
|
36
36
|
|
|
37
37
|
- name: Set up Python
|
|
38
38
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # 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@
|
|
43
|
+
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # 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@
|
|
77
|
+
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
|
78
78
|
|
|
79
79
|
- name: Set up Python ${{ matrix.python-version }}
|
|
80
80
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # 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@
|
|
85
|
+
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7
|
|
86
86
|
with:
|
|
87
87
|
version: ${{ env.UV_VERSION }}
|
|
88
88
|
python-version: ${{ matrix.python-version }}
|
|
@@ -95,7 +95,7 @@ jobs:
|
|
|
95
95
|
run: uv run --frozen pytest
|
|
96
96
|
|
|
97
97
|
- name: Upload coverage to Codecov
|
|
98
|
-
uses: codecov/codecov-action@v5
|
|
98
|
+
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
|
|
99
99
|
with:
|
|
100
100
|
env_vars: OS,PYTHON
|
|
101
101
|
fail_ci_if_error: false
|
|
@@ -105,7 +105,7 @@ jobs:
|
|
|
105
105
|
verbose: true
|
|
106
106
|
|
|
107
107
|
- name: Upload test results to Codecov
|
|
108
|
-
uses: codecov/codecov-action@v5
|
|
108
|
+
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
|
|
109
109
|
with:
|
|
110
110
|
env_vars: OS,PYTHON
|
|
111
111
|
fail_ci_if_error: false
|
|
@@ -124,7 +124,7 @@ jobs:
|
|
|
124
124
|
if: github.event_name == 'push' && github.ref_name == 'main'
|
|
125
125
|
runs-on: ubuntu-latest
|
|
126
126
|
steps:
|
|
127
|
-
- uses: actions/checkout@
|
|
127
|
+
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
|
128
128
|
|
|
129
129
|
- name: Set up Python
|
|
130
130
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
|
@@ -132,7 +132,7 @@ jobs:
|
|
|
132
132
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
133
133
|
|
|
134
134
|
- name: Install uv
|
|
135
|
-
uses: astral-sh/setup-uv@
|
|
135
|
+
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7
|
|
136
136
|
with:
|
|
137
137
|
version: ${{ env.UV_VERSION }}
|
|
138
138
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
@@ -184,7 +184,7 @@ jobs:
|
|
|
184
184
|
|
|
185
185
|
steps:
|
|
186
186
|
- name: Checkout repository on release branch
|
|
187
|
-
uses: actions/checkout@
|
|
187
|
+
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
|
188
188
|
with:
|
|
189
189
|
ref: ${{ github.head_ref || github.ref_name }}
|
|
190
190
|
fetch-depth: 0
|
|
@@ -196,7 +196,7 @@ jobs:
|
|
|
196
196
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
197
197
|
|
|
198
198
|
- name: Install uv
|
|
199
|
-
uses: astral-sh/setup-uv@
|
|
199
|
+
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7
|
|
200
200
|
with:
|
|
201
201
|
version: ${{ env.UV_VERSION }}
|
|
202
202
|
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@
|
|
28
|
+
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 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@
|
|
38
|
+
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # 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@
|
|
62
|
+
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # 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@
|
|
72
|
+
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # 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@
|
|
92
|
+
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6
|
|
93
93
|
|
|
94
94
|
- name: Set up Python
|
|
95
95
|
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # 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@
|
|
100
|
+
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7
|
|
101
101
|
with:
|
|
102
102
|
version: ${{ env.UV_VERSION }}
|
|
103
103
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- version list -->
|
|
4
4
|
|
|
5
|
+
## v4.3.8 (2025-11-25)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- **network**: Raise exception on StateUnhandled instead of returning False
|
|
10
|
+
([`5ca3e8a`](https://github.com/Djelibeybi/lifx-async/commit/5ca3e8abcde0ec0eefe77645aeb0a2e63b18418c))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## v4.3.7 (2025-11-25)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
- **devices**: Raise LifxUnsupportedCommandError on StateUnhandled responses
|
|
18
|
+
([`ec142cf`](https://github.com/Djelibeybi/lifx-async/commit/ec142cf0130847d65d4b9cd825575658936ef823))
|
|
19
|
+
|
|
20
|
+
|
|
5
21
|
## v4.3.6 (2025-11-25)
|
|
6
22
|
|
|
7
23
|
### Bug Fixes
|
|
@@ -19,7 +19,7 @@ from lifx.const import (
|
|
|
19
19
|
LIFX_LOCATION_NAMESPACE,
|
|
20
20
|
LIFX_UDP_PORT,
|
|
21
21
|
)
|
|
22
|
-
from lifx.exceptions import LifxDeviceNotFoundError
|
|
22
|
+
from lifx.exceptions import LifxDeviceNotFoundError, LifxUnsupportedCommandError
|
|
23
23
|
from lifx.network.connection import DeviceConnection
|
|
24
24
|
from lifx.products.registry import ProductInfo, get_product
|
|
25
25
|
from lifx.protocol import packets
|
|
@@ -152,6 +152,21 @@ class Device:
|
|
|
152
152
|
```
|
|
153
153
|
"""
|
|
154
154
|
|
|
155
|
+
@staticmethod
|
|
156
|
+
def _raise_if_unhandled(response: object) -> None:
|
|
157
|
+
"""Raise LifxUnsupportedCommandError if device doesn't support the command.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
response: The response from connection.request()
|
|
161
|
+
|
|
162
|
+
Raises:
|
|
163
|
+
LifxUnsupportedCommandError: If response is StateUnhandled or False
|
|
164
|
+
"""
|
|
165
|
+
if isinstance(response, packets.Device.StateUnhandled):
|
|
166
|
+
raise LifxUnsupportedCommandError(
|
|
167
|
+
f"Device does not support packet type {response.unhandled_type}"
|
|
168
|
+
)
|
|
169
|
+
|
|
155
170
|
def __init__(
|
|
156
171
|
self,
|
|
157
172
|
serial: str,
|
|
@@ -456,6 +471,7 @@ class Device:
|
|
|
456
471
|
LifxDeviceNotFoundError: If device is not connected
|
|
457
472
|
LifxTimeoutError: If device does not respond
|
|
458
473
|
LifxProtocolError: If response is invalid
|
|
474
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
459
475
|
|
|
460
476
|
Example:
|
|
461
477
|
```python
|
|
@@ -469,6 +485,7 @@ class Device:
|
|
|
469
485
|
"""
|
|
470
486
|
# Request automatically unpacks and decodes label
|
|
471
487
|
state = await self.connection.request(packets.Device.GetLabel())
|
|
488
|
+
self._raise_if_unhandled(state)
|
|
472
489
|
|
|
473
490
|
# Store label
|
|
474
491
|
self._label = state.label
|
|
@@ -492,6 +509,7 @@ class Device:
|
|
|
492
509
|
ValueError: If label is too long
|
|
493
510
|
LifxDeviceNotFoundError: If device is not connected
|
|
494
511
|
LifxTimeoutError: If device does not respond
|
|
512
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
495
513
|
|
|
496
514
|
Example:
|
|
497
515
|
```python
|
|
@@ -508,9 +526,10 @@ class Device:
|
|
|
508
526
|
label_bytes = label_bytes.ljust(32, b"\x00")
|
|
509
527
|
|
|
510
528
|
# Request automatically handles acknowledgement
|
|
511
|
-
await self.connection.request(
|
|
529
|
+
result = await self.connection.request(
|
|
512
530
|
packets.Device.SetLabel(label=label_bytes),
|
|
513
531
|
)
|
|
532
|
+
self._raise_if_unhandled(result)
|
|
514
533
|
|
|
515
534
|
# Update cached state
|
|
516
535
|
self._label = label
|
|
@@ -535,6 +554,7 @@ class Device:
|
|
|
535
554
|
LifxDeviceNotFoundError: If device is not connected
|
|
536
555
|
LifxTimeoutError: If device does not respond
|
|
537
556
|
LifxProtocolError: If response is invalid
|
|
557
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
538
558
|
|
|
539
559
|
Example:
|
|
540
560
|
```python
|
|
@@ -544,6 +564,7 @@ class Device:
|
|
|
544
564
|
"""
|
|
545
565
|
# Request automatically unpacks response
|
|
546
566
|
state = await self.connection.request(packets.Device.GetPower())
|
|
567
|
+
self._raise_if_unhandled(state)
|
|
547
568
|
|
|
548
569
|
# Power level is uint16 (0 or 65535)
|
|
549
570
|
_LOGGER.debug(
|
|
@@ -566,6 +587,7 @@ class Device:
|
|
|
566
587
|
ValueError: If integer value is not 0 or 65535
|
|
567
588
|
LifxDeviceNotFoundError: If device is not connected
|
|
568
589
|
LifxTimeoutError: If device does not respond
|
|
590
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
569
591
|
|
|
570
592
|
Example:
|
|
571
593
|
```python
|
|
@@ -591,9 +613,10 @@ class Device:
|
|
|
591
613
|
raise TypeError(f"Expected bool or int, got {type(level).__name__}")
|
|
592
614
|
|
|
593
615
|
# Request automatically handles acknowledgement
|
|
594
|
-
await self.connection.request(
|
|
616
|
+
result = await self.connection.request(
|
|
595
617
|
packets.Device.SetPower(level=power_level),
|
|
596
618
|
)
|
|
619
|
+
self._raise_if_unhandled(result)
|
|
597
620
|
|
|
598
621
|
_LOGGER.debug(
|
|
599
622
|
{
|
|
@@ -616,6 +639,7 @@ class Device:
|
|
|
616
639
|
LifxDeviceNotFoundError: If device is not connected
|
|
617
640
|
LifxTimeoutError: If device does not respond
|
|
618
641
|
LifxProtocolError: If response is invalid
|
|
642
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
619
643
|
|
|
620
644
|
Example:
|
|
621
645
|
```python
|
|
@@ -625,6 +649,7 @@ class Device:
|
|
|
625
649
|
"""
|
|
626
650
|
# Request automatically unpacks response
|
|
627
651
|
state = await self.connection.request(packets.Device.GetVersion())
|
|
652
|
+
self._raise_if_unhandled(state)
|
|
628
653
|
|
|
629
654
|
version = DeviceVersion(
|
|
630
655
|
vendor=state.vendor,
|
|
@@ -655,6 +680,7 @@ class Device:
|
|
|
655
680
|
LifxDeviceNotFoundError: If device is not connected
|
|
656
681
|
LifxTimeoutError: If device does not respond
|
|
657
682
|
LifxProtocolError: If response is invalid
|
|
683
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
658
684
|
|
|
659
685
|
Example:
|
|
660
686
|
```python
|
|
@@ -665,6 +691,7 @@ class Device:
|
|
|
665
691
|
"""
|
|
666
692
|
# Request automatically unpacks response
|
|
667
693
|
state = await self.connection.request(packets.Device.GetInfo()) # type: ignore
|
|
694
|
+
self._raise_if_unhandled(state)
|
|
668
695
|
|
|
669
696
|
info = DeviceInfo(time=state.time, uptime=state.uptime, downtime=state.downtime)
|
|
670
697
|
|
|
@@ -694,6 +721,7 @@ class Device:
|
|
|
694
721
|
LifxDeviceNotFoundError: If device is not connected
|
|
695
722
|
LifxTimeoutError: If device does not respond
|
|
696
723
|
LifxProtocolError: If response is invalid
|
|
724
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
697
725
|
|
|
698
726
|
Example:
|
|
699
727
|
```python
|
|
@@ -704,6 +732,7 @@ class Device:
|
|
|
704
732
|
"""
|
|
705
733
|
# Request WiFi info from device
|
|
706
734
|
state = await self.connection.request(packets.Device.GetWifiInfo())
|
|
735
|
+
self._raise_if_unhandled(state)
|
|
707
736
|
|
|
708
737
|
# Extract WiFi info from response
|
|
709
738
|
wifi_info = WifiInfo(signal=state.signal)
|
|
@@ -730,6 +759,7 @@ class Device:
|
|
|
730
759
|
LifxDeviceNotFoundError: If device is not connected
|
|
731
760
|
LifxTimeoutError: If device does not respond
|
|
732
761
|
LifxProtocolError: If response is invalid
|
|
762
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
733
763
|
|
|
734
764
|
Example:
|
|
735
765
|
```python
|
|
@@ -739,6 +769,7 @@ class Device:
|
|
|
739
769
|
"""
|
|
740
770
|
# Request automatically unpacks response
|
|
741
771
|
state = await self.connection.request(packets.Device.GetHostFirmware()) # type: ignore
|
|
772
|
+
self._raise_if_unhandled(state)
|
|
742
773
|
|
|
743
774
|
firmware = FirmwareInfo(
|
|
744
775
|
build=state.build,
|
|
@@ -778,6 +809,7 @@ class Device:
|
|
|
778
809
|
LifxDeviceNotFoundError: If device is not connected
|
|
779
810
|
LifxTimeoutError: If device does not respond
|
|
780
811
|
LifxProtocolError: If response is invalid
|
|
812
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
781
813
|
|
|
782
814
|
Example:
|
|
783
815
|
```python
|
|
@@ -787,6 +819,7 @@ class Device:
|
|
|
787
819
|
"""
|
|
788
820
|
# Request automatically unpacks response
|
|
789
821
|
state = await self.connection.request(packets.Device.GetWifiFirmware()) # type: ignore
|
|
822
|
+
self._raise_if_unhandled(state)
|
|
790
823
|
|
|
791
824
|
firmware = FirmwareInfo(
|
|
792
825
|
build=state.build,
|
|
@@ -822,6 +855,7 @@ class Device:
|
|
|
822
855
|
LifxDeviceNotFoundError: If device is not connected
|
|
823
856
|
LifxTimeoutError: If device does not respond
|
|
824
857
|
LifxProtocolError: If response is invalid
|
|
858
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
825
859
|
|
|
826
860
|
Example:
|
|
827
861
|
```python
|
|
@@ -832,6 +866,7 @@ class Device:
|
|
|
832
866
|
"""
|
|
833
867
|
# Request automatically unpacks response
|
|
834
868
|
state = await self.connection.request(packets.Device.GetLocation()) # type: ignore
|
|
869
|
+
self._raise_if_unhandled(state)
|
|
835
870
|
|
|
836
871
|
location = LocationInfo(
|
|
837
872
|
location=state.location,
|
|
@@ -873,6 +908,7 @@ class Device:
|
|
|
873
908
|
LifxDeviceNotFoundError: If device is not connected
|
|
874
909
|
LifxTimeoutError: If device does not respond
|
|
875
910
|
ValueError: If label is invalid
|
|
911
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
876
912
|
|
|
877
913
|
Example:
|
|
878
914
|
```python
|
|
@@ -967,11 +1003,12 @@ class Device:
|
|
|
967
1003
|
updated_at = int(time.time() * 1e9)
|
|
968
1004
|
|
|
969
1005
|
# Update this device
|
|
970
|
-
await self.connection.request(
|
|
1006
|
+
result = await self.connection.request(
|
|
971
1007
|
packets.Device.SetLocation(
|
|
972
1008
|
location=location_uuid_to_use, label=label_bytes, updated_at=updated_at
|
|
973
1009
|
),
|
|
974
1010
|
)
|
|
1011
|
+
self._raise_if_unhandled(result)
|
|
975
1012
|
|
|
976
1013
|
# Update cached state
|
|
977
1014
|
location_info = LocationInfo(
|
|
@@ -1003,6 +1040,7 @@ class Device:
|
|
|
1003
1040
|
LifxDeviceNotFoundError: If device is not connected
|
|
1004
1041
|
LifxTimeoutError: If device does not respond
|
|
1005
1042
|
LifxProtocolError: If response is invalid
|
|
1043
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
1006
1044
|
|
|
1007
1045
|
Example:
|
|
1008
1046
|
```python
|
|
@@ -1013,6 +1051,7 @@ class Device:
|
|
|
1013
1051
|
"""
|
|
1014
1052
|
# Request automatically unpacks response
|
|
1015
1053
|
state = await self.connection.request(packets.Device.GetGroup()) # type: ignore
|
|
1054
|
+
self._raise_if_unhandled(state)
|
|
1016
1055
|
|
|
1017
1056
|
group = GroupInfo(
|
|
1018
1057
|
group=state.group,
|
|
@@ -1054,6 +1093,7 @@ class Device:
|
|
|
1054
1093
|
LifxDeviceNotFoundError: If device is not connected
|
|
1055
1094
|
LifxTimeoutError: If device does not respond
|
|
1056
1095
|
ValueError: If label is invalid
|
|
1096
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
1057
1097
|
|
|
1058
1098
|
Example:
|
|
1059
1099
|
```python
|
|
@@ -1148,11 +1188,12 @@ class Device:
|
|
|
1148
1188
|
updated_at = int(time.time() * 1e9)
|
|
1149
1189
|
|
|
1150
1190
|
# Update this device
|
|
1151
|
-
await self.connection.request(
|
|
1191
|
+
result = await self.connection.request(
|
|
1152
1192
|
packets.Device.SetGroup(
|
|
1153
1193
|
group=group_uuid_to_use, label=label_bytes, updated_at=updated_at
|
|
1154
1194
|
),
|
|
1155
1195
|
)
|
|
1196
|
+
self._raise_if_unhandled(result)
|
|
1156
1197
|
|
|
1157
1198
|
# Update cached state
|
|
1158
1199
|
group_info = GroupInfo(
|
|
@@ -1181,6 +1222,7 @@ class Device:
|
|
|
1181
1222
|
Raises:
|
|
1182
1223
|
LifxDeviceNotFoundError: If device is not connected
|
|
1183
1224
|
LifxTimeoutError: If device does not respond
|
|
1225
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
1184
1226
|
|
|
1185
1227
|
Example:
|
|
1186
1228
|
```python
|
|
@@ -1194,9 +1236,10 @@ class Device:
|
|
|
1194
1236
|
comes back online and is discoverable again.
|
|
1195
1237
|
"""
|
|
1196
1238
|
# Send reboot request
|
|
1197
|
-
await self.connection.request(
|
|
1239
|
+
result = await self.connection.request(
|
|
1198
1240
|
packets.Device.SetReboot(),
|
|
1199
1241
|
)
|
|
1242
|
+
self._raise_if_unhandled(result)
|
|
1200
1243
|
_LOGGER.debug(
|
|
1201
1244
|
{
|
|
1202
1245
|
"class": "Device",
|
|
@@ -70,6 +70,7 @@ class HevLight(Light):
|
|
|
70
70
|
LifxDeviceNotFoundError: If device is not connected
|
|
71
71
|
LifxTimeoutError: If device does not respond
|
|
72
72
|
LifxProtocolError: If response is invalid
|
|
73
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
73
74
|
|
|
74
75
|
Example:
|
|
75
76
|
```python
|
|
@@ -82,6 +83,7 @@ class HevLight(Light):
|
|
|
82
83
|
"""
|
|
83
84
|
# Request HEV cycle state
|
|
84
85
|
state = await self.connection.request(packets.Light.GetHevCycle())
|
|
86
|
+
self._raise_if_unhandled(state)
|
|
85
87
|
|
|
86
88
|
# Create state object
|
|
87
89
|
cycle_state = HevCycleState(
|
|
@@ -116,6 +118,7 @@ class HevLight(Light):
|
|
|
116
118
|
ValueError: If duration is negative
|
|
117
119
|
LifxDeviceNotFoundError: If device is not connected
|
|
118
120
|
LifxTimeoutError: If device does not respond
|
|
121
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
119
122
|
|
|
120
123
|
Example:
|
|
121
124
|
```python
|
|
@@ -130,12 +133,13 @@ class HevLight(Light):
|
|
|
130
133
|
raise ValueError(f"Duration must be non-negative, got {duration_seconds}")
|
|
131
134
|
|
|
132
135
|
# Request automatically handles acknowledgement
|
|
133
|
-
await self.connection.request(
|
|
136
|
+
result = await self.connection.request(
|
|
134
137
|
packets.Light.SetHevCycle(
|
|
135
138
|
enable=enable,
|
|
136
139
|
duration_s=duration_seconds,
|
|
137
140
|
),
|
|
138
141
|
)
|
|
142
|
+
self._raise_if_unhandled(result)
|
|
139
143
|
|
|
140
144
|
_LOGGER.debug(
|
|
141
145
|
{
|
|
@@ -156,6 +160,7 @@ class HevLight(Light):
|
|
|
156
160
|
LifxDeviceNotFoundError: If device is not connected
|
|
157
161
|
LifxTimeoutError: If device does not respond
|
|
158
162
|
LifxProtocolError: If response is invalid
|
|
163
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
159
164
|
|
|
160
165
|
Example:
|
|
161
166
|
```python
|
|
@@ -166,6 +171,7 @@ class HevLight(Light):
|
|
|
166
171
|
"""
|
|
167
172
|
# Request HEV configuration
|
|
168
173
|
state = await self.connection.request(packets.Light.GetHevCycleConfiguration())
|
|
174
|
+
self._raise_if_unhandled(state)
|
|
169
175
|
|
|
170
176
|
# Create config object
|
|
171
177
|
config = HevConfig(
|
|
@@ -201,6 +207,7 @@ class HevLight(Light):
|
|
|
201
207
|
ValueError: If duration is negative
|
|
202
208
|
LifxDeviceNotFoundError: If device is not connected
|
|
203
209
|
LifxTimeoutError: If device does not respond
|
|
210
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
204
211
|
|
|
205
212
|
Example:
|
|
206
213
|
```python
|
|
@@ -212,12 +219,13 @@ class HevLight(Light):
|
|
|
212
219
|
raise ValueError(f"Duration must be non-negative, got {duration_seconds}")
|
|
213
220
|
|
|
214
221
|
# Request automatically handles acknowledgement
|
|
215
|
-
await self.connection.request(
|
|
222
|
+
result = await self.connection.request(
|
|
216
223
|
packets.Light.SetHevCycleConfiguration(
|
|
217
224
|
indication=indication,
|
|
218
225
|
duration_s=duration_seconds,
|
|
219
226
|
),
|
|
220
227
|
)
|
|
228
|
+
self._raise_if_unhandled(result)
|
|
221
229
|
|
|
222
230
|
# Update cached state
|
|
223
231
|
self._hev_config = HevConfig(indication=indication, duration_s=duration_seconds)
|
|
@@ -242,6 +250,7 @@ class HevLight(Light):
|
|
|
242
250
|
LifxDeviceNotFoundError: If device is not connected
|
|
243
251
|
LifxTimeoutError: If device does not respond
|
|
244
252
|
LifxProtocolError: If response is invalid
|
|
253
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
245
254
|
|
|
246
255
|
Example:
|
|
247
256
|
```python
|
|
@@ -254,6 +263,7 @@ class HevLight(Light):
|
|
|
254
263
|
"""
|
|
255
264
|
# Request last HEV result
|
|
256
265
|
state = await self.connection.request(packets.Light.GetLastHevCycleResult())
|
|
266
|
+
self._raise_if_unhandled(state)
|
|
257
267
|
|
|
258
268
|
# Store cached state
|
|
259
269
|
self._hev_result = state.result
|
|
@@ -58,6 +58,7 @@ class InfraredLight(Light):
|
|
|
58
58
|
LifxDeviceNotFoundError: If device is not connected
|
|
59
59
|
LifxTimeoutError: If device does not respond
|
|
60
60
|
LifxProtocolError: If response is invalid
|
|
61
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
61
62
|
|
|
62
63
|
Example:
|
|
63
64
|
```python
|
|
@@ -68,6 +69,7 @@ class InfraredLight(Light):
|
|
|
68
69
|
"""
|
|
69
70
|
# Request infrared state
|
|
70
71
|
state = await self.connection.request(packets.Light.GetInfrared())
|
|
72
|
+
self._raise_if_unhandled(state)
|
|
71
73
|
|
|
72
74
|
# Convert from uint16 (0-65535) to float (0.0-1.0)
|
|
73
75
|
brightness = state.brightness / 65535.0
|
|
@@ -96,6 +98,7 @@ class InfraredLight(Light):
|
|
|
96
98
|
ValueError: If brightness is out of range
|
|
97
99
|
LifxDeviceNotFoundError: If device is not connected
|
|
98
100
|
LifxTimeoutError: If device does not respond
|
|
101
|
+
LifxUnsupportedCommandError: If device doesn't support this command
|
|
99
102
|
|
|
100
103
|
Example:
|
|
101
104
|
```python
|
|
@@ -115,9 +118,10 @@ class InfraredLight(Light):
|
|
|
115
118
|
brightness_u16 = max(0, min(65535, int(round(brightness * 65535))))
|
|
116
119
|
|
|
117
120
|
# Request automatically handles acknowledgement
|
|
118
|
-
await self.connection.request(
|
|
121
|
+
result = await self.connection.request(
|
|
119
122
|
packets.Light.SetInfrared(brightness=brightness_u16),
|
|
120
123
|
)
|
|
124
|
+
self._raise_if_unhandled(result)
|
|
121
125
|
|
|
122
126
|
# Update cached state
|
|
123
127
|
self._infrared = brightness
|