lifx-emulator 2.4.0__py3-none-any.whl → 3.0.1__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.
Files changed (68) hide show
  1. lifx_emulator-3.0.1.dist-info/METADATA +102 -0
  2. lifx_emulator-3.0.1.dist-info/RECORD +18 -0
  3. lifx_emulator-3.0.1.dist-info/entry_points.txt +2 -0
  4. lifx_emulator_app/__init__.py +10 -0
  5. {lifx_emulator → lifx_emulator_app}/__main__.py +2 -3
  6. {lifx_emulator → lifx_emulator_app}/api/__init__.py +1 -1
  7. {lifx_emulator → lifx_emulator_app}/api/app.py +3 -3
  8. {lifx_emulator → lifx_emulator_app}/api/mappers/__init__.py +1 -1
  9. {lifx_emulator → lifx_emulator_app}/api/mappers/device_mapper.py +1 -1
  10. {lifx_emulator → lifx_emulator_app}/api/models.py +1 -2
  11. lifx_emulator_app/api/routers/__init__.py +11 -0
  12. {lifx_emulator → lifx_emulator_app}/api/routers/devices.py +2 -2
  13. {lifx_emulator → lifx_emulator_app}/api/routers/monitoring.py +1 -1
  14. {lifx_emulator → lifx_emulator_app}/api/routers/scenarios.py +1 -1
  15. lifx_emulator_app/api/services/__init__.py +8 -0
  16. {lifx_emulator → lifx_emulator_app}/api/services/device_service.py +3 -2
  17. lifx_emulator/__init__.py +0 -31
  18. lifx_emulator/api/routers/__init__.py +0 -11
  19. lifx_emulator/api/services/__init__.py +0 -8
  20. lifx_emulator/constants.py +0 -33
  21. lifx_emulator/devices/__init__.py +0 -37
  22. lifx_emulator/devices/device.py +0 -395
  23. lifx_emulator/devices/manager.py +0 -256
  24. lifx_emulator/devices/observers.py +0 -139
  25. lifx_emulator/devices/persistence.py +0 -308
  26. lifx_emulator/devices/state_restorer.py +0 -259
  27. lifx_emulator/devices/state_serializer.py +0 -157
  28. lifx_emulator/devices/states.py +0 -381
  29. lifx_emulator/factories/__init__.py +0 -39
  30. lifx_emulator/factories/builder.py +0 -375
  31. lifx_emulator/factories/default_config.py +0 -158
  32. lifx_emulator/factories/factory.py +0 -252
  33. lifx_emulator/factories/firmware_config.py +0 -77
  34. lifx_emulator/factories/serial_generator.py +0 -82
  35. lifx_emulator/handlers/__init__.py +0 -39
  36. lifx_emulator/handlers/base.py +0 -49
  37. lifx_emulator/handlers/device_handlers.py +0 -322
  38. lifx_emulator/handlers/light_handlers.py +0 -503
  39. lifx_emulator/handlers/multizone_handlers.py +0 -249
  40. lifx_emulator/handlers/registry.py +0 -110
  41. lifx_emulator/handlers/tile_handlers.py +0 -488
  42. lifx_emulator/products/__init__.py +0 -28
  43. lifx_emulator/products/generator.py +0 -1079
  44. lifx_emulator/products/registry.py +0 -1530
  45. lifx_emulator/products/specs.py +0 -284
  46. lifx_emulator/products/specs.yml +0 -386
  47. lifx_emulator/protocol/__init__.py +0 -1
  48. lifx_emulator/protocol/base.py +0 -446
  49. lifx_emulator/protocol/const.py +0 -8
  50. lifx_emulator/protocol/generator.py +0 -1384
  51. lifx_emulator/protocol/header.py +0 -159
  52. lifx_emulator/protocol/packets.py +0 -1351
  53. lifx_emulator/protocol/protocol_types.py +0 -817
  54. lifx_emulator/protocol/serializer.py +0 -379
  55. lifx_emulator/repositories/__init__.py +0 -22
  56. lifx_emulator/repositories/device_repository.py +0 -155
  57. lifx_emulator/repositories/storage_backend.py +0 -107
  58. lifx_emulator/scenarios/__init__.py +0 -22
  59. lifx_emulator/scenarios/manager.py +0 -322
  60. lifx_emulator/scenarios/models.py +0 -112
  61. lifx_emulator/scenarios/persistence.py +0 -241
  62. lifx_emulator/server.py +0 -464
  63. lifx_emulator-2.4.0.dist-info/METADATA +0 -107
  64. lifx_emulator-2.4.0.dist-info/RECORD +0 -62
  65. lifx_emulator-2.4.0.dist-info/entry_points.txt +0 -2
  66. lifx_emulator-2.4.0.dist-info/licenses/LICENSE +0 -35
  67. {lifx_emulator-2.4.0.dist-info → lifx_emulator-3.0.1.dist-info}/WHEEL +0 -0
  68. {lifx_emulator → lifx_emulator_app}/api/templates/dashboard.html +0 -0
@@ -1,252 +0,0 @@
1
- """Factory functions for creating emulated LIFX devices."""
2
-
3
- from __future__ import annotations
4
-
5
- from typing import TYPE_CHECKING
6
-
7
- from lifx_emulator.devices import EmulatedLifxDevice
8
- from lifx_emulator.factories.builder import DeviceBuilder
9
- from lifx_emulator.products.registry import get_product
10
-
11
- if TYPE_CHECKING:
12
- from lifx_emulator.devices import DevicePersistenceAsyncFile
13
- from lifx_emulator.scenarios import HierarchicalScenarioManager
14
-
15
-
16
- def create_color_light(
17
- serial: str | None = None,
18
- firmware_version: tuple[int, int] | None = None,
19
- storage: DevicePersistenceAsyncFile | None = None,
20
- scenario_manager: HierarchicalScenarioManager | None = None,
21
- ) -> EmulatedLifxDevice:
22
- """Create a regular color light (LIFX Color)"""
23
- return create_device(
24
- 91,
25
- serial=serial,
26
- firmware_version=firmware_version,
27
- storage=storage,
28
- scenario_manager=scenario_manager,
29
- ) # LIFX Color
30
-
31
-
32
- def create_infrared_light(
33
- serial: str | None = None,
34
- firmware_version: tuple[int, int] | None = None,
35
- storage: DevicePersistenceAsyncFile | None = None,
36
- scenario_manager: HierarchicalScenarioManager | None = None,
37
- ) -> EmulatedLifxDevice:
38
- """Create an infrared-enabled light (LIFX A19 Night Vision)"""
39
- return create_device(
40
- 29,
41
- serial=serial,
42
- firmware_version=firmware_version,
43
- storage=storage,
44
- scenario_manager=scenario_manager,
45
- ) # LIFX A19 Night Vision
46
-
47
-
48
- def create_hev_light(
49
- serial: str | None = None,
50
- firmware_version: tuple[int, int] | None = None,
51
- storage: DevicePersistenceAsyncFile | None = None,
52
- scenario_manager: HierarchicalScenarioManager | None = None,
53
- ) -> EmulatedLifxDevice:
54
- """Create an HEV-enabled light (LIFX Clean)"""
55
- return create_device(
56
- 90,
57
- serial=serial,
58
- firmware_version=firmware_version,
59
- storage=storage,
60
- scenario_manager=scenario_manager,
61
- ) # LIFX Clean
62
-
63
-
64
- def create_multizone_light(
65
- serial: str | None = None,
66
- zone_count: int | None = None,
67
- extended_multizone: bool = True,
68
- firmware_version: tuple[int, int] | None = None,
69
- storage: DevicePersistenceAsyncFile | None = None,
70
- scenario_manager: HierarchicalScenarioManager | None = None,
71
- ) -> EmulatedLifxDevice:
72
- """Create a multizone light (LIFX Beam)
73
-
74
- Args:
75
- serial: Optional serial
76
- zone_count: Optional zone count (uses product default if not specified)
77
- extended_multizone: enables support for extended multizone requests
78
- firmware_version: Optional firmware version tuple (major, minor)
79
- storage: Optional storage for persistence
80
- scenario_manager: Optional scenario manager
81
- """
82
- return create_device(
83
- 38,
84
- serial=serial,
85
- zone_count=zone_count,
86
- extended_multizone=extended_multizone,
87
- firmware_version=firmware_version,
88
- storage=storage,
89
- scenario_manager=scenario_manager,
90
- )
91
-
92
-
93
- def create_tile_device(
94
- serial: str | None = None,
95
- tile_count: int | None = None,
96
- tile_width: int | None = None,
97
- tile_height: int | None = None,
98
- firmware_version: tuple[int, int] | None = None,
99
- storage: DevicePersistenceAsyncFile | None = None,
100
- scenario_manager: HierarchicalScenarioManager | None = None,
101
- ) -> EmulatedLifxDevice:
102
- """Create a tile device (LIFX Tile)
103
-
104
- Args:
105
- serial: Optional serial
106
- tile_count: Optional tile count (uses product default)
107
- tile_width: Optional tile width in zones (uses product default)
108
- tile_height: Optional tile height in zones (uses product default)
109
- firmware_version: Optional firmware version tuple (major, minor)
110
- storage: Optional storage for persistence
111
- scenario_manager: Optional scenario manager
112
- """
113
- return create_device(
114
- 55,
115
- serial=serial,
116
- tile_count=tile_count,
117
- tile_width=tile_width,
118
- tile_height=tile_height,
119
- firmware_version=firmware_version,
120
- storage=storage,
121
- scenario_manager=scenario_manager,
122
- ) # LIFX Tile
123
-
124
-
125
- def create_color_temperature_light(
126
- serial: str | None = None,
127
- firmware_version: tuple[int, int] | None = None,
128
- storage: DevicePersistenceAsyncFile | None = None,
129
- scenario_manager: HierarchicalScenarioManager | None = None,
130
- ) -> EmulatedLifxDevice:
131
- """Create a color temperature light (LIFX Mini White to Warm).
132
-
133
- Variable color temperature, no RGB.
134
- """
135
- return create_device(
136
- 50,
137
- serial=serial,
138
- firmware_version=firmware_version,
139
- storage=storage,
140
- scenario_manager=scenario_manager,
141
- ) # LIFX Mini White to Warm
142
-
143
-
144
- def create_switch(
145
- serial: str | None = None,
146
- product_id: int = 70,
147
- firmware_version: tuple[int, int] | None = None,
148
- storage: DevicePersistenceAsyncFile | None = None,
149
- scenario_manager: HierarchicalScenarioManager | None = None,
150
- ) -> EmulatedLifxDevice:
151
- """Create a LIFX Switch device.
152
-
153
- Switches have has_relays and has_buttons capabilities but no lighting.
154
- They respond with StateUnhandled (223) to Light, MultiZone, and Tile packets.
155
-
156
- Args:
157
- serial: Device serial number (auto-generated if None)
158
- product_id: Switch product ID (default: 70 - LIFX Switch)
159
- firmware_version: Optional firmware version (major, minor)
160
- storage: Optional persistence backend
161
- scenario_manager: Optional scenario manager for testing
162
-
163
- Returns:
164
- EmulatedLifxDevice configured as a switch
165
- """
166
- return create_device(
167
- product_id,
168
- serial=serial,
169
- firmware_version=firmware_version,
170
- storage=storage,
171
- scenario_manager=scenario_manager,
172
- )
173
-
174
-
175
- def create_device(
176
- product_id: int,
177
- serial: str | None = None,
178
- zone_count: int | None = None,
179
- extended_multizone: bool | None = None,
180
- tile_count: int | None = None,
181
- tile_width: int | None = None,
182
- tile_height: int | None = None,
183
- firmware_version: tuple[int, int] | None = None,
184
- storage: DevicePersistenceAsyncFile | None = None,
185
- scenario_manager: HierarchicalScenarioManager | None = None,
186
- ) -> EmulatedLifxDevice:
187
- """Create a device for any LIFX product using the product registry.
188
-
189
- This function uses the DeviceBuilder pattern to construct devices with
190
- clean separation of concerns and testable components.
191
-
192
- Args:
193
- product_id: Product ID from the LIFX product registry
194
- serial: Optional serial (auto-generated if not provided)
195
- zone_count: Number of zones for multizone devices (auto-determined)
196
- extended_multizone: Enable extended multizone requests
197
- tile_count: Number of tiles for matrix devices (default: 5)
198
- tile_width: Width of each tile in zones (default: 8)
199
- tile_height: Height of each tile in zones (default: 8)
200
- firmware_version: Optional firmware version tuple (major, minor).
201
- If not specified, uses 3.70 for extended_multizone
202
- or 2.60 otherwise
203
- storage: Optional storage for persistence
204
- scenario_manager: Optional scenario manager for testing
205
-
206
- Returns:
207
- EmulatedLifxDevice configured for the specified product
208
-
209
- Raises:
210
- ValueError: If product_id is not found in registry
211
-
212
- Examples:
213
- >>> # Create LIFX A19 (PID 27)
214
- >>> device = create_device(27)
215
- >>> # Create LIFX Z strip (PID 32) with 24 zones
216
- >>> strip = create_device(32, zone_count=24)
217
- >>> # Create LIFX Tile (PID 55) with 10 tiles
218
- >>> tiles = create_device(55, tile_count=10)
219
- """
220
- # Get product info from registry
221
- product_info = get_product(product_id)
222
- if product_info is None:
223
- raise ValueError(f"Unknown product ID: {product_id}")
224
-
225
- # Build device using builder pattern
226
- builder = DeviceBuilder(product_info)
227
-
228
- if serial is not None:
229
- builder.with_serial(serial)
230
-
231
- if zone_count is not None:
232
- builder.with_zone_count(zone_count)
233
-
234
- if extended_multizone is not None:
235
- builder.with_extended_multizone(extended_multizone)
236
-
237
- if tile_count is not None:
238
- builder.with_tile_count(tile_count)
239
-
240
- if tile_width is not None and tile_height is not None:
241
- builder.with_tile_dimensions(tile_width, tile_height)
242
-
243
- if firmware_version is not None:
244
- builder.with_firmware_version(*firmware_version)
245
-
246
- if storage is not None:
247
- builder.with_storage(storage)
248
-
249
- if scenario_manager is not None:
250
- builder.with_scenario_manager(scenario_manager)
251
-
252
- return builder.build()
@@ -1,77 +0,0 @@
1
- """Firmware version configuration for devices."""
2
-
3
- from __future__ import annotations
4
-
5
- from lifx_emulator.products.specs import get_default_firmware_version
6
-
7
-
8
- class FirmwareConfig:
9
- """Determines firmware versions for devices.
10
-
11
- Extended multizone support requires firmware 3.70+.
12
- Devices without extended multizone use firmware 2.60.
13
-
14
- Examples:
15
- >>> config = FirmwareConfig()
16
- >>> major, minor = config.get_firmware_version(extended_multizone=True)
17
- >>> (major, minor)
18
- (3, 70)
19
- >>> major, minor = config.get_firmware_version(extended_multizone=False)
20
- >>> (major, minor)
21
- (2, 60)
22
- """
23
-
24
- # Firmware versions
25
- VERSION_EXTENDED = (3, 70) # Extended multizone support
26
- VERSION_LEGACY = (2, 60) # Legacy firmware
27
-
28
- def get_firmware_version(
29
- self,
30
- product_id: int | None = None,
31
- extended_multizone: bool | None = None,
32
- override: tuple[int, int] | None = None,
33
- ) -> tuple[int, int]:
34
- """Get firmware version based on product specs or extended multizone support.
35
-
36
- Precedence order:
37
- 1. Explicit override parameter
38
- 2. Product-specific default from specs.yml
39
- 3. Extended multizone flag (3.70 for True/None, 2.60 for False)
40
-
41
- Args:
42
- product_id: Optional product ID to check specs for defaults
43
- extended_multizone: Whether device supports extended multizone.
44
- None or True defaults to 3.70, False gives 2.60
45
- override: Optional explicit firmware version to use
46
-
47
- Returns:
48
- Tuple of (major, minor) firmware version
49
-
50
- Examples:
51
- >>> config = FirmwareConfig()
52
- >>> config.get_firmware_version(extended_multizone=True)
53
- (3, 70)
54
- >>> config.get_firmware_version(extended_multizone=False)
55
- (2, 60)
56
- >>> config.get_firmware_version(override=(4, 0))
57
- (4, 0)
58
- >>> # With product_id, uses specs if defined
59
- >>> config.get_firmware_version(product_id=27) # doctest: +SKIP
60
- (3, 70)
61
- """
62
- # Explicit override takes precedence
63
- if override is not None:
64
- return override
65
-
66
- # Check product-specific defaults from specs
67
- if product_id is not None:
68
- specs_version = get_default_firmware_version(product_id)
69
- if specs_version is not None:
70
- return specs_version
71
-
72
- # None or True defaults to extended (3.70)
73
- # Only explicit False gives legacy (2.60)
74
- if extended_multizone is False:
75
- return self.VERSION_LEGACY
76
- else:
77
- return self.VERSION_EXTENDED
@@ -1,82 +0,0 @@
1
- """Serial number generation service for LIFX devices."""
2
-
3
- from __future__ import annotations
4
-
5
- import random
6
- from typing import TYPE_CHECKING
7
-
8
- if TYPE_CHECKING:
9
- from lifx_emulator.products.registry import ProductInfo
10
-
11
-
12
- class SerialGenerator:
13
- """Generates serial numbers for emulated LIFX devices.
14
-
15
- Serial numbers are 12-character hex strings with different prefixes
16
- based on device capabilities for easier identification.
17
-
18
- Prefixes:
19
- - d073d9: Matrix/Tile devices
20
- - d073d8: Multizone devices (strips/beams)
21
- - d073d7: HEV devices
22
- - d073d6: Infrared devices
23
- - d073d5: Regular color/temperature lights
24
-
25
- Examples:
26
- >>> generator = SerialGenerator()
27
- >>> serial = generator.generate(product_info)
28
- >>> len(serial)
29
- 12
30
- >>> serial.startswith("d073d")
31
- True
32
- """
33
-
34
- # Device type prefixes for easy identification
35
- PREFIX_MATRIX = "d073d9"
36
- PREFIX_MULTIZONE = "d073d8"
37
- PREFIX_HEV = "d073d7"
38
- PREFIX_INFRARED = "d073d6"
39
- PREFIX_DEFAULT = "d073d5"
40
-
41
- def generate(self, product_info: ProductInfo) -> str:
42
- """Generate a serial number based on product capabilities.
43
-
44
- Args:
45
- product_info: Product information from registry
46
-
47
- Returns:
48
- 12-character hex serial number
49
-
50
- Examples:
51
- >>> from lifx_emulator.products.registry import get_product
52
- >>> generator = SerialGenerator()
53
- >>> product = get_product(55) # LIFX Tile
54
- >>> serial = generator.generate(product)
55
- >>> serial.startswith("d073d9") # Matrix prefix
56
- True
57
- """
58
- prefix = self._determine_prefix(product_info)
59
- suffix = random.randint(100000, 999999) # nosec
60
- return f"{prefix}{suffix:06x}"
61
-
62
- def _determine_prefix(self, product_info: ProductInfo) -> str:
63
- """Determine the prefix based on product capabilities.
64
-
65
- Precedence: matrix > multizone > hev > infrared > default
66
-
67
- Args:
68
- product_info: Product information from registry
69
-
70
- Returns:
71
- 6-character hex prefix
72
- """
73
- if product_info.has_matrix:
74
- return self.PREFIX_MATRIX
75
- elif product_info.has_multizone:
76
- return self.PREFIX_MULTIZONE
77
- elif product_info.has_hev:
78
- return self.PREFIX_HEV
79
- elif product_info.has_infrared:
80
- return self.PREFIX_INFRARED
81
- else:
82
- return self.PREFIX_DEFAULT
@@ -1,39 +0,0 @@
1
- """Packet handler infrastructure using Strategy pattern.
2
-
3
- This module provides the base classes and registry for handling LIFX protocol packets.
4
- Each packet type has a dedicated handler class that implements the business logic.
5
- """
6
-
7
- from lifx_emulator.handlers.base import PacketHandler
8
- from lifx_emulator.handlers.device_handlers import ALL_DEVICE_HANDLERS
9
- from lifx_emulator.handlers.light_handlers import ALL_LIGHT_HANDLERS
10
- from lifx_emulator.handlers.multizone_handlers import ALL_MULTIZONE_HANDLERS
11
- from lifx_emulator.handlers.registry import HandlerRegistry
12
- from lifx_emulator.handlers.tile_handlers import ALL_TILE_HANDLERS
13
-
14
- __all__ = [
15
- "PacketHandler",
16
- "HandlerRegistry",
17
- "ALL_DEVICE_HANDLERS",
18
- "ALL_LIGHT_HANDLERS",
19
- "ALL_MULTIZONE_HANDLERS",
20
- "ALL_TILE_HANDLERS",
21
- "create_default_registry",
22
- ]
23
-
24
-
25
- def create_default_registry() -> HandlerRegistry:
26
- """Create a handler registry with all default handlers registered.
27
-
28
- Returns:
29
- HandlerRegistry with all built-in handlers
30
- """
31
- registry = HandlerRegistry()
32
-
33
- # Register all handler categories
34
- registry.register_all(ALL_DEVICE_HANDLERS)
35
- registry.register_all(ALL_LIGHT_HANDLERS)
36
- registry.register_all(ALL_MULTIZONE_HANDLERS)
37
- registry.register_all(ALL_TILE_HANDLERS)
38
-
39
- return registry
@@ -1,49 +0,0 @@
1
- """Base classes for packet handlers."""
2
-
3
- from __future__ import annotations
4
-
5
- from abc import ABC, abstractmethod
6
- from typing import TYPE_CHECKING, Any
7
-
8
- if TYPE_CHECKING:
9
- from lifx_emulator.devices import DeviceState
10
-
11
-
12
- class PacketHandler(ABC):
13
- """Base class for all packet handlers.
14
-
15
- Each handler implements the logic for processing a specific packet type
16
- and optionally generating a response packet.
17
-
18
- Handlers are stateless and operate on the provided DeviceState.
19
- """
20
-
21
- # Subclasses must define the packet type they handle
22
- PKT_TYPE: int
23
-
24
- @abstractmethod
25
- def handle(
26
- self, device_state: DeviceState, packet: Any | None, res_required: bool
27
- ) -> list[Any]:
28
- """Handle the packet and return response packet(s).
29
-
30
- Args:
31
- device_state: Current device state to read/modify
32
- packet: Unpacked packet object (None for packets with no payload)
33
- res_required: Whether client requested a response (res_required
34
- flag from header)
35
-
36
- Returns:
37
- List of response packets (empty list if no response needed).
38
- This unified return type simplifies packet processing logic.
39
-
40
- Notes:
41
- - Handlers should modify device_state directly for SET operations
42
- - Handlers should check device capabilities before processing
43
- - Return empty list [] if the device doesn't support this packet type
44
- - Always return a list, even for single responses: [packet]
45
- """
46
- pass
47
-
48
- def __repr__(self) -> str:
49
- return f"{self.__class__.__name__}(PKT_TYPE={self.PKT_TYPE})"