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.
Files changed (110) hide show
  1. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/.github/workflows/ci.yml +14 -14
  2. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/.github/workflows/docs.yml +10 -10
  3. lifx_emulator-1.0.1/PKG-INFO +107 -0
  4. lifx_emulator-1.0.1/README.md +79 -0
  5. lifx_emulator-1.0.1/docs/changelog.md +20 -0
  6. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/pyproject.toml +1 -1
  7. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/renovate.json +1 -1
  8. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/api.py +17 -1
  9. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/server.py +6 -0
  10. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_api.py +82 -0
  11. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_server.py +4 -2
  12. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/uv.lock +1 -1
  13. lifx_emulator-1.0.0/PKG-INFO +0 -445
  14. lifx_emulator-1.0.0/README.md +0 -417
  15. lifx_emulator-1.0.0/docs/changelog.md +0 -7
  16. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/.gitignore +0 -0
  17. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/.pre-commit-config.yaml +0 -0
  18. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/CLAUDE.md +0 -0
  19. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/LICENSE +0 -0
  20. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/advanced/device-management-api.md +0 -0
  21. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/advanced/scenario-api.md +0 -0
  22. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/advanced/scenarios.md +0 -0
  23. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/advanced/storage.md +0 -0
  24. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/device.md +0 -0
  25. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/factories.md +0 -0
  26. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/index.md +0 -0
  27. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/products.md +0 -0
  28. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/protocol.md +0 -0
  29. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/server.md +0 -0
  30. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/api/storage.md +0 -0
  31. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/architecture/device-state.md +0 -0
  32. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/architecture/overview.md +0 -0
  33. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/architecture/packet-flow.md +0 -0
  34. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/architecture/protocol.md +0 -0
  35. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/assets/favicon.png +0 -0
  36. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/faq.md +0 -0
  37. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/getting-started/cli.md +0 -0
  38. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/getting-started/installation.md +0 -0
  39. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/getting-started/quickstart.md +0 -0
  40. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/best-practices.md +0 -0
  41. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/device-types.md +0 -0
  42. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/integration-testing.md +0 -0
  43. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/overview.md +0 -0
  44. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/products-and-specs.md +0 -0
  45. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/testing-scenarios.md +0 -0
  46. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/guide/web-interface.md +0 -0
  47. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/index.md +0 -0
  48. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/reference/glossary.md +0 -0
  49. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/reference/troubleshooting.md +0 -0
  50. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/stylesheets/extra.css +0 -0
  51. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/tutorials/01-first-device.md +0 -0
  52. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/tutorials/02-basic.md +0 -0
  53. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/tutorials/03-advanced.md +0 -0
  54. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/tutorials/04-integration.md +0 -0
  55. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/tutorials/05-cicd.md +0 -0
  56. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/docs/tutorials/index.md +0 -0
  57. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/mkdocs.yml +0 -0
  58. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/__init__.py +0 -0
  59. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/__main__.py +0 -0
  60. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/async_storage.py +0 -0
  61. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/constants.py +0 -0
  62. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/device.py +0 -0
  63. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/device_states.py +0 -0
  64. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/factories.py +0 -0
  65. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/__init__.py +0 -0
  66. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/base.py +0 -0
  67. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/device_handlers.py +0 -0
  68. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/light_handlers.py +0 -0
  69. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/multizone_handlers.py +0 -0
  70. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/registry.py +0 -0
  71. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/handlers/tile_handlers.py +0 -0
  72. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/observers.py +0 -0
  73. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/products/__init__.py +0 -0
  74. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/products/generator.py +0 -0
  75. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/products/registry.py +0 -0
  76. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/products/specs.py +0 -0
  77. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/products/specs.yml +0 -0
  78. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/__init__.py +0 -0
  79. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/base.py +0 -0
  80. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/const.py +0 -0
  81. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/generator.py +0 -0
  82. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/header.py +0 -0
  83. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/packets.py +0 -0
  84. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/protocol_types.py +0 -0
  85. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/protocol/serializer.py +0 -0
  86. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/scenario_manager.py +0 -0
  87. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/scenario_persistence.py +0 -0
  88. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/state_restorer.py +0 -0
  89. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/state_serializer.py +0 -0
  90. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/src/lifx_emulator/storage_protocol.py +0 -0
  91. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/conftest.py +0 -0
  92. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_async_storage.py +0 -0
  93. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_cli.py +0 -0
  94. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_cli_validation.py +0 -0
  95. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_device.py +0 -0
  96. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_device_edge_cases.py +0 -0
  97. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_device_handlers_extended.py +0 -0
  98. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_handler_registry.py +0 -0
  99. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_integration.py +0 -0
  100. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_light_handlers_extended.py +0 -0
  101. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_multizone_handlers_extended.py +0 -0
  102. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_observers.py +0 -0
  103. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_products_generator.py +0 -0
  104. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_protocol_generator.py +0 -0
  105. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_protocol_types_coverage.py +0 -0
  106. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_scenario_manager.py +0 -0
  107. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_scenario_persistence.py +0 -0
  108. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_serializer.py +0 -0
  109. {lifx_emulator-1.0.0 → lifx_emulator-1.0.1}/tests/test_state_restorer.py +0 -0
  110. {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-async
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
+ [![Codecov](https://codecov.io/gh/Djelibeybi/lifx-emulator/branch/main/graph/badge.svg)](https://codecov.io/gh/Djelibeybi/lifx-emulator)
34
+ [![CI](https://github.com/Djelibeybi/lifx-emulator/actions/workflows/ci.yml/badge.svg)](https://github.com/Djelibeybi/lifx-emulator/actions/workflows/ci.yml)
35
+ [![Docs](https://github.com/Djelibeybi/lifx-emulator/workflows/Documentation/badge.svg)](https://Djelibeybi.github.io/lifx-emulator/)
36
+
37
+ [![GitHub](https://img.shields.io/github/v/release/Djelibeybi/lifx-emulator)](https://github.com/Djelibeybi/lifx-emulator/releases)
38
+ [![PyPI](https://img.shields.io/pypi/v/lifx-emulator)](https://pypi.org/project/lifx-emulator/)
39
+ [![License](https://img.shields.io/badge/License-UPL--1.0-blue.svg)](LICENSE)
40
+ [![Python](https://img.shields.io/badge/python-3.11%20|%203.12%20|%203.13%20|%203.14-blue)](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
+ [![Codecov](https://codecov.io/gh/Djelibeybi/lifx-emulator/branch/main/graph/badge.svg)](https://codecov.io/gh/Djelibeybi/lifx-emulator)
6
+ [![CI](https://github.com/Djelibeybi/lifx-emulator/actions/workflows/ci.yml/badge.svg)](https://github.com/Djelibeybi/lifx-emulator/actions/workflows/ci.yml)
7
+ [![Docs](https://github.com/Djelibeybi/lifx-emulator/workflows/Documentation/badge.svg)](https://Djelibeybi.github.io/lifx-emulator/)
8
+
9
+ [![GitHub](https://img.shields.io/github/v/release/Djelibeybi/lifx-emulator)](https://github.com/Djelibeybi/lifx-emulator/releases)
10
+ [![PyPI](https://img.shields.io/pypi/v/lifx-emulator)](https://pypi.org/project/lifx-emulator/)
11
+ [![License](https://img.shields.io/badge/License-UPL--1.0-blue.svg)](LICENSE)
12
+ [![Python](https://img.shields.io/badge/python-3.11%20|%203.12%20|%203.13%20|%203.14-blue)](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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "lifx-emulator"
3
- version = "1.0.0"
3
+ version = "1.0.1"
4
4
  description = "LIFX Emulator for testing LIFX LAN protocol libraries"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -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-async.",
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
- color_device.scenario_manager = scenario_manager
199
- server = EmulatedLifxServer([color_device], "127.0.0.1", 56700)
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
@@ -441,7 +441,7 @@ wheels = [
441
441
 
442
442
  [[package]]
443
443
  name = "lifx-emulator"
444
- version = "1.0.0"
444
+ version = "1.0.1"
445
445
  source = { editable = "." }
446
446
  dependencies = [
447
447
  { name = "cyclopts" },