lifx-emulator 1.0.0__py3-none-any.whl → 1.0.2__py3-none-any.whl

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/__main__.py CHANGED
@@ -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
- if product.has_multizone:
106
- caps.append("multizone")
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:
lifx_emulator/api.py CHANGED
@@ -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 in upgrades
100
+ # Check for extended multizone capability
101
101
  min_ext_mz_firmware = None
102
- for upgrade in product.get("upgrades", []):
103
- if upgrade.get("features", {}).get("extended_multizone"):
104
- capabilities.append("ProductCapability.EXTENDED_MULTIZONE")
105
- # Parse firmware version (major.minor format)
106
- major = upgrade.get("major", 0)
107
- minor = upgrade.get("minor", 0)
108
- min_ext_mz_firmware = (major << 16) | minor
109
- break
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 in upgrades
366
+ # Check for extended multizone capability
361
367
  min_ext_mz_firmware = None
362
- for upgrade in product.get("upgrades", []):
363
- if upgrade.get("features", {}).get("extended_multizone"):
364
- capabilities |= ProductCapability.EXTENDED_MULTIZONE
365
- # Parse firmware version (major.minor format)
366
- major = upgrade.get("major", 0)
367
- minor = upgrade.get("minor", 0)
368
- min_ext_mz_firmware = (major << 16) | minor
369
- break
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
@@ -677,7 +677,9 @@ PRODUCTS: dict[int, ProductInfo] = {
677
677
  pid=117,
678
678
  name="LIFX Z US",
679
679
  vendor=1,
680
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
680
+ capabilities=ProductCapability.COLOR
681
+ | ProductCapability.MULTIZONE
682
+ | ProductCapability.EXTENDED_MULTIZONE,
681
683
  temperature_range=TemperatureRange(min=1500, max=9000),
682
684
  min_ext_mz_firmware=None,
683
685
  ),
@@ -685,7 +687,9 @@ PRODUCTS: dict[int, ProductInfo] = {
685
687
  pid=118,
686
688
  name="LIFX Z Intl",
687
689
  vendor=1,
688
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
690
+ capabilities=ProductCapability.COLOR
691
+ | ProductCapability.MULTIZONE
692
+ | ProductCapability.EXTENDED_MULTIZONE,
689
693
  temperature_range=TemperatureRange(min=1500, max=9000),
690
694
  min_ext_mz_firmware=None,
691
695
  ),
@@ -693,7 +697,9 @@ PRODUCTS: dict[int, ProductInfo] = {
693
697
  pid=119,
694
698
  name="LIFX Beam US",
695
699
  vendor=1,
696
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
700
+ capabilities=ProductCapability.COLOR
701
+ | ProductCapability.MULTIZONE
702
+ | ProductCapability.EXTENDED_MULTIZONE,
697
703
  temperature_range=TemperatureRange(min=1500, max=9000),
698
704
  min_ext_mz_firmware=None,
699
705
  ),
@@ -701,7 +707,9 @@ PRODUCTS: dict[int, ProductInfo] = {
701
707
  pid=120,
702
708
  name="LIFX Beam Intl",
703
709
  vendor=1,
704
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
710
+ capabilities=ProductCapability.COLOR
711
+ | ProductCapability.MULTIZONE
712
+ | ProductCapability.EXTENDED_MULTIZONE,
705
713
  temperature_range=TemperatureRange(min=1500, max=9000),
706
714
  min_ext_mz_firmware=None,
707
715
  ),
@@ -853,7 +861,9 @@ PRODUCTS: dict[int, ProductInfo] = {
853
861
  pid=141,
854
862
  name="LIFX Neon US",
855
863
  vendor=1,
856
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
864
+ capabilities=ProductCapability.COLOR
865
+ | ProductCapability.MULTIZONE
866
+ | ProductCapability.EXTENDED_MULTIZONE,
857
867
  temperature_range=TemperatureRange(min=1500, max=9000),
858
868
  min_ext_mz_firmware=None,
859
869
  ),
@@ -861,7 +871,9 @@ PRODUCTS: dict[int, ProductInfo] = {
861
871
  pid=142,
862
872
  name="LIFX Neon Intl",
863
873
  vendor=1,
864
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
874
+ capabilities=ProductCapability.COLOR
875
+ | ProductCapability.MULTIZONE
876
+ | ProductCapability.EXTENDED_MULTIZONE,
865
877
  temperature_range=TemperatureRange(min=1500, max=9000),
866
878
  min_ext_mz_firmware=None,
867
879
  ),
@@ -869,7 +881,9 @@ PRODUCTS: dict[int, ProductInfo] = {
869
881
  pid=143,
870
882
  name="LIFX String US",
871
883
  vendor=1,
872
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
884
+ capabilities=ProductCapability.COLOR
885
+ | ProductCapability.MULTIZONE
886
+ | ProductCapability.EXTENDED_MULTIZONE,
873
887
  temperature_range=TemperatureRange(min=1500, max=9000),
874
888
  min_ext_mz_firmware=None,
875
889
  ),
@@ -877,7 +891,9 @@ PRODUCTS: dict[int, ProductInfo] = {
877
891
  pid=144,
878
892
  name="LIFX String Intl",
879
893
  vendor=1,
880
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
894
+ capabilities=ProductCapability.COLOR
895
+ | ProductCapability.MULTIZONE
896
+ | ProductCapability.EXTENDED_MULTIZONE,
881
897
  temperature_range=TemperatureRange(min=1500, max=9000),
882
898
  min_ext_mz_firmware=None,
883
899
  ),
@@ -885,7 +901,9 @@ PRODUCTS: dict[int, ProductInfo] = {
885
901
  pid=161,
886
902
  name="LIFX Outdoor Neon US",
887
903
  vendor=1,
888
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
904
+ capabilities=ProductCapability.COLOR
905
+ | ProductCapability.MULTIZONE
906
+ | ProductCapability.EXTENDED_MULTIZONE,
889
907
  temperature_range=TemperatureRange(min=1500, max=9000),
890
908
  min_ext_mz_firmware=None,
891
909
  ),
@@ -893,7 +911,9 @@ PRODUCTS: dict[int, ProductInfo] = {
893
911
  pid=162,
894
912
  name="LIFX Outdoor Neon Intl",
895
913
  vendor=1,
896
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
914
+ capabilities=ProductCapability.COLOR
915
+ | ProductCapability.MULTIZONE
916
+ | ProductCapability.EXTENDED_MULTIZONE,
897
917
  temperature_range=TemperatureRange(min=1500, max=9000),
898
918
  min_ext_mz_firmware=None,
899
919
  ),
@@ -1101,7 +1121,9 @@ PRODUCTS: dict[int, ProductInfo] = {
1101
1121
  pid=203,
1102
1122
  name="LIFX String US",
1103
1123
  vendor=1,
1104
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
1124
+ capabilities=ProductCapability.COLOR
1125
+ | ProductCapability.MULTIZONE
1126
+ | ProductCapability.EXTENDED_MULTIZONE,
1105
1127
  temperature_range=TemperatureRange(min=1500, max=9000),
1106
1128
  min_ext_mz_firmware=None,
1107
1129
  ),
@@ -1109,7 +1131,9 @@ PRODUCTS: dict[int, ProductInfo] = {
1109
1131
  pid=204,
1110
1132
  name="LIFX String Intl",
1111
1133
  vendor=1,
1112
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
1134
+ capabilities=ProductCapability.COLOR
1135
+ | ProductCapability.MULTIZONE
1136
+ | ProductCapability.EXTENDED_MULTIZONE,
1113
1137
  temperature_range=TemperatureRange(min=1500, max=9000),
1114
1138
  min_ext_mz_firmware=None,
1115
1139
  ),
@@ -1117,7 +1141,9 @@ PRODUCTS: dict[int, ProductInfo] = {
1117
1141
  pid=205,
1118
1142
  name="LIFX Indoor Neon US",
1119
1143
  vendor=1,
1120
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
1144
+ capabilities=ProductCapability.COLOR
1145
+ | ProductCapability.MULTIZONE
1146
+ | ProductCapability.EXTENDED_MULTIZONE,
1121
1147
  temperature_range=TemperatureRange(min=1500, max=9000),
1122
1148
  min_ext_mz_firmware=None,
1123
1149
  ),
@@ -1125,7 +1151,9 @@ PRODUCTS: dict[int, ProductInfo] = {
1125
1151
  pid=206,
1126
1152
  name="LIFX Indoor Neon Intl",
1127
1153
  vendor=1,
1128
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
1154
+ capabilities=ProductCapability.COLOR
1155
+ | ProductCapability.MULTIZONE
1156
+ | ProductCapability.EXTENDED_MULTIZONE,
1129
1157
  temperature_range=TemperatureRange(min=1500, max=9000),
1130
1158
  min_ext_mz_firmware=None,
1131
1159
  ),
@@ -1133,7 +1161,9 @@ PRODUCTS: dict[int, ProductInfo] = {
1133
1161
  pid=213,
1134
1162
  name="LIFX Permanent Outdoor US",
1135
1163
  vendor=1,
1136
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
1164
+ capabilities=ProductCapability.COLOR
1165
+ | ProductCapability.MULTIZONE
1166
+ | ProductCapability.EXTENDED_MULTIZONE,
1137
1167
  temperature_range=TemperatureRange(min=1500, max=9000),
1138
1168
  min_ext_mz_firmware=None,
1139
1169
  ),
@@ -1141,7 +1171,9 @@ PRODUCTS: dict[int, ProductInfo] = {
1141
1171
  pid=214,
1142
1172
  name="LIFX Permanent Outdoor Intl",
1143
1173
  vendor=1,
1144
- capabilities=ProductCapability.COLOR | ProductCapability.MULTIZONE,
1174
+ capabilities=ProductCapability.COLOR
1175
+ | ProductCapability.MULTIZONE
1176
+ | ProductCapability.EXTENDED_MULTIZONE,
1145
1177
  temperature_range=TemperatureRange(min=1500, max=9000),
1146
1178
  min_ext_mz_firmware=None,
1147
1179
  ),
@@ -1300,16 +1332,22 @@ class ProductRegistry:
1300
1332
  if features.get("hev"):
1301
1333
  capabilities |= ProductCapability.HEV
1302
1334
 
1303
- # Check for extended multizone in upgrades
1335
+ # Check for extended multizone capability
1304
1336
  min_ext_mz_firmware = None
1305
- for upgrade in product.get("upgrades", []):
1306
- if upgrade.get("features", {}).get("extended_multizone"):
1307
- capabilities |= ProductCapability.EXTENDED_MULTIZONE
1308
- # Parse firmware version (major.minor format)
1309
- major = upgrade.get("major", 0)
1310
- minor = upgrade.get("minor", 0)
1311
- min_ext_mz_firmware = (major << 16) | minor
1312
- break
1337
+
1338
+ # First check if it's a native feature (no firmware requirement)
1339
+ if features.get("extended_multizone"):
1340
+ capabilities |= ProductCapability.EXTENDED_MULTIZONE
1341
+ else:
1342
+ # Check if it's available as an upgrade (requires minimum firmware)
1343
+ for upgrade in product.get("upgrades", []):
1344
+ if upgrade.get("features", {}).get("extended_multizone"):
1345
+ capabilities |= ProductCapability.EXTENDED_MULTIZONE
1346
+ # Parse firmware version (major.minor format)
1347
+ major = upgrade.get("major", 0)
1348
+ minor = upgrade.get("minor", 0)
1349
+ min_ext_mz_firmware = (major << 16) | minor
1350
+ break
1313
1351
 
1314
1352
  # Parse temperature range
1315
1353
  temp_range = None
lifx_emulator/server.py CHANGED
@@ -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
@@ -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
+ [![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
@@ -1,6 +1,6 @@
1
1
  lifx_emulator/__init__.py,sha256=WzD3GRp_QFlYF9IexitPxuTVyOUDNv2ZApKhfRP0UGA,818
2
- lifx_emulator/__main__.py,sha256=gfx_SW0Vywlvt68KmozYuZMRWiTow0rgPmeiVHoTfHg,22437
3
- lifx_emulator/api.py,sha256=AVtaLYIjTtYJ-FL19Ny9WyvCOJHqfPmP5Tb1B_Gs20M,66810
2
+ lifx_emulator/__main__.py,sha256=Irr5g-OBIoWn6shPzfWIPjJSVM2Sq5Y1R1ETxdhCjx8,22570
3
+ lifx_emulator/api.py,sha256=ATVLtPNh-9TySn_orFV03vJvZ20D5VZyuVQPgvzUuFg,67416
4
4
  lifx_emulator/async_storage.py,sha256=-J4s_4Cxika_EDlFjBegKAaM8sY78wbTDWnttWH4Eik,10504
5
5
  lifx_emulator/constants.py,sha256=DFZkUsdewE-x_3MgO28tMGkjUCWPeYc3xLj_EXViGOw,1032
6
6
  lifx_emulator/device.py,sha256=qaTNgeyvg1S-g1rC91NnQpvDe4p1razMNVDTlv0vKMI,24109
@@ -9,7 +9,7 @@ lifx_emulator/factories.py,sha256=vLsGFuP8E_9bbdo2Sry3G6Ax6qSl5QfpXsRfXE3SfBU,13
9
9
  lifx_emulator/observers.py,sha256=-KnUgFcKdhlNo7CNVstP-u0wU2W0JAGg055ZPV15Sj0,3874
10
10
  lifx_emulator/scenario_manager.py,sha256=xv2NwdZjDcF83MTt7JjKgDvua6zx6SkGQT1P4156-RU,14154
11
11
  lifx_emulator/scenario_persistence.py,sha256=oytK5W2si1N-O31jBACHhGQyo5IrINlqfWE0K2bZq_8,7394
12
- lifx_emulator/server.py,sha256=Puw5OG1hVauv_EReu9Kp5i6S7q78H_6AT-X0zcx1kEs,16953
12
+ lifx_emulator/server.py,sha256=mWkssl42Z_5m78DAn5_-DRACLGvymSbouTE4j8dVAXA,17239
13
13
  lifx_emulator/state_restorer.py,sha256=isgCsmcjxc4aQrTXoIYr_FoIU4H5Swg7GNaoh_-X7zU,9793
14
14
  lifx_emulator/state_serializer.py,sha256=O4Cp3bbGkd4eZf5jzb0MKzWDTgiNhrSGgypmMWaB4dg,5097
15
15
  lifx_emulator/storage_protocol.py,sha256=j5wC4gI-2DtPgmY4kM3mKDI6OYpmiNpGulzdJnZnqYY,2929
@@ -21,8 +21,8 @@ lifx_emulator/handlers/multizone_handlers.py,sha256=949FEt1Ilhp-LCxxY0xfHxYJccGN
21
21
  lifx_emulator/handlers/registry.py,sha256=s1ht4PmPhXhAcwu1hoY4yW39wy3SPJBMY-9Uxd0FWuE,3292
22
22
  lifx_emulator/handlers/tile_handlers.py,sha256=jzguqNr_aZheuqd4mikfQlkRcVeqs3HAEAseFSlkBlE,10038
23
23
  lifx_emulator/products/__init__.py,sha256=qcNop_kRYFF3zSjNemzQEgu3jPrIxfyQyLv9GsnaLEI,627
24
- lifx_emulator/products/generator.py,sha256=N8dNDTX9eoDtB3Nb02zHnLyooUR396GdjWGI4_I-Dls,25742
25
- lifx_emulator/products/registry.py,sha256=zNF3Nzzaj3Rlrs_YdI-KpDGCj3jA_zpoqFmcaD3_LJo,44526
24
+ lifx_emulator/products/generator.py,sha256=ovXSwh-3bFNeNJJntYDSURUrzM-WAtXeGKI0-sP1miI,26431
25
+ lifx_emulator/products/registry.py,sha256=oKO7Nb4KsWMMc3bkr5FWKgdXsi4xbrRCD9xP27rQPPQ,45757
26
26
  lifx_emulator/products/specs.py,sha256=2bpgr2z2Ugb_0IBl9LY8LGotB031Bim8zizwvtviFZ0,7186
27
27
  lifx_emulator/products/specs.yml,sha256=uxzdKFREAHphk8XSPiCHvQE2vwoPfT2m1xy-zC4ZIl4,8552
28
28
  lifx_emulator/protocol/__init__.py,sha256=-wjC-wBcb7fxi5I-mJr2Ad8K2YRflJFdLLdobfD-W1Q,56
@@ -33,8 +33,8 @@ lifx_emulator/protocol/header.py,sha256=RXMJ5YZG1jyxl4Mz46ZGJBYX41Jdp7J95BHuY-sc
33
33
  lifx_emulator/protocol/packets.py,sha256=_xYYPuu6WfBpg1LwYVdzQdytFwTyMqpZRadmDdwZXWk,41567
34
34
  lifx_emulator/protocol/protocol_types.py,sha256=2Mccm9717EuTXQYaW44W_yReI4EtnlPp3-WEVASgdGY,24820
35
35
  lifx_emulator/protocol/serializer.py,sha256=2bZz7TddxaMRO4_6LujRGCS1w7GxD4E3rRk3r-hpEIE,10738
36
- lifx_emulator-1.0.0.dist-info/METADATA,sha256=6ULy_R-nnswf1Oa3bKMfgIZePm6ZrHrOil7rUjdWx4I,13190
37
- lifx_emulator-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
38
- lifx_emulator-1.0.0.dist-info/entry_points.txt,sha256=R9C_K_tTgt6yXEmhzH4r2Yx2Tu1rLlnYzeG4RFUVzSc,62
39
- lifx_emulator-1.0.0.dist-info/licenses/LICENSE,sha256=eBz48GRA3gSiWn3rYZAz2Ewp35snnhV9cSqkVBq7g3k,1832
40
- lifx_emulator-1.0.0.dist-info/RECORD,,
36
+ lifx_emulator-1.0.2.dist-info/METADATA,sha256=gASUaAnvN-dLyu3rBoyYb4lYq_guVFwTXznmTOzAAvM,4549
37
+ lifx_emulator-1.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
38
+ lifx_emulator-1.0.2.dist-info/entry_points.txt,sha256=R9C_K_tTgt6yXEmhzH4r2Yx2Tu1rLlnYzeG4RFUVzSc,62
39
+ lifx_emulator-1.0.2.dist-info/licenses/LICENSE,sha256=eBz48GRA3gSiWn3rYZAz2Ewp35snnhV9cSqkVBq7g3k,1832
40
+ lifx_emulator-1.0.2.dist-info/RECORD,,
@@ -1,445 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: lifx-emulator
3
- Version: 1.0.0
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
- [![License](https://img.shields.io/badge/License-UPL--1.0-blue.svg)](LICENSE)
34
- [![Python](https://img.shields.io/badge/Python-3.13+-blue.svg)](https://www.python.org/downloads/)
35
-
36
- ## Overview
37
-
38
- LIFX Emulator implements the complete binary UDP protocol from [lan.developer.lifx.com](https://lan.developer.lifx.com), providing virtual LIFX devices for testing without physical hardware.
39
-
40
- ## Features
41
-
42
- - **Complete Protocol Support**: 44+ packet types from the LIFX LAN protocol
43
- - **Multiple Device Types**: Color lights, infrared, HEV, multizone strips, matrix tiles
44
- - **Product Registry**: 40+ official LIFX product definitions with accurate defaults
45
- - **Testing Scenarios**: Built-in support for packet loss, delays, malformed responses
46
- - **Easy Integration**: Simple Python API and comprehensive CLI
47
- - **Zero Dependencies**: Pure Python with only PyYAML for configuration
48
-
49
- ## Quick Start
50
-
51
- ### Installation
52
-
53
- ```bash
54
- pip install lifx-emulator
55
- ```
56
-
57
- ### CLI Usage
58
-
59
- ```bash
60
- # Start with default configuration (1 color light)
61
- lifx-emulator
62
-
63
- # Create multiple device types with verbose logging
64
- lifx-emulator --color 2 --multizone 1 --tile 1 --verbose
65
-
66
- # Use specific products from registry
67
- lifx-emulator --product 27 --product 32 --product 55
68
-
69
- # List all available products
70
- lifx-emulator list-products
71
- ```
72
-
73
- ### Python API
74
-
75
- ```python
76
- import asyncio
77
- from lifx_emulator import EmulatedLifxServer, create_color_light
78
-
79
- async def main():
80
- # Create emulated device
81
- device = create_color_light("d073d5000001")
82
-
83
- # Start server
84
- async with EmulatedLifxServer([device], "127.0.0.1", 56700) as server:
85
- print(f"Server running with device: {device.state.label}")
86
- await asyncio.Event().wait()
87
-
88
- asyncio.run(main())
89
- ```
90
-
91
- ### Integration Testing
92
-
93
- ```python
94
- import pytest
95
- from lifx_emulator import EmulatedLifxServer, create_color_light
96
- from your_lifx_library import LifxClient
97
-
98
- @pytest.mark.asyncio
99
- async def test_discover_devices():
100
- # Create emulated device
101
- device = create_color_light("d073d5000001")
102
-
103
- # Start emulator
104
- async with EmulatedLifxServer([device], "127.0.0.1", 56700):
105
- # Test your library
106
- client = LifxClient()
107
- await client.discover(port=56700)
108
-
109
- assert len(client.devices) == 1
110
- assert client.devices[0].mac == "d073d5000001"
111
- ```
112
-
113
- ## Supported Device Types
114
-
115
- | Device Type | Factory Function | Example Product |
116
- |------------|------------------|-----------------|
117
- | Color Lights | `create_color_light()` | LIFX A19 |
118
- | Color Temperature | `create_color_temperature_light()` | LIFX Mini White to Warm |
119
- | Infrared | `create_infrared_light()` | LIFX A19 Night Vision |
120
- | HEV | `create_hev_light()` | LIFX Clean |
121
- | Multizone | `create_multizone_light()` | LIFX Z, LIFX Beam |
122
- | Matrix Tiles | `create_tile_device()` | LIFX Tile |
123
-
124
- [See all 40+ supported products →](https://lifx-emulator.readthedocs.io/en/latest/guide/device-types/)
125
-
126
- ## Documentation
127
-
128
- - **[Installation Guide](https://lifx-emulator.readthedocs.io/en/latest/getting-started/installation/)** - Get started
129
- - **[Quick Start](https://lifx-emulator.readthedocs.io/en/latest/getting-started/quickstart/)** - Your first emulated device
130
- - **[CLI Reference](https://lifx-emulator.readthedocs.io/en/latest/getting-started/cli/)** - All CLI options
131
- - **[Device Types](https://lifx-emulator.readthedocs.io/en/latest/guide/device-types/)** - Supported devices
132
- - **[API Reference](https://lifx-emulator.readthedocs.io/en/latest/api/)** - Complete API docs
133
- - **[Architecture](https://lifx-emulator.readthedocs.io/en/latest/architecture/overview/)** - How it works
134
-
135
- ## CLI Examples
136
-
137
- ### Basic Usage
138
-
139
- ```bash
140
- # Single color light on default port 56700
141
- lifx-emulator
142
-
143
- # Multiple devices with verbose logging
144
- lifx-emulator --color 2 --multizone 1 --tile 1 --verbose
145
- ```
146
-
147
- ### Advanced Usage
148
-
149
- ```bash
150
- # Extended multizone (LIFX Beam) with custom zone count
151
- lifx-emulator --multizone 1 --multizone-extended --multizone-zones 60
152
-
153
- # Specific products by ID
154
- lifx-emulator --product 27 --product 32 --product 55
155
-
156
- # Custom port and localhost only
157
- lifx-emulator --bind 127.0.0.1 --port 56701
158
-
159
- # Filter products list
160
- lifx-emulator list-products --filter-type multizone
161
- ```
162
-
163
- ## Python API Examples
164
-
165
- ### Multiple Device Types
166
-
167
- ```python
168
- from lifx_emulator import (
169
- EmulatedLifxServer,
170
- create_color_light,
171
- create_multizone_light,
172
- create_tile_device,
173
- )
174
-
175
- devices = [
176
- create_color_light("d073d5000001"),
177
- create_multizone_light("d073d8000001", zone_count=16),
178
- create_tile_device("d073d9000001", tile_count=5),
179
- ]
180
-
181
- async with EmulatedLifxServer(devices, "127.0.0.1", 56700) as server:
182
- # All devices are discoverable and controllable
183
- print(f"Emulating {len(devices)} devices")
184
- await asyncio.Event().wait()
185
- ```
186
-
187
- ### Testing Scenarios
188
-
189
- ```python
190
- # Configure error scenarios for testing
191
- device = create_color_light("d073d5000001")
192
- device.scenarios = {
193
- 'drop_packets': [101], # Drop LightGet packets
194
- 'response_delays': {102: 0.5}, # Delay SetColor by 500ms
195
- 'malformed_packets': [107], # Truncate StateLight
196
- }
197
-
198
- async with EmulatedLifxServer([device], "127.0.0.1", 56700) as server:
199
- # Test your library's error handling
200
- pass
201
- ```
202
-
203
- ## Use Cases
204
-
205
- - **Library Testing**: Test your LIFX library without physical devices
206
- - **CI/CD Integration**: Run automated tests in pipelines
207
- - **Protocol Development**: Experiment with LIFX protocol features
208
- - **Error Simulation**: Test error handling with configurable scenarios
209
- - **Performance Testing**: Test concurrent device handling
210
-
211
- ## Requirements
212
-
213
- - Python 3.13+
214
- - PyYAML (automatically installed)
215
-
216
- ## Performance
217
-
218
- The emulator includes comprehensive performance optimization with **51% average throughput improvement**:
219
-
220
- - Complete benchmarking and profiling suite
221
- - Detailed optimization analysis and recommendations
222
- - Before/after performance comparison
223
-
224
- **Key Results:**
225
- - **Packet Processing**: +51% (35K → 53K pkt/s)
226
- - **Serialization**: +77% (27K → 48K pkt/s)
227
- - **Latency**: -35% to -44% reduction
228
-
229
- See the [Performance Documentation](docs/performance/index.md) for details.
230
-
231
- ### Run Performance Benchmarks
232
-
233
- ```bash
234
- uv run python tools/performance/benchmark.py
235
- uv run python tools/performance/profiler.py
236
- ```
237
-
238
- ## Development
239
-
240
- ```bash
241
- # Clone repository
242
- git clone https://github.com/Djelibeybi/lifx-emulator.git
243
- cd lifx-emulator
244
-
245
- # Install with uv (recommended)
246
- uv sync
247
-
248
- # Or with pip
249
- pip install -e ".[dev]"
250
-
251
- # Run tests
252
- uv run pytest
253
-
254
- # Run linter
255
- uv run ruff check .
256
-
257
- # Build docs
258
- uv run mkdocs serve
259
- ```
260
-
261
- ### Documentation Tools
262
-
263
- The emulator includes automated tools for documentation generation, validation, and quality checking:
264
-
265
- #### 1. API Reference Generator (`generate_api_reference.py`)
266
-
267
- Auto-generates API reference documentation from Python source code with docstrings, type hints, and inheritance diagrams.
268
-
269
- ```bash
270
- # Generate API reference for a module
271
- python tools/docs/generate_api_reference.py src/lifx_emulator/device.py > docs/api/device.md
272
-
273
- # With output file
274
- python tools/docs/generate_api_reference.py src/lifx_emulator/storage.py --output docs/api/storage.md
275
- ```
276
-
277
- #### 2. Diagram Generator (`generate_diagrams.py`)
278
-
279
- Auto-generates architecture diagrams from code analysis (component, packet flow, state machine, handler flow).
280
-
281
- ```bash
282
- # Generate all diagrams
283
- python tools/docs/generate_diagrams.py --type all --output docs/architecture/diagrams/
284
-
285
- # Generate specific diagram type
286
- python tools/docs/generate_diagrams.py --type component
287
- python tools/docs/generate_diagrams.py --type packet-flow
288
- ```
289
-
290
- #### 3. Example Validator (`validate_examples.py`)
291
-
292
- Validates that code examples in documentation actually work by extracting and compiling Python code blocks.
293
-
294
- ```bash
295
- # Validate all examples in docs/
296
- python tools/docs/validate_examples.py docs/
297
-
298
- # Validate with verbose output
299
- python tools/docs/validate_examples.py docs/tutorials/ --verbose
300
- ```
301
-
302
- #### 4. Coverage Reporter (`coverage_report.py`)
303
-
304
- Tracks documentation coverage across the codebase and enforces minimum coverage thresholds.
305
-
306
- ```bash
307
- # Generate coverage report
308
- python tools/docs/coverage_report.py
309
-
310
- # Require minimum coverage
311
- python tools/docs/coverage_report.py --min-coverage 80
312
-
313
- # Generate HTML report
314
- python tools/docs/coverage_report.py --html --output coverage.html
315
- ```
316
-
317
- #### 5. Terminology Checker (`check_terminology.py`)
318
-
319
- Validates consistent terminology usage across documentation against defined glossary.
320
-
321
- ```bash
322
- # Check terminology
323
- python tools/docs/check_terminology.py docs/
324
-
325
- # Auto-fix violations
326
- python tools/docs/check_terminology.py docs/ --fix
327
- ```
328
-
329
- #### Documentation Workflow
330
-
331
- When writing documentation:
332
-
333
- 1. **Write content** with code examples
334
- 2. **Run validation locally:**
335
- ```bash
336
- # Check terminology
337
- python tools/docs/check_terminology.py docs/ --fix
338
-
339
- # Validate examples
340
- python tools/docs/validate_examples.py docs/ --verbose
341
-
342
- # Check coverage
343
- python tools/docs/coverage_report.py
344
- ```
345
-
346
- 3. **Auto-generate API reference:**
347
- ```bash
348
- python tools/docs/generate_api_reference.py src/lifx_emulator/device.py --output docs/api/device.md
349
- ```
350
-
351
- 4. **Generate diagrams:**
352
- ```bash
353
- python tools/docs/generate_diagrams.py --type all --output docs/architecture/diagrams/
354
- ```
355
-
356
- 5. **Commit changes** - CI will validate everything
357
-
358
- The `.github/workflows/docs.yml` workflow runs all validation tools on pull requests and pushes to main.
359
-
360
- See [Documentation Tools](docs/development/documentation-tools.md) for detailed documentation tool usage and troubleshooting.
361
-
362
- ## Product Registry
363
-
364
- The emulator includes an auto-generated registry of all official LIFX products with accurate defaults:
365
-
366
- ```python
367
- from lifx_emulator.products.registry import get_product, get_registry
368
-
369
- # Get specific product
370
- product = get_product(27) # LIFX A19
371
- print(f"{product.name}: {product.pid}")
372
- print(f"Capabilities: color={product.has_color}, multizone={product.has_multizone}")
373
-
374
- # List all products
375
- registry = get_registry()
376
- for pid, product in registry.products.items():
377
- print(f"PID {pid}: {product.name}")
378
- ```
379
-
380
- ## Testing Scenarios
381
-
382
- Configure devices to simulate real-world issues:
383
-
384
- ```python
385
- device.scenarios = {
386
- # Packet dropping (simulate packet loss)
387
- 'drop_packets': [101, 102],
388
-
389
- # Response delays (simulate latency)
390
- 'response_delays': {102: 0.5, 107: 1.0},
391
-
392
- # Malformed packets (test error handling)
393
- 'malformed_packets': [107],
394
-
395
- # Invalid field values (test validation)
396
- 'invalid_field_values': {22: True},
397
-
398
- # Partial responses (test timeout handling)
399
- 'partial_responses': [506],
400
- }
401
- ```
402
-
403
- ## Architecture
404
-
405
- ```
406
- ┌─────────────────┐
407
- │ LIFX Client │
408
- │ Library │
409
- └────────┬────────┘
410
- │ UDP Packets
411
-
412
- ┌─────────────────┐
413
- │ EmulatedLifx │
414
- │ Server │
415
- └────────┬────────┘
416
- │ Route by MAC
417
-
418
- ┌─────────────────┐ ┌──────────────┐
419
- │ EmulatedLifx │────▶│ DeviceState │
420
- │ Device │ │ (Stateful) │
421
- └────────┬────────┘ └──────────────┘
422
-
423
-
424
- ┌─────────────────┐
425
- │ Protocol Layer │
426
- │ (44+ packets) │
427
- └─────────────────┘
428
- ```
429
-
430
- [Learn more about the architecture →](https://lifx-emulator.readthedocs.io/en/latest/architecture/overview/)
431
-
432
- ## Contributing
433
-
434
- Contributions are welcome! Please see our [Contributing Guide](https://lifx-emulator.readthedocs.io/en/latest/development/contributing/) for details.
435
-
436
- ## License
437
-
438
- [UPL-1.0](LICENSE)
439
-
440
- ## Links
441
-
442
- - **Documentation**: https://lifx-emulator.readthedocs.io
443
- - **GitHub**: https://github.com/Djelibeybi/lifx-emulator
444
- - **PyPI**: https://pypi.org/project/lifx-emulator/
445
- - **LIFX Protocol**: https://lan.developer.lifx.com