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.
- lifx_emulator-3.0.1.dist-info/METADATA +102 -0
- lifx_emulator-3.0.1.dist-info/RECORD +18 -0
- lifx_emulator-3.0.1.dist-info/entry_points.txt +2 -0
- lifx_emulator_app/__init__.py +10 -0
- {lifx_emulator → lifx_emulator_app}/__main__.py +2 -3
- {lifx_emulator → lifx_emulator_app}/api/__init__.py +1 -1
- {lifx_emulator → lifx_emulator_app}/api/app.py +3 -3
- {lifx_emulator → lifx_emulator_app}/api/mappers/__init__.py +1 -1
- {lifx_emulator → lifx_emulator_app}/api/mappers/device_mapper.py +1 -1
- {lifx_emulator → lifx_emulator_app}/api/models.py +1 -2
- lifx_emulator_app/api/routers/__init__.py +11 -0
- {lifx_emulator → lifx_emulator_app}/api/routers/devices.py +2 -2
- {lifx_emulator → lifx_emulator_app}/api/routers/monitoring.py +1 -1
- {lifx_emulator → lifx_emulator_app}/api/routers/scenarios.py +1 -1
- lifx_emulator_app/api/services/__init__.py +8 -0
- {lifx_emulator → lifx_emulator_app}/api/services/device_service.py +3 -2
- lifx_emulator/__init__.py +0 -31
- lifx_emulator/api/routers/__init__.py +0 -11
- lifx_emulator/api/services/__init__.py +0 -8
- lifx_emulator/constants.py +0 -33
- lifx_emulator/devices/__init__.py +0 -37
- lifx_emulator/devices/device.py +0 -395
- lifx_emulator/devices/manager.py +0 -256
- lifx_emulator/devices/observers.py +0 -139
- lifx_emulator/devices/persistence.py +0 -308
- lifx_emulator/devices/state_restorer.py +0 -259
- lifx_emulator/devices/state_serializer.py +0 -157
- lifx_emulator/devices/states.py +0 -381
- lifx_emulator/factories/__init__.py +0 -39
- lifx_emulator/factories/builder.py +0 -375
- lifx_emulator/factories/default_config.py +0 -158
- lifx_emulator/factories/factory.py +0 -252
- lifx_emulator/factories/firmware_config.py +0 -77
- lifx_emulator/factories/serial_generator.py +0 -82
- lifx_emulator/handlers/__init__.py +0 -39
- lifx_emulator/handlers/base.py +0 -49
- lifx_emulator/handlers/device_handlers.py +0 -322
- lifx_emulator/handlers/light_handlers.py +0 -503
- lifx_emulator/handlers/multizone_handlers.py +0 -249
- lifx_emulator/handlers/registry.py +0 -110
- lifx_emulator/handlers/tile_handlers.py +0 -488
- lifx_emulator/products/__init__.py +0 -28
- lifx_emulator/products/generator.py +0 -1079
- lifx_emulator/products/registry.py +0 -1530
- lifx_emulator/products/specs.py +0 -284
- lifx_emulator/products/specs.yml +0 -386
- lifx_emulator/protocol/__init__.py +0 -1
- lifx_emulator/protocol/base.py +0 -446
- lifx_emulator/protocol/const.py +0 -8
- lifx_emulator/protocol/generator.py +0 -1384
- lifx_emulator/protocol/header.py +0 -159
- lifx_emulator/protocol/packets.py +0 -1351
- lifx_emulator/protocol/protocol_types.py +0 -817
- lifx_emulator/protocol/serializer.py +0 -379
- lifx_emulator/repositories/__init__.py +0 -22
- lifx_emulator/repositories/device_repository.py +0 -155
- lifx_emulator/repositories/storage_backend.py +0 -107
- lifx_emulator/scenarios/__init__.py +0 -22
- lifx_emulator/scenarios/manager.py +0 -322
- lifx_emulator/scenarios/models.py +0 -112
- lifx_emulator/scenarios/persistence.py +0 -241
- lifx_emulator/server.py +0 -464
- lifx_emulator-2.4.0.dist-info/METADATA +0 -107
- lifx_emulator-2.4.0.dist-info/RECORD +0 -62
- lifx_emulator-2.4.0.dist-info/entry_points.txt +0 -2
- lifx_emulator-2.4.0.dist-info/licenses/LICENSE +0 -35
- {lifx_emulator-2.4.0.dist-info → lifx_emulator-3.0.1.dist-info}/WHEEL +0 -0
- {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)"
|