lifx-emulator 1.0.0__tar.gz → 1.0.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.
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/.github/workflows/ci.yml +14 -14
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/.github/workflows/docs.yml +10 -10
- lifx_emulator-1.0.1/PKG-INFO +107 -0
- lifx_emulator-1.0.1/README.md +79 -0
- lifx_emulator-1.0.1/docs/changelog.md +20 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/pyproject.toml +1 -1
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/renovate.json +1 -1
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/api.py +17 -1
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/server.py +6 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_api.py +82 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_server.py +4 -2
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/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.1}/.gitignore +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/.pre-commit-config.yaml +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/CLAUDE.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/LICENSE +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/advanced/device-management-api.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/advanced/scenario-api.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/advanced/scenarios.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/advanced/storage.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/device.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/factories.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/index.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/products.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/protocol.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/server.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/storage.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/architecture/device-state.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/architecture/overview.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/architecture/packet-flow.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/architecture/protocol.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/assets/favicon.png +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/faq.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/getting-started/cli.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/getting-started/installation.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/getting-started/quickstart.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/best-practices.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/device-types.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/integration-testing.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/overview.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/products-and-specs.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/testing-scenarios.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/web-interface.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/index.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/reference/glossary.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/reference/troubleshooting.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/stylesheets/extra.css +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/tutorials/01-first-device.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/tutorials/02-basic.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/tutorials/03-advanced.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/tutorials/04-integration.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/tutorials/05-cicd.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/tutorials/index.md +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/mkdocs.yml +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/__init__.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/__main__.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/async_storage.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/constants.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/device.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/device_states.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/factories.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/__init__.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/base.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/device_handlers.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/light_handlers.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/multizone_handlers.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/registry.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/tile_handlers.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/observers.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/products/__init__.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/products/generator.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/products/registry.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/products/specs.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/products/specs.yml +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/__init__.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/base.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/const.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/generator.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/header.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/packets.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/protocol_types.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/serializer.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/scenario_manager.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/scenario_persistence.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/state_restorer.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/state_serializer.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/storage_protocol.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/conftest.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_async_storage.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_cli.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_cli_validation.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_device.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_device_edge_cases.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_device_handlers_extended.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_handler_registry.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_integration.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_light_handlers_extended.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_multizone_handlers_extended.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_observers.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_products_generator.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_protocol_generator.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_protocol_types_coverage.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_scenario_manager.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_scenario_persistence.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_serializer.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_state_restorer.py +0 -0
- {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/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 }}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: lifx-emulator
|
|
3
|
+
Version: 1.0.1
|
|
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,20 @@
|
|
|
1
|
+
# CHANGELOG
|
|
2
|
+
|
|
3
|
+
<!-- version list -->
|
|
4
|
+
|
|
5
|
+
## v1.0.1 (2025-11-10)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- Scenarios are now properly applied to initial devices
|
|
10
|
+
([`4808512`](https://github.com/Djelibeybi/lifx-emulator/commit/480851231dbfe6c01b215e3938fa8067c9864227))
|
|
11
|
+
|
|
12
|
+
### Documentation
|
|
13
|
+
|
|
14
|
+
- Replace lifx-async with lifx-emulator and update README.md
|
|
15
|
+
([`64ab6b6`](https://github.com/Djelibeybi/lifx-emulator/commit/64ab6b62dae6422774d8dc72f8f8020f0b6bb705))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
## v1.0.0 (2025-11-06)
|
|
19
|
+
|
|
20
|
+
- Initial Release
|
|
@@ -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
|
}
|
|
@@ -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."""
|
|
@@ -117,6 +117,12 @@ class EmulatedLifxServer:
|
|
|
117
117
|
# Scenario manager (shared across all devices for runtime updates)
|
|
118
118
|
self.scenario_manager = scenario_manager or HierarchicalScenarioManager()
|
|
119
119
|
|
|
120
|
+
# Share scenario manager with all initial devices
|
|
121
|
+
for device in devices:
|
|
122
|
+
if isinstance(device.scenario_manager, HierarchicalScenarioManager):
|
|
123
|
+
device.scenario_manager = self.scenario_manager
|
|
124
|
+
device.invalidate_scenario_cache()
|
|
125
|
+
|
|
120
126
|
# Activity observer - defaults to ActivityLogger if track_activity=True
|
|
121
127
|
if activity_observer is not None:
|
|
122
128
|
self.activity_observer = activity_observer
|
|
@@ -627,3 +627,85 @@ class TestScenarioConfiguration:
|
|
|
627
627
|
assert data["scenario"]["response_delays"]["101"] == 0.1
|
|
628
628
|
assert data["scenario"]["response_delays"]["102"] == 0.2
|
|
629
629
|
assert data["scenario"]["response_delays"]["116"] == 0.5
|
|
630
|
+
|
|
631
|
+
def test_scenario_drop_packets_string_keys_converted(
|
|
632
|
+
self, api_client, server_with_devices
|
|
633
|
+
):
|
|
634
|
+
"""Test that string keys in drop_packets are converted to integers.
|
|
635
|
+
|
|
636
|
+
Regression test for bug where JSON string keys like {"101": 1.0}
|
|
637
|
+
were not being converted to integers, causing packet dropping to fail
|
|
638
|
+
because the comparison was int vs string.
|
|
639
|
+
"""
|
|
640
|
+
from lifx_emulator.protocol.header import LifxHeader
|
|
641
|
+
|
|
642
|
+
# Set scenario with string keys (as JSON will provide)
|
|
643
|
+
scenario_config = {
|
|
644
|
+
"drop_packets": {"101": 1.0}, # String key
|
|
645
|
+
"response_delays": {},
|
|
646
|
+
"malformed_packets": [],
|
|
647
|
+
"invalid_field_values": [],
|
|
648
|
+
"firmware_version": None,
|
|
649
|
+
"partial_responses": [],
|
|
650
|
+
"send_unhandled": False,
|
|
651
|
+
}
|
|
652
|
+
response = api_client.put("/api/scenarios/global", json=scenario_config)
|
|
653
|
+
assert response.status_code == 200
|
|
654
|
+
|
|
655
|
+
# Verify the device's scenario manager has integer keys
|
|
656
|
+
device = server_with_devices.get_device("d073d5000001")
|
|
657
|
+
resolved_scenario = device._get_resolved_scenario()
|
|
658
|
+
|
|
659
|
+
# Keys should be integers, not strings
|
|
660
|
+
assert 101 in resolved_scenario.drop_packets
|
|
661
|
+
assert "101" not in resolved_scenario.drop_packets
|
|
662
|
+
assert resolved_scenario.drop_packets[101] == 1.0
|
|
663
|
+
|
|
664
|
+
# Verify packet dropping actually works
|
|
665
|
+
header = LifxHeader(
|
|
666
|
+
source=12345,
|
|
667
|
+
target=device.state.get_target_bytes(),
|
|
668
|
+
sequence=1,
|
|
669
|
+
pkt_type=101, # GetColor - should be dropped
|
|
670
|
+
res_required=True,
|
|
671
|
+
)
|
|
672
|
+
responses = device.process_packet(header, None)
|
|
673
|
+
assert len(responses) == 0 # Packet should be dropped
|
|
674
|
+
|
|
675
|
+
def test_scenario_response_delays_string_keys_converted(
|
|
676
|
+
self, api_client, server_with_devices
|
|
677
|
+
):
|
|
678
|
+
"""Test that string keys in response_delays are converted to integers.
|
|
679
|
+
|
|
680
|
+
Ensures Pydantic validation correctly converts JSON string keys like
|
|
681
|
+
{"101": 0.5} to integer keys for proper packet type matching.
|
|
682
|
+
"""
|
|
683
|
+
# Set scenario with string keys (as JSON will provide)
|
|
684
|
+
scenario_config = {
|
|
685
|
+
"drop_packets": {},
|
|
686
|
+
"response_delays": {"101": 0.5, "116": 1.0}, # String keys
|
|
687
|
+
"malformed_packets": [],
|
|
688
|
+
"invalid_field_values": [],
|
|
689
|
+
"firmware_version": None,
|
|
690
|
+
"partial_responses": [],
|
|
691
|
+
"send_unhandled": False,
|
|
692
|
+
}
|
|
693
|
+
response = api_client.put("/api/scenarios/global", json=scenario_config)
|
|
694
|
+
assert response.status_code == 200
|
|
695
|
+
|
|
696
|
+
# Verify the response contains the expected data
|
|
697
|
+
data = response.json()
|
|
698
|
+
assert data["scenario"]["response_delays"] == {"101": 0.5, "116": 1.0}
|
|
699
|
+
|
|
700
|
+
# Verify the device's scenario manager has integer keys
|
|
701
|
+
device = server_with_devices.get_device("d073d5000001")
|
|
702
|
+
resolved_scenario = device._get_resolved_scenario()
|
|
703
|
+
|
|
704
|
+
# Keys should be integers, not strings
|
|
705
|
+
assert 101 in resolved_scenario.response_delays
|
|
706
|
+
assert "101" not in resolved_scenario.response_delays
|
|
707
|
+
assert resolved_scenario.response_delays[101] == 0.5
|
|
708
|
+
|
|
709
|
+
assert 116 in resolved_scenario.response_delays
|
|
710
|
+
assert "116" not in resolved_scenario.response_delays
|
|
711
|
+
assert resolved_scenario.response_delays[116] == 1.0
|
|
@@ -195,8 +195,10 @@ class TestResponseDelays:
|
|
|
195
195
|
color_device.state.serial,
|
|
196
196
|
ScenarioConfig(response_delays={107: 0.1}), # StateColor response
|
|
197
197
|
)
|
|
198
|
-
|
|
199
|
-
server = EmulatedLifxServer(
|
|
198
|
+
# Pass scenario_manager to server so it gets shared with all devices
|
|
199
|
+
server = EmulatedLifxServer(
|
|
200
|
+
[color_device], "127.0.0.1", 56700, scenario_manager=scenario_manager
|
|
201
|
+
)
|
|
200
202
|
|
|
201
203
|
# StateColor response (107) has 100ms delay configured
|
|
202
204
|
from lifx_emulator.constants import HEADER_SIZE
|