lifx-emulator 2.4.0__py3-none-any.whl → 3.1.0__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.1.0.dist-info/METADATA +103 -0
- lifx_emulator-3.1.0.dist-info/RECORD +19 -0
- {lifx_emulator-2.4.0.dist-info → lifx_emulator-3.1.0.dist-info}/WHEEL +1 -1
- lifx_emulator-3.1.0.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 +9 -4
- {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_app/api/static/dashboard.js +588 -0
- lifx_emulator_app/api/templates/dashboard.html +357 -0
- lifx_emulator/__init__.py +0 -31
- lifx_emulator/api/routers/__init__.py +0 -11
- lifx_emulator/api/services/__init__.py +0 -8
- lifx_emulator/api/templates/dashboard.html +0 -899
- 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
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
"""State restoration for devices with persistent storage.
|
|
2
|
-
|
|
3
|
-
This module provides centralized state restoration logic, eliminating
|
|
4
|
-
duplication between factories and device initialization.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from __future__ import annotations
|
|
8
|
-
|
|
9
|
-
import logging
|
|
10
|
-
from typing import Any
|
|
11
|
-
|
|
12
|
-
from lifx_emulator.devices.device import DeviceState
|
|
13
|
-
|
|
14
|
-
logger = logging.getLogger(__name__)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class StateRestorer:
|
|
18
|
-
"""Handles restoration of device state from persistent storage.
|
|
19
|
-
|
|
20
|
-
Consolidates state restoration logic that was previously duplicated
|
|
21
|
-
between factories and device initialization.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
def __init__(self, storage: Any):
|
|
25
|
-
"""Initialize state restorer.
|
|
26
|
-
|
|
27
|
-
Args:
|
|
28
|
-
storage: Storage instance (DeviceStorage or DevicePersistenceAsyncFile)
|
|
29
|
-
"""
|
|
30
|
-
self.storage = storage
|
|
31
|
-
|
|
32
|
-
def restore_if_available(self, state: DeviceState) -> DeviceState:
|
|
33
|
-
"""Restore saved state if available and compatible.
|
|
34
|
-
|
|
35
|
-
Args:
|
|
36
|
-
state: DeviceState to restore into
|
|
37
|
-
|
|
38
|
-
Returns:
|
|
39
|
-
The same DeviceState instance with restored values
|
|
40
|
-
"""
|
|
41
|
-
if not self.storage:
|
|
42
|
-
return state
|
|
43
|
-
|
|
44
|
-
saved_state = self.storage.load_device_state(state.serial)
|
|
45
|
-
if not saved_state:
|
|
46
|
-
logger.debug("No saved state found for device %s", state.serial)
|
|
47
|
-
return state
|
|
48
|
-
|
|
49
|
-
# Only restore if product matches
|
|
50
|
-
if saved_state.get("product") != state.product:
|
|
51
|
-
logger.warning(
|
|
52
|
-
"Saved state for %s has different product (%s vs %s), skipping restore",
|
|
53
|
-
state.serial,
|
|
54
|
-
saved_state.get("product"),
|
|
55
|
-
state.product,
|
|
56
|
-
)
|
|
57
|
-
return state
|
|
58
|
-
|
|
59
|
-
logger.info("Restoring saved state for device %s", state.serial)
|
|
60
|
-
|
|
61
|
-
# Restore core state
|
|
62
|
-
self._restore_core_state(state, saved_state)
|
|
63
|
-
|
|
64
|
-
# Restore location and group
|
|
65
|
-
self._restore_location_and_group(state, saved_state)
|
|
66
|
-
|
|
67
|
-
# Restore capability-specific state
|
|
68
|
-
self._restore_capability_state(state, saved_state)
|
|
69
|
-
|
|
70
|
-
return state
|
|
71
|
-
|
|
72
|
-
def _restore_core_state(
|
|
73
|
-
self, state: DeviceState, saved_state: dict[str, Any]
|
|
74
|
-
) -> None:
|
|
75
|
-
"""Restore core device state fields.
|
|
76
|
-
|
|
77
|
-
Args:
|
|
78
|
-
state: DeviceState to restore into
|
|
79
|
-
saved_state: Dictionary with saved state values
|
|
80
|
-
"""
|
|
81
|
-
if "label" in saved_state:
|
|
82
|
-
state.core.label = saved_state["label"]
|
|
83
|
-
if "power_level" in saved_state:
|
|
84
|
-
state.core.power_level = saved_state["power_level"]
|
|
85
|
-
if "color" in saved_state:
|
|
86
|
-
state.core.color = saved_state["color"]
|
|
87
|
-
|
|
88
|
-
def _restore_location_and_group(
|
|
89
|
-
self, state: DeviceState, saved_state: dict[str, Any]
|
|
90
|
-
) -> None:
|
|
91
|
-
"""Restore location and group metadata.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
state: DeviceState to restore into
|
|
95
|
-
saved_state: Dictionary with saved state values
|
|
96
|
-
"""
|
|
97
|
-
# Location
|
|
98
|
-
if "location_id" in saved_state:
|
|
99
|
-
state.location.location_id = saved_state["location_id"]
|
|
100
|
-
if "location_label" in saved_state:
|
|
101
|
-
state.location.location_label = saved_state["location_label"]
|
|
102
|
-
if "location_updated_at" in saved_state:
|
|
103
|
-
state.location.location_updated_at = saved_state["location_updated_at"]
|
|
104
|
-
|
|
105
|
-
# Group
|
|
106
|
-
if "group_id" in saved_state:
|
|
107
|
-
state.group.group_id = saved_state["group_id"]
|
|
108
|
-
if "group_label" in saved_state:
|
|
109
|
-
state.group.group_label = saved_state["group_label"]
|
|
110
|
-
if "group_updated_at" in saved_state:
|
|
111
|
-
state.group.group_updated_at = saved_state["group_updated_at"]
|
|
112
|
-
|
|
113
|
-
def _restore_capability_state(
|
|
114
|
-
self, state: DeviceState, saved_state: dict[str, Any]
|
|
115
|
-
) -> None:
|
|
116
|
-
"""Restore capability-specific state.
|
|
117
|
-
|
|
118
|
-
Args:
|
|
119
|
-
state: DeviceState to restore into
|
|
120
|
-
saved_state: Dictionary with saved state values
|
|
121
|
-
"""
|
|
122
|
-
# Infrared
|
|
123
|
-
if (
|
|
124
|
-
state.has_infrared
|
|
125
|
-
and state.infrared
|
|
126
|
-
and "infrared_brightness" in saved_state
|
|
127
|
-
):
|
|
128
|
-
state.infrared.infrared_brightness = saved_state["infrared_brightness"]
|
|
129
|
-
|
|
130
|
-
# HEV
|
|
131
|
-
if state.has_hev and state.hev:
|
|
132
|
-
if "hev_cycle_duration_s" in saved_state:
|
|
133
|
-
state.hev.hev_cycle_duration_s = saved_state["hev_cycle_duration_s"]
|
|
134
|
-
if "hev_cycle_remaining_s" in saved_state:
|
|
135
|
-
state.hev.hev_cycle_remaining_s = saved_state["hev_cycle_remaining_s"]
|
|
136
|
-
if "hev_cycle_last_power" in saved_state:
|
|
137
|
-
state.hev.hev_cycle_last_power = saved_state["hev_cycle_last_power"]
|
|
138
|
-
if "hev_indication" in saved_state:
|
|
139
|
-
state.hev.hev_indication = saved_state["hev_indication"]
|
|
140
|
-
if "hev_last_result" in saved_state:
|
|
141
|
-
state.hev.hev_last_result = saved_state["hev_last_result"]
|
|
142
|
-
|
|
143
|
-
# Multizone
|
|
144
|
-
if state.has_multizone and state.multizone:
|
|
145
|
-
self._restore_multizone_state(state, saved_state)
|
|
146
|
-
|
|
147
|
-
# Matrix (Tile)
|
|
148
|
-
if state.has_matrix and state.matrix:
|
|
149
|
-
self._restore_matrix_state(state, saved_state)
|
|
150
|
-
|
|
151
|
-
def _restore_multizone_state(
|
|
152
|
-
self, state: DeviceState, saved_state: dict[str, Any]
|
|
153
|
-
) -> None:
|
|
154
|
-
"""Restore multizone-specific state.
|
|
155
|
-
|
|
156
|
-
Args:
|
|
157
|
-
state: DeviceState to restore into
|
|
158
|
-
saved_state: Dictionary with saved state values
|
|
159
|
-
"""
|
|
160
|
-
if state.multizone is None:
|
|
161
|
-
return
|
|
162
|
-
|
|
163
|
-
# First restore zone_count from saved state
|
|
164
|
-
# This ensures the device matches what was previously saved
|
|
165
|
-
if "zone_count" in saved_state:
|
|
166
|
-
state.multizone.zone_count = saved_state["zone_count"]
|
|
167
|
-
logger.debug("Restored zone_count: %s", state.multizone.zone_count)
|
|
168
|
-
|
|
169
|
-
# Now restore zone colors if available
|
|
170
|
-
if "zone_colors" in saved_state:
|
|
171
|
-
# Verify zone count matches (should match now that we restored it)
|
|
172
|
-
if len(saved_state["zone_colors"]) == state.multizone.zone_count:
|
|
173
|
-
state.multizone.zone_colors = saved_state["zone_colors"]
|
|
174
|
-
logger.debug("Restored %s zone colors", len(saved_state["zone_colors"]))
|
|
175
|
-
else:
|
|
176
|
-
logger.warning(
|
|
177
|
-
"Zone count mismatch: saved has %s zones, current has %s zones",
|
|
178
|
-
len(saved_state["zone_colors"]),
|
|
179
|
-
state.multizone.zone_count,
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
if "multizone_effect_type" in saved_state:
|
|
183
|
-
state.multizone.effect_type = saved_state["multizone_effect_type"]
|
|
184
|
-
if "multizone_effect_speed" in saved_state:
|
|
185
|
-
state.multizone.effect_speed = saved_state["multizone_effect_speed"]
|
|
186
|
-
|
|
187
|
-
def _restore_matrix_state(
|
|
188
|
-
self, state: DeviceState, saved_state: dict[str, Any]
|
|
189
|
-
) -> None:
|
|
190
|
-
"""Restore matrix (tile) specific state.
|
|
191
|
-
|
|
192
|
-
Args:
|
|
193
|
-
state: DeviceState to restore into
|
|
194
|
-
saved_state: Dictionary with saved state values
|
|
195
|
-
"""
|
|
196
|
-
if state.matrix is None:
|
|
197
|
-
return
|
|
198
|
-
|
|
199
|
-
# First restore tile configuration (count, width, height) from saved state
|
|
200
|
-
# This ensures the device matches what was previously saved
|
|
201
|
-
if "tile_count" in saved_state:
|
|
202
|
-
state.matrix.tile_count = saved_state["tile_count"]
|
|
203
|
-
logger.debug("Restored tile_count: %s", state.matrix.tile_count)
|
|
204
|
-
if "tile_width" in saved_state:
|
|
205
|
-
state.matrix.tile_width = saved_state["tile_width"]
|
|
206
|
-
logger.debug("Restored tile_width: %s", state.matrix.tile_width)
|
|
207
|
-
if "tile_height" in saved_state:
|
|
208
|
-
state.matrix.tile_height = saved_state["tile_height"]
|
|
209
|
-
logger.debug("Restored tile_height: %s", state.matrix.tile_height)
|
|
210
|
-
|
|
211
|
-
# Now restore tile devices if available
|
|
212
|
-
if "tile_devices" in saved_state:
|
|
213
|
-
saved_tiles = saved_state["tile_devices"]
|
|
214
|
-
# Verify tile count matches (should match now that we restored it)
|
|
215
|
-
if len(saved_tiles) == state.matrix.tile_count:
|
|
216
|
-
# Verify all tiles have matching dimensions
|
|
217
|
-
if all(
|
|
218
|
-
t["width"] == state.matrix.tile_width
|
|
219
|
-
and t["height"] == state.matrix.tile_height
|
|
220
|
-
for t in saved_tiles
|
|
221
|
-
):
|
|
222
|
-
state.matrix.tile_devices = saved_tiles
|
|
223
|
-
logger.debug("Restored %s tile devices", len(saved_tiles))
|
|
224
|
-
else:
|
|
225
|
-
logger.warning(
|
|
226
|
-
"Tile dimensions mismatch, skipping tile restoration"
|
|
227
|
-
)
|
|
228
|
-
else:
|
|
229
|
-
logger.warning(
|
|
230
|
-
f"Tile count mismatch: saved has {len(saved_tiles)} tiles, "
|
|
231
|
-
f"current has {state.matrix.tile_count} tiles"
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
if "tile_effect_type" in saved_state:
|
|
235
|
-
state.matrix.effect_type = saved_state["tile_effect_type"]
|
|
236
|
-
if "tile_effect_speed" in saved_state:
|
|
237
|
-
state.matrix.effect_speed = saved_state["tile_effect_speed"]
|
|
238
|
-
if "tile_effect_palette_count" in saved_state:
|
|
239
|
-
state.matrix.effect_palette_count = saved_state["tile_effect_palette_count"]
|
|
240
|
-
if "tile_effect_palette" in saved_state:
|
|
241
|
-
state.matrix.effect_palette = saved_state["tile_effect_palette"]
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
class NullStateRestorer:
|
|
245
|
-
"""No-op state restorer for devices without persistence.
|
|
246
|
-
|
|
247
|
-
Allows code to unconditionally call restore without checking for None.
|
|
248
|
-
"""
|
|
249
|
-
|
|
250
|
-
def restore_if_available(self, state: DeviceState) -> DeviceState:
|
|
251
|
-
"""No-op restoration.
|
|
252
|
-
|
|
253
|
-
Args:
|
|
254
|
-
state: DeviceState (returned unchanged)
|
|
255
|
-
|
|
256
|
-
Returns:
|
|
257
|
-
The same DeviceState instance
|
|
258
|
-
"""
|
|
259
|
-
return state
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
"""Shared serialization logic for device state."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import Any
|
|
6
|
-
|
|
7
|
-
from lifx_emulator.protocol.protocol_types import LightHsbk
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def serialize_hsbk(hsbk: LightHsbk) -> dict[str, int]:
|
|
11
|
-
"""Serialize LightHsbk to dict."""
|
|
12
|
-
return {
|
|
13
|
-
"hue": hsbk.hue,
|
|
14
|
-
"saturation": hsbk.saturation,
|
|
15
|
-
"brightness": hsbk.brightness,
|
|
16
|
-
"kelvin": hsbk.kelvin,
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def deserialize_hsbk(data: dict[str, int]) -> LightHsbk:
|
|
21
|
-
"""Deserialize dict to LightHsbk."""
|
|
22
|
-
return LightHsbk(
|
|
23
|
-
hue=data["hue"],
|
|
24
|
-
saturation=data["saturation"],
|
|
25
|
-
brightness=data["brightness"],
|
|
26
|
-
kelvin=data["kelvin"],
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def serialize_device_state(device_state: Any) -> dict[str, Any]:
|
|
31
|
-
"""Serialize DeviceState to dict.
|
|
32
|
-
|
|
33
|
-
Note: Accesses state via properties for backward compatibility with composed state.
|
|
34
|
-
"""
|
|
35
|
-
state_dict = {
|
|
36
|
-
"serial": device_state.serial,
|
|
37
|
-
"label": device_state.label,
|
|
38
|
-
"product": device_state.product,
|
|
39
|
-
"power_level": device_state.power_level,
|
|
40
|
-
"color": serialize_hsbk(device_state.color),
|
|
41
|
-
"location_id": device_state.location_id.hex(),
|
|
42
|
-
"location_label": device_state.location_label,
|
|
43
|
-
"location_updated_at": device_state.location_updated_at,
|
|
44
|
-
"group_id": device_state.group_id.hex(),
|
|
45
|
-
"group_label": device_state.group_label,
|
|
46
|
-
"group_updated_at": device_state.group_updated_at,
|
|
47
|
-
"has_color": device_state.has_color,
|
|
48
|
-
"has_infrared": device_state.has_infrared,
|
|
49
|
-
"has_multizone": device_state.has_multizone,
|
|
50
|
-
"has_matrix": device_state.has_matrix,
|
|
51
|
-
"has_hev": device_state.has_hev,
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if device_state.has_infrared:
|
|
55
|
-
state_dict["infrared_brightness"] = device_state.infrared_brightness
|
|
56
|
-
|
|
57
|
-
if device_state.has_hev:
|
|
58
|
-
state_dict["hev_cycle_duration_s"] = device_state.hev_cycle_duration_s
|
|
59
|
-
state_dict["hev_cycle_remaining_s"] = device_state.hev_cycle_remaining_s
|
|
60
|
-
state_dict["hev_cycle_last_power"] = device_state.hev_cycle_last_power
|
|
61
|
-
state_dict["hev_indication"] = device_state.hev_indication
|
|
62
|
-
state_dict["hev_last_result"] = device_state.hev_last_result
|
|
63
|
-
|
|
64
|
-
if device_state.has_multizone:
|
|
65
|
-
state_dict["zone_count"] = device_state.zone_count
|
|
66
|
-
state_dict["zone_colors"] = [
|
|
67
|
-
serialize_hsbk(c) for c in device_state.zone_colors
|
|
68
|
-
]
|
|
69
|
-
state_dict["multizone_effect_type"] = device_state.multizone_effect_type
|
|
70
|
-
state_dict["multizone_effect_speed"] = device_state.multizone_effect_speed
|
|
71
|
-
|
|
72
|
-
if device_state.has_matrix:
|
|
73
|
-
state_dict["tile_count"] = device_state.tile_count
|
|
74
|
-
state_dict["tile_width"] = device_state.tile_width
|
|
75
|
-
state_dict["tile_height"] = device_state.tile_height
|
|
76
|
-
state_dict["tile_effect_type"] = device_state.tile_effect_type
|
|
77
|
-
state_dict["tile_effect_speed"] = device_state.tile_effect_speed
|
|
78
|
-
state_dict["tile_effect_palette_count"] = device_state.tile_effect_palette_count
|
|
79
|
-
state_dict["tile_effect_palette"] = [
|
|
80
|
-
serialize_hsbk(c) for c in device_state.tile_effect_palette
|
|
81
|
-
]
|
|
82
|
-
state_dict["tile_devices"] = [
|
|
83
|
-
{
|
|
84
|
-
"accel_meas_x": t["accel_meas_x"],
|
|
85
|
-
"accel_meas_y": t["accel_meas_y"],
|
|
86
|
-
"accel_meas_z": t["accel_meas_z"],
|
|
87
|
-
"user_x": t["user_x"],
|
|
88
|
-
"user_y": t["user_y"],
|
|
89
|
-
"width": t["width"],
|
|
90
|
-
"height": t["height"],
|
|
91
|
-
"device_version_vendor": t["device_version_vendor"],
|
|
92
|
-
"device_version_product": t["device_version_product"],
|
|
93
|
-
"firmware_build": t["firmware_build"],
|
|
94
|
-
"firmware_version_minor": t["firmware_version_minor"],
|
|
95
|
-
"firmware_version_major": t["firmware_version_major"],
|
|
96
|
-
"colors": [serialize_hsbk(c) for c in t["colors"]],
|
|
97
|
-
}
|
|
98
|
-
for t in device_state.tile_devices
|
|
99
|
-
]
|
|
100
|
-
# Serialize tile framebuffers (non-visible framebuffers 1-7)
|
|
101
|
-
state_dict["tile_framebuffers"] = [
|
|
102
|
-
{
|
|
103
|
-
"tile_index": fb.tile_index,
|
|
104
|
-
"framebuffers": {
|
|
105
|
-
str(fb_idx): [serialize_hsbk(c) for c in colors]
|
|
106
|
-
for fb_idx, colors in fb.framebuffers.items()
|
|
107
|
-
},
|
|
108
|
-
}
|
|
109
|
-
for fb in device_state.tile_framebuffers
|
|
110
|
-
]
|
|
111
|
-
|
|
112
|
-
return state_dict
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def deserialize_device_state(state_dict: dict[str, Any]) -> dict[str, Any]:
|
|
116
|
-
"""Deserialize device state dict (convert hex strings and nested objects)."""
|
|
117
|
-
# Deserialize bytes fields
|
|
118
|
-
state_dict["location_id"] = bytes.fromhex(state_dict["location_id"])
|
|
119
|
-
state_dict["group_id"] = bytes.fromhex(state_dict["group_id"])
|
|
120
|
-
|
|
121
|
-
# Deserialize color
|
|
122
|
-
state_dict["color"] = deserialize_hsbk(state_dict["color"])
|
|
123
|
-
|
|
124
|
-
# Deserialize zone colors if present
|
|
125
|
-
if "zone_colors" in state_dict:
|
|
126
|
-
state_dict["zone_colors"] = [
|
|
127
|
-
deserialize_hsbk(c) for c in state_dict["zone_colors"]
|
|
128
|
-
]
|
|
129
|
-
|
|
130
|
-
# Deserialize tile effect palette if present
|
|
131
|
-
if "tile_effect_palette" in state_dict:
|
|
132
|
-
state_dict["tile_effect_palette"] = [
|
|
133
|
-
deserialize_hsbk(c) for c in state_dict["tile_effect_palette"]
|
|
134
|
-
]
|
|
135
|
-
|
|
136
|
-
# Deserialize tile devices if present
|
|
137
|
-
if "tile_devices" in state_dict:
|
|
138
|
-
for tile_dict in state_dict["tile_devices"]:
|
|
139
|
-
tile_dict["colors"] = [deserialize_hsbk(c) for c in tile_dict["colors"]]
|
|
140
|
-
|
|
141
|
-
# Deserialize tile framebuffers if present (for backwards compatibility)
|
|
142
|
-
if "tile_framebuffers" in state_dict:
|
|
143
|
-
from lifx_emulator.devices.states import TileFramebuffers
|
|
144
|
-
|
|
145
|
-
deserialized_fbs = []
|
|
146
|
-
for fb_dict in state_dict["tile_framebuffers"]:
|
|
147
|
-
tile_fb = TileFramebuffers(tile_index=fb_dict["tile_index"])
|
|
148
|
-
# Deserialize each framebuffer's colors
|
|
149
|
-
for fb_idx_str, colors_list in fb_dict["framebuffers"].items():
|
|
150
|
-
fb_idx = int(fb_idx_str)
|
|
151
|
-
tile_fb.framebuffers[fb_idx] = [
|
|
152
|
-
deserialize_hsbk(c) for c in colors_list
|
|
153
|
-
]
|
|
154
|
-
deserialized_fbs.append(tile_fb)
|
|
155
|
-
state_dict["tile_framebuffers"] = deserialized_fbs
|
|
156
|
-
|
|
157
|
-
return state_dict
|