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,249 +0,0 @@
1
- """MultiZone packet handlers."""
2
-
3
- from __future__ import annotations
4
-
5
- import logging
6
- from typing import TYPE_CHECKING, Any
7
-
8
- from lifx_emulator.handlers.base import PacketHandler
9
- from lifx_emulator.protocol.packets import MultiZone
10
- from lifx_emulator.protocol.protocol_types import (
11
- LightHsbk,
12
- MultiZoneEffectParameter,
13
- MultiZoneEffectSettings,
14
- MultiZoneEffectType,
15
- )
16
-
17
- if TYPE_CHECKING:
18
- from lifx_emulator.devices import DeviceState
19
-
20
- logger = logging.getLogger(__name__)
21
-
22
-
23
- class GetColorZonesHandler(PacketHandler):
24
- """Handle MultiZoneGetColorZones (502) -> StateMultiZone (506) packets."""
25
-
26
- PKT_TYPE = MultiZone.GetColorZones.PKT_TYPE
27
-
28
- def handle(
29
- self,
30
- device_state: DeviceState,
31
- packet: MultiZone.GetColorZones | None,
32
- res_required: bool,
33
- ) -> list[Any]:
34
- if not device_state.has_multizone:
35
- return []
36
-
37
- start_index = packet.start_index if packet else 0
38
- end_index = packet.end_index if packet else 0
39
-
40
- # Return multiple StateMultiZone packets, each containing up to 8 zones
41
- responses = []
42
-
43
- # Send packets of up to 8 zones each (StateMultiZone format)
44
- index = start_index
45
- while index <= end_index and index < device_state.zone_count:
46
- # Collect up to 8 zones for this packet
47
- colors = []
48
- for i in range(8):
49
- zone_index = index + i
50
- if zone_index < device_state.zone_count and zone_index <= end_index:
51
- zone_color = (
52
- device_state.zone_colors[zone_index]
53
- if zone_index < len(device_state.zone_colors)
54
- else LightHsbk(hue=0, saturation=0, brightness=0, kelvin=3500)
55
- )
56
- colors.append(zone_color)
57
- else:
58
- # Pad remaining slots with black
59
- colors.append(
60
- LightHsbk(hue=0, saturation=0, brightness=0, kelvin=3500)
61
- )
62
-
63
- # Pad to exactly 8 colors
64
- while len(colors) < 8:
65
- colors.append(LightHsbk(hue=0, saturation=0, brightness=0, kelvin=3500))
66
-
67
- packet_obj = MultiZone.StateMultiZone(
68
- count=device_state.zone_count, index=index, colors=colors
69
- )
70
- responses.append(packet_obj)
71
-
72
- index += 8
73
-
74
- return responses
75
-
76
-
77
- class SetColorZonesHandler(PacketHandler):
78
- """Handle MultiZoneSetColorZones (501)."""
79
-
80
- PKT_TYPE = MultiZone.SetColorZones.PKT_TYPE
81
-
82
- def handle(
83
- self,
84
- device_state: DeviceState,
85
- packet: MultiZone.SetColorZones | None,
86
- res_required: bool,
87
- ) -> list[Any]:
88
- if not device_state.has_multizone:
89
- return []
90
-
91
- if packet:
92
- start_index = packet.start_index
93
- end_index = packet.end_index
94
-
95
- # Update zone colors
96
- for i in range(start_index, min(end_index + 1, device_state.zone_count)):
97
- if i < len(device_state.zone_colors):
98
- device_state.zone_colors[i] = packet.color
99
-
100
- logger.info(
101
- f"MultiZone set zones {start_index}-{end_index} to color, "
102
- f"duration={packet.duration}ms"
103
- )
104
-
105
- if res_required and packet:
106
- # Create a GetColorZones packet to reuse the get handler
107
- get_packet = MultiZone.GetColorZones(
108
- start_index=packet.start_index, end_index=packet.end_index
109
- )
110
- # Reuse GetColorZonesHandler
111
- handler = GetColorZonesHandler()
112
- return handler.handle(device_state, get_packet, res_required)
113
- return []
114
-
115
-
116
- class ExtendedGetColorZonesHandler(PacketHandler):
117
- """Handle MultiZoneExtendedGetColorZones (511) -> ExtendedStateMultiZone (512)."""
118
-
119
- PKT_TYPE = MultiZone.ExtendedGetColorZones.PKT_TYPE
120
-
121
- def handle(
122
- self, device_state: DeviceState, packet: Any | None, res_required: bool
123
- ) -> list[Any]:
124
- if not device_state.has_multizone:
125
- return []
126
-
127
- colors_count = min(82, len(device_state.zone_colors))
128
- colors = []
129
- for i in range(colors_count):
130
- colors.append(device_state.zone_colors[i])
131
- # Pad to 82 colors
132
- while len(colors) < 82:
133
- colors.append(LightHsbk(hue=0, saturation=0, brightness=0, kelvin=3500))
134
-
135
- return [
136
- MultiZone.ExtendedStateMultiZone(
137
- count=device_state.zone_count,
138
- index=0,
139
- colors_count=colors_count,
140
- colors=colors,
141
- )
142
- ]
143
-
144
-
145
- class ExtendedSetColorZonesHandler(PacketHandler):
146
- """Handle MultiZoneExtendedSetColorZones (510)."""
147
-
148
- PKT_TYPE = MultiZone.ExtendedSetColorZones.PKT_TYPE
149
-
150
- def handle(
151
- self,
152
- device_state: DeviceState,
153
- packet: MultiZone.ExtendedSetColorZones | None,
154
- res_required: bool,
155
- ) -> list[Any]:
156
- if not device_state.has_multizone:
157
- return []
158
-
159
- if packet:
160
- # Update zone colors from packet
161
- for i, color in enumerate(packet.colors[: packet.colors_count]):
162
- zone_index = packet.index + i
163
- if zone_index < len(device_state.zone_colors):
164
- device_state.zone_colors[zone_index] = color
165
-
166
- logger.info(
167
- f"MultiZone extended set {packet.colors_count} zones "
168
- f"from index {packet.index}, duration={packet.duration}ms"
169
- )
170
-
171
- if res_required:
172
- handler = ExtendedGetColorZonesHandler()
173
- return handler.handle(device_state, None, res_required)
174
- return []
175
-
176
-
177
- class GetEffectHandler(PacketHandler):
178
- """Handle MultiZoneGetEffect (507) -> StateEffect (509)."""
179
-
180
- PKT_TYPE = MultiZone.GetEffect.PKT_TYPE
181
-
182
- def handle(
183
- self, device_state: DeviceState, packet: Any | None, res_required: bool
184
- ) -> list[Any]:
185
- if not device_state.has_multizone:
186
- return []
187
-
188
- # Create effect settings
189
- parameter = MultiZoneEffectParameter(
190
- parameter0=0,
191
- parameter1=0,
192
- parameter2=0,
193
- parameter3=0,
194
- parameter4=0,
195
- parameter5=0,
196
- parameter6=0,
197
- parameter7=0,
198
- )
199
- settings = MultiZoneEffectSettings(
200
- instanceid=0,
201
- type=MultiZoneEffectType(device_state.multizone_effect_type),
202
- speed=device_state.multizone_effect_speed * 1000, # convert to milliseconds
203
- duration=0, # infinite
204
- parameter=parameter,
205
- )
206
-
207
- return [MultiZone.StateEffect(settings=settings)]
208
-
209
-
210
- class SetEffectHandler(PacketHandler):
211
- """Handle MultiZoneSetEffect (508) -> StateEffect (509)."""
212
-
213
- PKT_TYPE = MultiZone.SetEffect.PKT_TYPE
214
-
215
- def handle(
216
- self,
217
- device_state: DeviceState,
218
- packet: MultiZone.SetEffect | None,
219
- res_required: bool,
220
- ) -> list[Any]:
221
- if not device_state.has_multizone:
222
- return []
223
-
224
- if packet:
225
- device_state.multizone_effect_type = int(packet.settings.type)
226
- device_state.multizone_effect_speed = (
227
- packet.settings.speed // 1000
228
- ) # convert to seconds
229
-
230
- logger.info(
231
- f"MultiZone effect set: type={packet.settings.type}, "
232
- f"speed={packet.settings.speed}ms"
233
- )
234
-
235
- if res_required:
236
- handler = GetEffectHandler()
237
- return handler.handle(device_state, None, res_required)
238
- return []
239
-
240
-
241
- # List of all multizone handlers for easy registration
242
- ALL_MULTIZONE_HANDLERS = [
243
- GetColorZonesHandler(),
244
- SetColorZonesHandler(),
245
- ExtendedGetColorZonesHandler(),
246
- ExtendedSetColorZonesHandler(),
247
- GetEffectHandler(),
248
- SetEffectHandler(),
249
- ]
@@ -1,110 +0,0 @@
1
- """Handler registry for managing packet handlers."""
2
-
3
- from __future__ import annotations
4
-
5
- import logging
6
- from typing import TYPE_CHECKING
7
-
8
- if TYPE_CHECKING:
9
- from lifx_emulator.handlers.base import PacketHandler
10
-
11
- logger = logging.getLogger(__name__)
12
-
13
-
14
- class HandlerRegistry:
15
- """Registry for packet handlers using Strategy pattern.
16
-
17
- The registry maps packet type numbers to handler instances.
18
- Handlers can be registered individually or in bulk.
19
-
20
- Example:
21
- >>> registry = HandlerRegistry()
22
- >>> registry.register(GetServiceHandler())
23
- >>> handler = registry.get_handler(2) # Device.GetService
24
- >>> response = handler.handle(device_state, None, True)
25
- """
26
-
27
- def __init__(self):
28
- """Initialize an empty handler registry."""
29
- self._handlers: dict[int, PacketHandler] = {}
30
-
31
- def register(self, handler: PacketHandler) -> None:
32
- """Register a packet handler.
33
-
34
- Args:
35
- handler: Handler instance to register
36
-
37
- Raises:
38
- ValueError: If handler doesn't have PKT_TYPE attribute
39
-
40
- Note:
41
- If a handler for this packet type already exists, it will be replaced.
42
- """
43
- if not hasattr(handler, "PKT_TYPE"):
44
- raise ValueError(
45
- f"Handler {handler.__class__.__name__} missing PKT_TYPE attribute"
46
- )
47
-
48
- pkt_type = handler.PKT_TYPE
49
-
50
- # Warn if replacing existing handler
51
- if pkt_type in self._handlers:
52
- old_handler = self._handlers[pkt_type]
53
- logger.warning(
54
- f"Replacing handler for packet type {pkt_type}: "
55
- f"{old_handler.__class__.__name__} -> {handler.__class__.__name__}"
56
- )
57
-
58
- self._handlers[pkt_type] = handler
59
- logger.debug(
60
- f"Registered {handler.__class__.__name__} for packet type {pkt_type}"
61
- )
62
-
63
- def register_all(self, handlers: list[PacketHandler]) -> None:
64
- """Register multiple handlers at once.
65
-
66
- Args:
67
- handlers: List of handler instances to register
68
- """
69
- for handler in handlers:
70
- self.register(handler)
71
-
72
- def get_handler(self, pkt_type: int) -> PacketHandler | None:
73
- """Get handler for a packet type.
74
-
75
- Args:
76
- pkt_type: Packet type number
77
-
78
- Returns:
79
- Handler instance if registered, None otherwise
80
- """
81
- return self._handlers.get(pkt_type)
82
-
83
- def has_handler(self, pkt_type: int) -> bool:
84
- """Check if a handler is registered for a packet type.
85
-
86
- Args:
87
- pkt_type: Packet type number
88
-
89
- Returns:
90
- True if handler is registered, False otherwise
91
- """
92
- return pkt_type in self._handlers
93
-
94
- def list_handlers(self) -> list[tuple[int, str]]:
95
- """List all registered handlers.
96
-
97
- Returns:
98
- List of (packet_type, handler_class_name) tuples
99
- """
100
- return [
101
- (pkt_type, handler.__class__.__name__)
102
- for pkt_type, handler in sorted(self._handlers.items())
103
- ]
104
-
105
- def __len__(self) -> int:
106
- """Return number of registered handlers."""
107
- return len(self._handlers)
108
-
109
- def __repr__(self) -> str:
110
- return f"HandlerRegistry({len(self)} handlers)"