lifx-emulator 1.0.0__tar.gz → 1.0.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.
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/.github/workflows/ci.yml +14 -14
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/.github/workflows/docs.yml +10 -10
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/CLAUDE.md +42 -0
- lifx_emulator-1.0.2/PKG-INFO +107 -0
- lifx_emulator-1.0.2/README.md +79 -0
- lifx_emulator-1.0.2/docs/changelog.md +28 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/pyproject.toml +9 -1
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/renovate.json +1 -1
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/__main__.py +4 -2
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/api.py +17 -1
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/products/generator.py +30 -18
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/products/registry.py +63 -25
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/server.py +6 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_api.py +82 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_server.py +4 -2
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/uv.lock +1 -1
- lifx_emulator-1.0.0/PKG-INFO +0 -445
- lifx_emulator-1.0.0/README.md +0 -417
- lifx_emulator-1.0.0/docs/changelog.md +0 -7
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/.gitignore +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/.pre-commit-config.yaml +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/LICENSE +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/advanced/device-management-api.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/advanced/scenario-api.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/advanced/scenarios.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/advanced/storage.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/api/device.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/api/factories.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/api/index.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/api/products.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/api/protocol.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/api/server.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/api/storage.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/architecture/device-state.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/architecture/overview.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/architecture/packet-flow.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/architecture/protocol.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/assets/favicon.png +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/faq.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/getting-started/cli.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/getting-started/installation.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/getting-started/quickstart.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/guide/best-practices.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/guide/device-types.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/guide/integration-testing.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/guide/overview.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/guide/products-and-specs.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/guide/testing-scenarios.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/guide/web-interface.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/index.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/reference/glossary.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/reference/troubleshooting.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/stylesheets/extra.css +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/tutorials/01-first-device.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/tutorials/02-basic.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/tutorials/03-advanced.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/tutorials/04-integration.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/tutorials/05-cicd.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/docs/tutorials/index.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/mkdocs.yml +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/__init__.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/async_storage.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/constants.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/device.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/device_states.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/factories.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/handlers/__init__.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/handlers/base.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/handlers/device_handlers.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/handlers/light_handlers.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/handlers/multizone_handlers.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/handlers/registry.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/handlers/tile_handlers.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/observers.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/products/__init__.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/products/specs.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/products/specs.yml +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/protocol/__init__.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/protocol/base.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/protocol/const.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/protocol/generator.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/protocol/header.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/protocol/packets.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/protocol/protocol_types.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/protocol/serializer.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/scenario_manager.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/scenario_persistence.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/state_restorer.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/state_serializer.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/src/lifx_emulator/storage_protocol.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/conftest.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_async_storage.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_cli.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_cli_validation.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_device.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_device_edge_cases.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_device_handlers_extended.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_handler_registry.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_integration.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_light_handlers_extended.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_multizone_handlers_extended.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_observers.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_products_generator.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_protocol_generator.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_protocol_types_coverage.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_scenario_manager.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_scenario_persistence.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_serializer.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_state_restorer.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.2}/tests/test_tile_handlers_extended.py +0 -0
|
@@ -17,15 +17,15 @@ jobs:
|
|
|
17
17
|
name: Code Quality
|
|
18
18
|
runs-on: ubuntu-latest
|
|
19
19
|
steps:
|
|
20
|
-
- uses: actions/checkout@v5
|
|
20
|
+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
21
21
|
|
|
22
22
|
- name: Set up Python
|
|
23
|
-
uses: actions/setup-python@v6
|
|
23
|
+
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
|
24
24
|
with:
|
|
25
25
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
26
26
|
|
|
27
27
|
- name: Install uv
|
|
28
|
-
uses: astral-sh/setup-uv@v7
|
|
28
|
+
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7
|
|
29
29
|
with:
|
|
30
30
|
version: ${{ env.UV_VERSION }}
|
|
31
31
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
@@ -57,15 +57,15 @@ jobs:
|
|
|
57
57
|
os: [ubuntu-latest, macos-latest]
|
|
58
58
|
python-version: ['3.11', '3.12', '3.13', '3.14']
|
|
59
59
|
steps:
|
|
60
|
-
- uses: actions/checkout@v5
|
|
60
|
+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
61
61
|
|
|
62
62
|
- name: Set up Python ${{ matrix.python-version }}
|
|
63
|
-
uses: actions/setup-python@v6
|
|
63
|
+
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
|
64
64
|
with:
|
|
65
65
|
python-version: ${{ matrix.python-version }}
|
|
66
66
|
|
|
67
67
|
- name: Install uv
|
|
68
|
-
uses: astral-sh/setup-uv@v7
|
|
68
|
+
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7
|
|
69
69
|
with:
|
|
70
70
|
version: ${{ env.UV_VERSION }}
|
|
71
71
|
python-version: ${{ matrix.python-version }}
|
|
@@ -79,15 +79,15 @@ jobs:
|
|
|
79
79
|
|
|
80
80
|
- name: Upload coverage to Codecov
|
|
81
81
|
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
|
|
82
|
-
uses: codecov/codecov-action@v5
|
|
82
|
+
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5
|
|
83
83
|
with:
|
|
84
|
-
slug: Djelibeybi/lifx-
|
|
84
|
+
slug: Djelibeybi/lifx-emulator
|
|
85
85
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
86
86
|
fail_ci_if_error: false
|
|
87
87
|
|
|
88
88
|
- name: Upload test results to Codecov
|
|
89
89
|
if: ${{ !cancelled() }}
|
|
90
|
-
uses: codecov/test-results-action@v1
|
|
90
|
+
uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1
|
|
91
91
|
with:
|
|
92
92
|
fail_ci_if_error: false
|
|
93
93
|
token: ${{ secrets.CODECOV_TOKEN }}
|
|
@@ -112,19 +112,19 @@ jobs:
|
|
|
112
112
|
|
|
113
113
|
steps:
|
|
114
114
|
- name: Checkout repository on release branch
|
|
115
|
-
uses: actions/checkout@v5
|
|
115
|
+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
116
116
|
with:
|
|
117
117
|
ref: ${{ github.head_ref || github.ref_name }}
|
|
118
118
|
fetch-depth: 0
|
|
119
119
|
ssh-key: ${{ secrets.DEPLOY_KEY }}
|
|
120
120
|
|
|
121
121
|
- name: Set up Python
|
|
122
|
-
uses: actions/setup-python@v6
|
|
122
|
+
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
|
123
123
|
with:
|
|
124
124
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
125
125
|
|
|
126
126
|
- name: Install uv
|
|
127
|
-
uses: astral-sh/setup-uv@v7
|
|
127
|
+
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7
|
|
128
128
|
with:
|
|
129
129
|
version: ${{ env.UV_VERSION }}
|
|
130
130
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
@@ -153,7 +153,7 @@ jobs:
|
|
|
153
153
|
uv run --with python-semantic-release semantic-release version
|
|
154
154
|
|
|
155
155
|
- name: Upload Distribution Artifacts
|
|
156
|
-
uses: actions/upload-artifact@v5
|
|
156
|
+
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
157
157
|
with:
|
|
158
158
|
name: distribution-artifacts
|
|
159
159
|
path: dist
|
|
@@ -174,7 +174,7 @@ jobs:
|
|
|
174
174
|
|
|
175
175
|
steps:
|
|
176
176
|
- name: Download Build Artifacts
|
|
177
|
-
uses: actions/download-artifact@v6
|
|
177
|
+
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6
|
|
178
178
|
id: artifact-download
|
|
179
179
|
with:
|
|
180
180
|
name: distribution-artifacts
|
|
@@ -25,17 +25,17 @@ jobs:
|
|
|
25
25
|
build-docs:
|
|
26
26
|
runs-on: ubuntu-latest
|
|
27
27
|
steps:
|
|
28
|
-
- uses: actions/checkout@v5
|
|
28
|
+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
29
29
|
with:
|
|
30
30
|
fetch-depth: 0 # Fetch all history for git-revision-date-localized
|
|
31
31
|
|
|
32
32
|
- name: Set up Python
|
|
33
|
-
uses: actions/setup-python@v6
|
|
33
|
+
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
|
34
34
|
with:
|
|
35
35
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
36
36
|
|
|
37
37
|
- name: Install uv
|
|
38
|
-
uses: astral-sh/setup-uv@v7
|
|
38
|
+
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7
|
|
39
39
|
with:
|
|
40
40
|
version: ${{ env.UV_VERSION }}
|
|
41
41
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
@@ -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@v5
|
|
52
|
+
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5
|
|
53
53
|
with:
|
|
54
54
|
name: docs
|
|
55
55
|
path: site/
|
|
@@ -59,17 +59,17 @@ 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@v5
|
|
62
|
+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
63
63
|
with:
|
|
64
64
|
fetch-depth: 0
|
|
65
65
|
|
|
66
66
|
- name: Set up Python
|
|
67
|
-
uses: actions/setup-python@v6
|
|
67
|
+
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
|
68
68
|
with:
|
|
69
69
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
70
70
|
|
|
71
71
|
- name: Install uv
|
|
72
|
-
uses: astral-sh/setup-uv@v7
|
|
72
|
+
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7
|
|
73
73
|
with:
|
|
74
74
|
version: ${{ env.UV_VERSION }}
|
|
75
75
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
@@ -89,15 +89,15 @@ jobs:
|
|
|
89
89
|
validate-links:
|
|
90
90
|
runs-on: ubuntu-latest
|
|
91
91
|
steps:
|
|
92
|
-
- uses: actions/checkout@v5
|
|
92
|
+
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
|
93
93
|
|
|
94
94
|
- name: Set up Python
|
|
95
|
-
uses: actions/setup-python@v6
|
|
95
|
+
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6
|
|
96
96
|
with:
|
|
97
97
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
98
98
|
|
|
99
99
|
- name: Install uv
|
|
100
|
-
uses: astral-sh/setup-uv@v7
|
|
100
|
+
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7
|
|
101
101
|
with:
|
|
102
102
|
version: ${{ env.UV_VERSION }}
|
|
103
103
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
@@ -553,6 +553,48 @@ All factory functions now use the specs system to load product-specific defaults
|
|
|
553
553
|
- Tile dimensions (e.g., 8x8 for Tiles, 5x6 for Candles)
|
|
554
554
|
- Users can override these defaults by passing explicit parameters
|
|
555
555
|
|
|
556
|
+
### Product Registry
|
|
557
|
+
|
|
558
|
+
**Product Registry** (`src/lifx_emulator/products/registry.py`):
|
|
559
|
+
- Auto-generated from official LIFX products.json (https://github.com/LIFX/products)
|
|
560
|
+
- Contains 137+ product definitions with capabilities, temperature ranges, and firmware requirements
|
|
561
|
+
- Pre-built `ProductInfo` instances for efficient runtime lookups
|
|
562
|
+
- Capability flags: `COLOR`, `INFRARED`, `MULTIZONE`, `CHAIN`, `MATRIX`, `RELAYS`, `BUTTONS`, `HEV`, `EXTENDED_MULTIZONE`
|
|
563
|
+
- Never edit this file manually - regenerate using the generator
|
|
564
|
+
|
|
565
|
+
**Product Registry Generator** (`src/lifx_emulator/products/generator.py`):
|
|
566
|
+
- Downloads latest products.json from LIFX GitHub repository
|
|
567
|
+
- Generates optimized Python code with pre-built product definitions
|
|
568
|
+
- Handles extended multizone capability detection:
|
|
569
|
+
- **Native support**: Products with `extended_multizone: true` in features (no firmware requirement)
|
|
570
|
+
- Examples: LIFX Z US (PID 117), LIFX Beam US (PID 119), LIFX Neon, LIFX Permanent Outdoor
|
|
571
|
+
- **Firmware upgrade**: Products with `extended_multizone` in upgrades section (requires minimum firmware)
|
|
572
|
+
- Examples: LIFX Z (PID 32, requires firmware 2.77+), LIFX Beam (PID 38, requires firmware 2.77+)
|
|
573
|
+
- Updates specs.yml with templates for new multizone/matrix products
|
|
574
|
+
- Run with: `python -m lifx_emulator.products.generator`
|
|
575
|
+
|
|
576
|
+
**Product Specs** (`src/lifx_emulator/products/specs.yml`):
|
|
577
|
+
- Product-specific configuration not available in upstream products.json
|
|
578
|
+
- Default zone counts, tile configurations, and device-specific defaults
|
|
579
|
+
- Used by factory functions to create realistic device configurations
|
|
580
|
+
- Manually maintained for accurate product specifications
|
|
581
|
+
|
|
582
|
+
**ProductInfo API:**
|
|
583
|
+
```python
|
|
584
|
+
from lifx_emulator.products.registry import get_product
|
|
585
|
+
|
|
586
|
+
product = get_product(117) # LIFX Z US
|
|
587
|
+
product.has_extended_multizone # True
|
|
588
|
+
product.min_ext_mz_firmware # None (native support)
|
|
589
|
+
product.supports_extended_multizone() # True
|
|
590
|
+
|
|
591
|
+
product = get_product(32) # LIFX Z (older model)
|
|
592
|
+
product.has_extended_multizone # True
|
|
593
|
+
product.min_ext_mz_firmware # 131149 (firmware 2.77)
|
|
594
|
+
product.supports_extended_multizone(131149) # True (meets requirement)
|
|
595
|
+
product.supports_extended_multizone(131148) # False (below requirement)
|
|
596
|
+
```
|
|
597
|
+
|
|
556
598
|
## Key Implementation Details
|
|
557
599
|
|
|
558
600
|
### MultiZone Handling
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lifx-emulator
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: LIFX Emulator for testing LIFX LAN protocol libraries
|
|
5
|
+
Author-email: Avi Miller <me@dje.li>
|
|
6
|
+
Maintainer-email: Avi Miller <me@dje.li>
|
|
7
|
+
License-Expression: UPL-1.0
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Framework :: AsyncIO
|
|
10
|
+
Classifier: Framework :: Pytest
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: cyclopts>=4.2.0
|
|
23
|
+
Requires-Dist: fastapi>=0.115.0
|
|
24
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
25
|
+
Requires-Dist: rich>=14.2.0
|
|
26
|
+
Requires-Dist: uvicorn>=0.34.0
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# LIFX Emulator
|
|
30
|
+
|
|
31
|
+
> A comprehensive LIFX device emulator for testing LIFX LAN protocol libraries
|
|
32
|
+
|
|
33
|
+
[](https://codecov.io/gh/Djelibeybi/lifx-emulator)
|
|
34
|
+
[](https://github.com/Djelibeybi/lifx-emulator/actions/workflows/ci.yml)
|
|
35
|
+
[](https://Djelibeybi.github.io/lifx-emulator/)
|
|
36
|
+
|
|
37
|
+
[](https://github.com/Djelibeybi/lifx-emulator/releases)
|
|
38
|
+
[](https://pypi.org/project/lifx-emulator/)
|
|
39
|
+
[](LICENSE)
|
|
40
|
+
[](https://www.python.org)
|
|
41
|
+
## Overview
|
|
42
|
+
|
|
43
|
+
LIFX Emulator implements the complete binary UDP protocol from [lan.developer.lifx.com](https://lan.developer.lifx.com) by providing virtual LIFX devices for testing without physical hardware. The emulator includes a basic web interface and OpenAPI-compliant REST API for device and scenario management at runtime.
|
|
44
|
+
|
|
45
|
+
## Features
|
|
46
|
+
|
|
47
|
+
- **Complete Protocol Support**: 44+ packet types from the LIFX LAN protocol
|
|
48
|
+
- **Multiple Device Types**: Color lights, infrared, HEV, multizone strips, matrix tiles
|
|
49
|
+
- **REST API and Web Interface**: Monitor and manage your virtual devices during testing
|
|
50
|
+
- **Testing Scenarios**: Built-in support for packet loss, delays, malformed responses
|
|
51
|
+
- **Easy Integration**: Simple Python API and comprehensive CLI
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## Documentation
|
|
55
|
+
|
|
56
|
+
- **[Installation Guide](https://djelibeybi.github.io/lifx-emulator/getting-started/installation/)** - Get started
|
|
57
|
+
- **[Quick Start](https://djelibeybi.github.io/lifx-emulator/getting-started/quickstart/)** - Your first emulated device
|
|
58
|
+
- **[User Guide](https://djelibeybi.github.io/lifx-emulator/guide/overview/)** - Product specifications and testing scenarios
|
|
59
|
+
- **[Advanced Topics](https://djelibeybi.github.io/lifx-emulator/advanced/device-management-api/)** - REST API and persistent storage
|
|
60
|
+
- **[CLI Reference](https://djelibeybi.github.io/lifx-emulator/getting-started/cli/)** - All CLI options
|
|
61
|
+
- **[Device Types](https://djelibeybi.github.io/lifx-emulator/guide/device-types/)** - Supported devices
|
|
62
|
+
- **[API Reference](https://djelibeybi.github.io/lifx-emulator/api/)** - Complete API docs
|
|
63
|
+
- **[Architecture](https://djelibeybi.github.io/lifx-emulator/architecture/overview/)** - How it works
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
## Use Cases
|
|
67
|
+
|
|
68
|
+
- **Library Testing**: Test your LIFX library without physical devices
|
|
69
|
+
- **CI/CD Integration**: Run automated tests in pipelines
|
|
70
|
+
- **Protocol Development**: Experiment with LIFX protocol features
|
|
71
|
+
- **Error Simulation**: Test error handling with configurable scenarios
|
|
72
|
+
- **Performance Testing**: Test concurrent device handling
|
|
73
|
+
|
|
74
|
+
## Development
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Clone repository
|
|
78
|
+
git clone https://github.com/Djelibeybi/lifx-emulator.git
|
|
79
|
+
cd lifx-emulator
|
|
80
|
+
|
|
81
|
+
# Install with uv (recommended)
|
|
82
|
+
uv sync
|
|
83
|
+
|
|
84
|
+
# Or with pip
|
|
85
|
+
pip install -e ".[dev]"
|
|
86
|
+
|
|
87
|
+
# Run tests
|
|
88
|
+
uv run pytest
|
|
89
|
+
|
|
90
|
+
# Run linter
|
|
91
|
+
uv run ruff check .
|
|
92
|
+
|
|
93
|
+
# Build docs
|
|
94
|
+
uv run mkdocs serve
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
[UPL-1.0](LICENSE)
|
|
101
|
+
|
|
102
|
+
## Links
|
|
103
|
+
|
|
104
|
+
- **Documentation**: https://djelibeybi.github.io/lifx-emulator
|
|
105
|
+
- **GitHub**: https://github.com/Djelibeybi/lifx-emulator
|
|
106
|
+
- **PyPI**: https://pypi.org/project/lifx-emulator/
|
|
107
|
+
- **LIFX Protocol**: https://lan.developer.lifx.com
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# LIFX Emulator
|
|
2
|
+
|
|
3
|
+
> A comprehensive LIFX device emulator for testing LIFX LAN protocol libraries
|
|
4
|
+
|
|
5
|
+
[](https://codecov.io/gh/Djelibeybi/lifx-emulator)
|
|
6
|
+
[](https://github.com/Djelibeybi/lifx-emulator/actions/workflows/ci.yml)
|
|
7
|
+
[](https://Djelibeybi.github.io/lifx-emulator/)
|
|
8
|
+
|
|
9
|
+
[](https://github.com/Djelibeybi/lifx-emulator/releases)
|
|
10
|
+
[](https://pypi.org/project/lifx-emulator/)
|
|
11
|
+
[](LICENSE)
|
|
12
|
+
[](https://www.python.org)
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
LIFX Emulator implements the complete binary UDP protocol from [lan.developer.lifx.com](https://lan.developer.lifx.com) by providing virtual LIFX devices for testing without physical hardware. The emulator includes a basic web interface and OpenAPI-compliant REST API for device and scenario management at runtime.
|
|
16
|
+
|
|
17
|
+
## Features
|
|
18
|
+
|
|
19
|
+
- **Complete Protocol Support**: 44+ packet types from the LIFX LAN protocol
|
|
20
|
+
- **Multiple Device Types**: Color lights, infrared, HEV, multizone strips, matrix tiles
|
|
21
|
+
- **REST API and Web Interface**: Monitor and manage your virtual devices during testing
|
|
22
|
+
- **Testing Scenarios**: Built-in support for packet loss, delays, malformed responses
|
|
23
|
+
- **Easy Integration**: Simple Python API and comprehensive CLI
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## Documentation
|
|
27
|
+
|
|
28
|
+
- **[Installation Guide](https://djelibeybi.github.io/lifx-emulator/getting-started/installation/)** - Get started
|
|
29
|
+
- **[Quick Start](https://djelibeybi.github.io/lifx-emulator/getting-started/quickstart/)** - Your first emulated device
|
|
30
|
+
- **[User Guide](https://djelibeybi.github.io/lifx-emulator/guide/overview/)** - Product specifications and testing scenarios
|
|
31
|
+
- **[Advanced Topics](https://djelibeybi.github.io/lifx-emulator/advanced/device-management-api/)** - REST API and persistent storage
|
|
32
|
+
- **[CLI Reference](https://djelibeybi.github.io/lifx-emulator/getting-started/cli/)** - All CLI options
|
|
33
|
+
- **[Device Types](https://djelibeybi.github.io/lifx-emulator/guide/device-types/)** - Supported devices
|
|
34
|
+
- **[API Reference](https://djelibeybi.github.io/lifx-emulator/api/)** - Complete API docs
|
|
35
|
+
- **[Architecture](https://djelibeybi.github.io/lifx-emulator/architecture/overview/)** - How it works
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## Use Cases
|
|
39
|
+
|
|
40
|
+
- **Library Testing**: Test your LIFX library without physical devices
|
|
41
|
+
- **CI/CD Integration**: Run automated tests in pipelines
|
|
42
|
+
- **Protocol Development**: Experiment with LIFX protocol features
|
|
43
|
+
- **Error Simulation**: Test error handling with configurable scenarios
|
|
44
|
+
- **Performance Testing**: Test concurrent device handling
|
|
45
|
+
|
|
46
|
+
## Development
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Clone repository
|
|
50
|
+
git clone https://github.com/Djelibeybi/lifx-emulator.git
|
|
51
|
+
cd lifx-emulator
|
|
52
|
+
|
|
53
|
+
# Install with uv (recommended)
|
|
54
|
+
uv sync
|
|
55
|
+
|
|
56
|
+
# Or with pip
|
|
57
|
+
pip install -e ".[dev]"
|
|
58
|
+
|
|
59
|
+
# Run tests
|
|
60
|
+
uv run pytest
|
|
61
|
+
|
|
62
|
+
# Run linter
|
|
63
|
+
uv run ruff check .
|
|
64
|
+
|
|
65
|
+
# Build docs
|
|
66
|
+
uv run mkdocs serve
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
[UPL-1.0](LICENSE)
|
|
73
|
+
|
|
74
|
+
## Links
|
|
75
|
+
|
|
76
|
+
- **Documentation**: https://djelibeybi.github.io/lifx-emulator
|
|
77
|
+
- **GitHub**: https://github.com/Djelibeybi/lifx-emulator
|
|
78
|
+
- **PyPI**: https://pypi.org/project/lifx-emulator/
|
|
79
|
+
- **LIFX Protocol**: https://lan.developer.lifx.com
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
<!-- version list -->
|
|
4
|
+
|
|
5
|
+
## v1.0.2 (2025-11-10)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Extended_multizone added to products correctly by generator
|
|
10
|
+
([`b6c4f78`](https://github.com/Djelibeybi/lifx-emulator/commit/b6c4f78c7353313b961acdb4283023a595141151))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## v1.0.1 (2025-11-10)
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
- Scenarios are now properly applied to initial devices
|
|
18
|
+
([`4808512`](https://github.com/Djelibeybi/lifx-emulator/commit/480851231dbfe6c01b215e3938fa8067c9864227))
|
|
19
|
+
|
|
20
|
+
### Documentation
|
|
21
|
+
|
|
22
|
+
- Replace lifx-async with lifx-emulator and update README.md
|
|
23
|
+
([`64ab6b6`](https://github.com/Djelibeybi/lifx-emulator/commit/64ab6b62dae6422774d8dc72f8f8020f0b6bb705))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## v1.0.0 (2025-11-06)
|
|
27
|
+
|
|
28
|
+
- Initial Release
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "lifx-emulator"
|
|
3
|
-
version = "1.0.
|
|
3
|
+
version = "1.0.2"
|
|
4
4
|
description = "LIFX Emulator for testing LIFX LAN protocol libraries"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -103,6 +103,14 @@ addopts = """\
|
|
|
103
103
|
asyncio_mode = "auto"
|
|
104
104
|
asyncio_default_fixture_loop_scope = "function"
|
|
105
105
|
|
|
106
|
+
[tool.coverage.run]
|
|
107
|
+
omit = [
|
|
108
|
+
"src/lifx_emulator/protocol/generator.py",
|
|
109
|
+
"src/lifx_emulator/protocol/protocol_types.py",
|
|
110
|
+
"src/lifx_emulator/products/generator.py",
|
|
111
|
+
"src/lifx_emulator/products/registry.py",
|
|
112
|
+
]
|
|
113
|
+
|
|
106
114
|
[tool.coverage.report]
|
|
107
115
|
exclude_lines = [
|
|
108
116
|
"pragma: no cover",
|
|
@@ -156,6 +156,6 @@
|
|
|
156
156
|
},
|
|
157
157
|
"dependencyDashboard": true,
|
|
158
158
|
"dependencyDashboardTitle": "Dependency Dashboard",
|
|
159
|
-
"dependencyDashboardHeader": "This dashboard shows all pending dependency updates for lifx-
|
|
159
|
+
"dependencyDashboardHeader": "This dashboard shows all pending dependency updates for lifx-emulator.",
|
|
160
160
|
"dependencyDashboardFooter": "Configure Renovate by editing `renovate.json`"
|
|
161
161
|
}
|
|
@@ -102,10 +102,12 @@ def _format_product_capabilities(product: ProductInfo) -> str:
|
|
|
102
102
|
# Add additional capabilities
|
|
103
103
|
if product.has_infrared:
|
|
104
104
|
caps.append("infrared")
|
|
105
|
-
|
|
106
|
-
|
|
105
|
+
# Extended multizone is backwards compatible with multizone,
|
|
106
|
+
# so only show multizone if extended multizone is not present
|
|
107
107
|
if product.has_extended_multizone:
|
|
108
108
|
caps.append("extended-multizone")
|
|
109
|
+
elif product.has_multizone:
|
|
110
|
+
caps.append("multizone")
|
|
109
111
|
if product.has_matrix:
|
|
110
112
|
caps.append("matrix")
|
|
111
113
|
if product.has_hev:
|
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING
|
|
|
7
7
|
|
|
8
8
|
from fastapi import FastAPI, HTTPException
|
|
9
9
|
from fastapi.responses import HTMLResponse
|
|
10
|
-
from pydantic import BaseModel, Field
|
|
10
|
+
from pydantic import BaseModel, Field, field_validator
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from lifx_emulator.server import EmulatedLifxServer
|
|
@@ -133,6 +133,22 @@ class ScenarioConfigModel(BaseModel):
|
|
|
133
133
|
False, description="Send unhandled message responses for unknown packet types"
|
|
134
134
|
)
|
|
135
135
|
|
|
136
|
+
@field_validator("drop_packets", mode="before")
|
|
137
|
+
@classmethod
|
|
138
|
+
def convert_drop_packets_keys(cls, v):
|
|
139
|
+
"""Convert string keys to integers for drop_packets."""
|
|
140
|
+
if isinstance(v, dict):
|
|
141
|
+
return {int(k): float(val) for k, val in v.items()}
|
|
142
|
+
return v
|
|
143
|
+
|
|
144
|
+
@field_validator("response_delays", mode="before")
|
|
145
|
+
@classmethod
|
|
146
|
+
def convert_response_delays_keys(cls, v):
|
|
147
|
+
"""Convert string keys to integers for response_delays."""
|
|
148
|
+
if isinstance(v, dict):
|
|
149
|
+
return {int(k): float(val) for k, val in v.items()}
|
|
150
|
+
return v
|
|
151
|
+
|
|
136
152
|
|
|
137
153
|
class ScenarioResponse(BaseModel):
|
|
138
154
|
"""Response model for scenario operations."""
|
|
@@ -97,16 +97,22 @@ def generate_product_definitions(
|
|
|
97
97
|
if features.get("hev"):
|
|
98
98
|
capabilities.append("ProductCapability.HEV")
|
|
99
99
|
|
|
100
|
-
# Check for extended multizone
|
|
100
|
+
# Check for extended multizone capability
|
|
101
101
|
min_ext_mz_firmware = None
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
102
|
+
|
|
103
|
+
# First check if it's a native feature (no firmware requirement)
|
|
104
|
+
if features.get("extended_multizone"):
|
|
105
|
+
capabilities.append("ProductCapability.EXTENDED_MULTIZONE")
|
|
106
|
+
else:
|
|
107
|
+
# Check if it's available as an upgrade (requires minimum firmware)
|
|
108
|
+
for upgrade in product.get("upgrades", []):
|
|
109
|
+
if upgrade.get("features", {}).get("extended_multizone"):
|
|
110
|
+
capabilities.append("ProductCapability.EXTENDED_MULTIZONE")
|
|
111
|
+
# Parse firmware version (major.minor format)
|
|
112
|
+
major = upgrade.get("major", 0)
|
|
113
|
+
minor = upgrade.get("minor", 0)
|
|
114
|
+
min_ext_mz_firmware = (major << 16) | minor
|
|
115
|
+
break
|
|
110
116
|
|
|
111
117
|
# Build capabilities expression
|
|
112
118
|
if capabilities:
|
|
@@ -357,16 +363,22 @@ class ProductRegistry:
|
|
|
357
363
|
if features.get("hev"):
|
|
358
364
|
capabilities |= ProductCapability.HEV
|
|
359
365
|
|
|
360
|
-
# Check for extended multizone
|
|
366
|
+
# Check for extended multizone capability
|
|
361
367
|
min_ext_mz_firmware = None
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
368
|
+
|
|
369
|
+
# First check if it's a native feature (no firmware requirement)
|
|
370
|
+
if features.get("extended_multizone"):
|
|
371
|
+
capabilities |= ProductCapability.EXTENDED_MULTIZONE
|
|
372
|
+
else:
|
|
373
|
+
# Check if it's available as an upgrade (requires minimum firmware)
|
|
374
|
+
for upgrade in product.get("upgrades", []):
|
|
375
|
+
if upgrade.get("features", {}).get("extended_multizone"):
|
|
376
|
+
capabilities |= ProductCapability.EXTENDED_MULTIZONE
|
|
377
|
+
# Parse firmware version (major.minor format)
|
|
378
|
+
major = upgrade.get("major", 0)
|
|
379
|
+
minor = upgrade.get("minor", 0)
|
|
380
|
+
min_ext_mz_firmware = (major << 16) | minor
|
|
381
|
+
break
|
|
370
382
|
|
|
371
383
|
# Parse temperature range
|
|
372
384
|
temp_range = None
|