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,503 +0,0 @@
|
|
|
1
|
-
"""Light 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 Light
|
|
10
|
-
from lifx_emulator.protocol.protocol_types import LightHsbk, LightLastHevCycleResult
|
|
11
|
-
|
|
12
|
-
if TYPE_CHECKING:
|
|
13
|
-
from lifx_emulator.devices import DeviceState
|
|
14
|
-
|
|
15
|
-
logger = logging.getLogger(__name__)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def _compute_average_color(colors: list[LightHsbk]) -> LightHsbk:
|
|
19
|
-
"""Compute average HSBK color from a list of LightHsbk colors.
|
|
20
|
-
|
|
21
|
-
Uses circular mean for hue to correctly handle hue wraparound
|
|
22
|
-
(e.g., average of 10° and 350° is 0°, not 180°).
|
|
23
|
-
|
|
24
|
-
Args:
|
|
25
|
-
colors: List of LightHsbk colors to average
|
|
26
|
-
|
|
27
|
-
Returns:
|
|
28
|
-
LightHsbk with averaged values using circular mean for hue
|
|
29
|
-
"""
|
|
30
|
-
import math
|
|
31
|
-
|
|
32
|
-
if not colors:
|
|
33
|
-
return LightHsbk(hue=0, saturation=0, brightness=0, kelvin=3500)
|
|
34
|
-
|
|
35
|
-
# Convert uint16 values to proper ranges and calculate circular mean
|
|
36
|
-
hue_x_total = 0.0
|
|
37
|
-
hue_y_total = 0.0
|
|
38
|
-
saturation_total = 0.0
|
|
39
|
-
brightness_total = 0.0
|
|
40
|
-
kelvin_total = 0
|
|
41
|
-
|
|
42
|
-
for color in colors:
|
|
43
|
-
# Convert uint16 hue (0-65535) to degrees (0-360)
|
|
44
|
-
hue_deg = round(float(color.hue) * 360 / 0x10000, 2)
|
|
45
|
-
|
|
46
|
-
# Convert uint16 sat/bright (0-65535) to float (0-1)
|
|
47
|
-
sat_float = round(float(color.saturation) / 0xFFFF, 4)
|
|
48
|
-
bright_float = round(float(color.brightness) / 0xFFFF, 4)
|
|
49
|
-
|
|
50
|
-
# Circular mean calculation for hue using sin/cos
|
|
51
|
-
hue_x_total += math.sin(hue_deg * 2.0 * math.pi / 360)
|
|
52
|
-
hue_y_total += math.cos(hue_deg * 2.0 * math.pi / 360)
|
|
53
|
-
|
|
54
|
-
# Regular sums for other components
|
|
55
|
-
saturation_total += sat_float
|
|
56
|
-
brightness_total += bright_float
|
|
57
|
-
kelvin_total += color.kelvin
|
|
58
|
-
|
|
59
|
-
# Calculate circular mean for hue
|
|
60
|
-
hue = math.atan2(hue_x_total, hue_y_total) / (2.0 * math.pi)
|
|
61
|
-
if hue < 0.0:
|
|
62
|
-
hue += 1.0
|
|
63
|
-
hue *= 360
|
|
64
|
-
hue = round(hue, 4)
|
|
65
|
-
|
|
66
|
-
# Calculate arithmetic means for other components
|
|
67
|
-
saturation = round(saturation_total / len(colors), 4)
|
|
68
|
-
brightness = round(brightness_total / len(colors), 4)
|
|
69
|
-
kelvin = round(kelvin_total / len(colors))
|
|
70
|
-
|
|
71
|
-
# Convert back to uint16 values
|
|
72
|
-
uint16_hue = int(round(0x10000 * hue) / 360) % 0x10000
|
|
73
|
-
uint16_saturation = int(round(0xFFFF * saturation))
|
|
74
|
-
uint16_brightness = int(round(0xFFFF * brightness))
|
|
75
|
-
|
|
76
|
-
return LightHsbk(
|
|
77
|
-
hue=uint16_hue,
|
|
78
|
-
saturation=uint16_saturation,
|
|
79
|
-
brightness=uint16_brightness,
|
|
80
|
-
kelvin=kelvin,
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
class GetColorHandler(PacketHandler):
|
|
85
|
-
"""Handle LightGet (101) -> LightState (107)."""
|
|
86
|
-
|
|
87
|
-
PKT_TYPE = Light.GetColor.PKT_TYPE
|
|
88
|
-
|
|
89
|
-
def handle(
|
|
90
|
-
self, device_state: DeviceState, packet: Any | None, res_required: bool
|
|
91
|
-
) -> list[Any]:
|
|
92
|
-
# For multizone/matrix devices, compute average color from all zones
|
|
93
|
-
# This provides backwards compatibility with clients that don't use
|
|
94
|
-
# zone-specific or tile-specific packets
|
|
95
|
-
color_to_return = device_state.color
|
|
96
|
-
|
|
97
|
-
if device_state.has_multizone and device_state.zone_colors:
|
|
98
|
-
# Return average of all zone colors
|
|
99
|
-
color_to_return = _compute_average_color(device_state.zone_colors)
|
|
100
|
-
elif device_state.has_matrix and device_state.tile_devices:
|
|
101
|
-
# Collect all zone colors from all tiles
|
|
102
|
-
all_zones = []
|
|
103
|
-
for tile in device_state.tile_devices:
|
|
104
|
-
all_zones.extend(tile["colors"])
|
|
105
|
-
color_to_return = _compute_average_color(all_zones)
|
|
106
|
-
|
|
107
|
-
return [
|
|
108
|
-
Light.StateColor(
|
|
109
|
-
color=color_to_return,
|
|
110
|
-
power=device_state.power_level,
|
|
111
|
-
label=device_state.label,
|
|
112
|
-
)
|
|
113
|
-
]
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
class SetColorHandler(PacketHandler):
|
|
117
|
-
"""Handle LightSetColor (102) -> LightState (107)."""
|
|
118
|
-
|
|
119
|
-
PKT_TYPE = Light.SetColor.PKT_TYPE
|
|
120
|
-
|
|
121
|
-
def handle(
|
|
122
|
-
self,
|
|
123
|
-
device_state: DeviceState,
|
|
124
|
-
packet: Light.SetColor | None,
|
|
125
|
-
res_required: bool,
|
|
126
|
-
) -> list[Any]:
|
|
127
|
-
if packet:
|
|
128
|
-
device_state.color = packet.color
|
|
129
|
-
c = packet.color
|
|
130
|
-
|
|
131
|
-
# For backwards compatibility: propagate color to all zones
|
|
132
|
-
# Multizone devices: update all zone colors
|
|
133
|
-
if device_state.has_multizone and device_state.zone_colors:
|
|
134
|
-
for i in range(len(device_state.zone_colors)):
|
|
135
|
-
device_state.zone_colors[i] = packet.color
|
|
136
|
-
logger.info(
|
|
137
|
-
f"Color set to HSBK({c.hue}, {c.saturation}, "
|
|
138
|
-
f"{c.brightness}, {c.kelvin}) across all "
|
|
139
|
-
f"{len(device_state.zone_colors)} zones, "
|
|
140
|
-
f"duration={packet.duration}ms"
|
|
141
|
-
)
|
|
142
|
-
# Matrix devices: update all tile zones
|
|
143
|
-
elif device_state.has_matrix and device_state.tile_devices:
|
|
144
|
-
total_zones = 0
|
|
145
|
-
for tile in device_state.tile_devices:
|
|
146
|
-
for i in range(len(tile["colors"])):
|
|
147
|
-
tile["colors"][i] = packet.color
|
|
148
|
-
total_zones += len(tile["colors"])
|
|
149
|
-
logger.info(
|
|
150
|
-
f"Color set to HSBK({c.hue}, {c.saturation}, "
|
|
151
|
-
f"{c.brightness}, {c.kelvin}) across all {total_zones} zones, "
|
|
152
|
-
f"duration={packet.duration}ms"
|
|
153
|
-
)
|
|
154
|
-
else:
|
|
155
|
-
# Simple color device
|
|
156
|
-
logger.info(
|
|
157
|
-
f"Color set to HSBK({c.hue}, {c.saturation}, "
|
|
158
|
-
f"{c.brightness}, {c.kelvin}), duration={packet.duration}ms"
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
if res_required:
|
|
162
|
-
return [
|
|
163
|
-
Light.StateColor(
|
|
164
|
-
color=device_state.color,
|
|
165
|
-
power=device_state.power_level,
|
|
166
|
-
label=device_state.label,
|
|
167
|
-
)
|
|
168
|
-
]
|
|
169
|
-
return []
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
class GetPowerHandler(PacketHandler):
|
|
173
|
-
"""Handle LightGetPower (116) -> LightStatePower (118)."""
|
|
174
|
-
|
|
175
|
-
PKT_TYPE = Light.GetPower.PKT_TYPE
|
|
176
|
-
|
|
177
|
-
def handle(
|
|
178
|
-
self, device_state: DeviceState, packet: Any | None, res_required: bool
|
|
179
|
-
) -> list[Any]:
|
|
180
|
-
return [Light.StatePower(level=device_state.power_level)]
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
class SetPowerHandler(PacketHandler):
|
|
184
|
-
"""Handle LightSetPower (117) -> LightStatePower (118)."""
|
|
185
|
-
|
|
186
|
-
PKT_TYPE = Light.SetPower.PKT_TYPE
|
|
187
|
-
|
|
188
|
-
def handle(
|
|
189
|
-
self,
|
|
190
|
-
device_state: DeviceState,
|
|
191
|
-
packet: Light.SetPower | None,
|
|
192
|
-
res_required: bool,
|
|
193
|
-
) -> list[Any]:
|
|
194
|
-
if packet:
|
|
195
|
-
device_state.power_level = packet.level
|
|
196
|
-
logger.info(
|
|
197
|
-
f"Light power set to {packet.level}, duration={packet.duration}ms"
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
if res_required:
|
|
201
|
-
return [Light.StatePower(level=device_state.power_level)]
|
|
202
|
-
return []
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
class SetWaveformHandler(PacketHandler):
|
|
206
|
-
"""Handle LightSetWaveform (103) -> LightState (107)."""
|
|
207
|
-
|
|
208
|
-
PKT_TYPE = Light.SetWaveform.PKT_TYPE
|
|
209
|
-
|
|
210
|
-
def handle(
|
|
211
|
-
self,
|
|
212
|
-
device_state: DeviceState,
|
|
213
|
-
packet: Light.SetWaveform | None,
|
|
214
|
-
res_required: bool,
|
|
215
|
-
) -> list[Any]:
|
|
216
|
-
if packet:
|
|
217
|
-
# Store waveform state
|
|
218
|
-
device_state.waveform_active = True
|
|
219
|
-
device_state.waveform_transient = packet.transient
|
|
220
|
-
device_state.waveform_color = packet.color
|
|
221
|
-
device_state.waveform_period_ms = packet.period
|
|
222
|
-
device_state.waveform_cycles = packet.cycles
|
|
223
|
-
device_state.waveform_skew_ratio = packet.skew_ratio
|
|
224
|
-
device_state.waveform_type = int(packet.waveform)
|
|
225
|
-
|
|
226
|
-
# If not transient, update the color state
|
|
227
|
-
if not packet.transient:
|
|
228
|
-
device_state.color = packet.color
|
|
229
|
-
|
|
230
|
-
# For backwards compatibility: propagate color to all zones
|
|
231
|
-
# Multizone devices: update all zone colors
|
|
232
|
-
if device_state.has_multizone and device_state.zone_colors:
|
|
233
|
-
for i in range(len(device_state.zone_colors)):
|
|
234
|
-
device_state.zone_colors[i] = packet.color
|
|
235
|
-
# Matrix devices: update all tile zones
|
|
236
|
-
elif device_state.has_matrix and device_state.tile_devices:
|
|
237
|
-
for tile in device_state.tile_devices:
|
|
238
|
-
for i in range(len(tile["colors"])):
|
|
239
|
-
tile["colors"][i] = packet.color
|
|
240
|
-
|
|
241
|
-
logger.info(
|
|
242
|
-
f"Waveform set: type={packet.waveform}, "
|
|
243
|
-
f"transient={packet.transient}, period={packet.period}ms, "
|
|
244
|
-
f"cycles={packet.cycles}, skew={packet.skew_ratio}"
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
if res_required:
|
|
248
|
-
# Use GetColorHandler to get proper averaged color if needed
|
|
249
|
-
handler = GetColorHandler()
|
|
250
|
-
return handler.handle(device_state, None, res_required)
|
|
251
|
-
return []
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
class SetWaveformOptionalHandler(PacketHandler):
|
|
255
|
-
"""Handle LightSetWaveformOptional (119) -> LightState (107)."""
|
|
256
|
-
|
|
257
|
-
PKT_TYPE = Light.SetWaveformOptional.PKT_TYPE
|
|
258
|
-
|
|
259
|
-
def handle(
|
|
260
|
-
self,
|
|
261
|
-
device_state: DeviceState,
|
|
262
|
-
packet: Light.SetWaveformOptional | None,
|
|
263
|
-
res_required: bool,
|
|
264
|
-
) -> list[Any]:
|
|
265
|
-
if packet:
|
|
266
|
-
# Store waveform state
|
|
267
|
-
device_state.waveform_active = True
|
|
268
|
-
device_state.waveform_transient = packet.transient
|
|
269
|
-
device_state.waveform_period_ms = packet.period
|
|
270
|
-
device_state.waveform_cycles = packet.cycles
|
|
271
|
-
device_state.waveform_skew_ratio = packet.skew_ratio
|
|
272
|
-
device_state.waveform_type = int(packet.waveform)
|
|
273
|
-
|
|
274
|
-
# Apply color components selectively based on flags
|
|
275
|
-
if not packet.transient:
|
|
276
|
-
if packet.set_hue:
|
|
277
|
-
device_state.color.hue = packet.color.hue
|
|
278
|
-
if packet.set_saturation:
|
|
279
|
-
device_state.color.saturation = packet.color.saturation
|
|
280
|
-
if packet.set_brightness:
|
|
281
|
-
device_state.color.brightness = packet.color.brightness
|
|
282
|
-
if packet.set_kelvin:
|
|
283
|
-
device_state.color.kelvin = packet.color.kelvin
|
|
284
|
-
|
|
285
|
-
# Backwards compatibility propagates color changes to zones
|
|
286
|
-
# Multizone devices: update all zone colors
|
|
287
|
-
if device_state.has_multizone and device_state.zone_colors:
|
|
288
|
-
for zone_color in device_state.zone_colors:
|
|
289
|
-
if packet.set_hue:
|
|
290
|
-
zone_color.hue = packet.color.hue
|
|
291
|
-
if packet.set_saturation:
|
|
292
|
-
zone_color.saturation = packet.color.saturation
|
|
293
|
-
if packet.set_brightness:
|
|
294
|
-
zone_color.brightness = packet.color.brightness
|
|
295
|
-
if packet.set_kelvin:
|
|
296
|
-
zone_color.kelvin = packet.color.kelvin
|
|
297
|
-
# Matrix devices: update all tile zones
|
|
298
|
-
elif device_state.has_matrix and device_state.tile_devices:
|
|
299
|
-
for tile in device_state.tile_devices:
|
|
300
|
-
for zone_color in tile["colors"]:
|
|
301
|
-
if packet.set_hue:
|
|
302
|
-
zone_color.hue = packet.color.hue
|
|
303
|
-
if packet.set_saturation:
|
|
304
|
-
zone_color.saturation = packet.color.saturation
|
|
305
|
-
if packet.set_brightness:
|
|
306
|
-
zone_color.brightness = packet.color.brightness
|
|
307
|
-
if packet.set_kelvin:
|
|
308
|
-
zone_color.kelvin = packet.color.kelvin
|
|
309
|
-
|
|
310
|
-
# Store the waveform color (all components)
|
|
311
|
-
device_state.waveform_color = packet.color
|
|
312
|
-
|
|
313
|
-
logger.info(
|
|
314
|
-
f"Waveform optional set: type={packet.waveform}, "
|
|
315
|
-
f"transient={packet.transient}, period={packet.period}ms, "
|
|
316
|
-
f"cycles={packet.cycles}, components=[H:{packet.set_hue},"
|
|
317
|
-
f"S:{packet.set_saturation},B:{packet.set_brightness},"
|
|
318
|
-
f"K:{packet.set_kelvin}]"
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
if res_required:
|
|
322
|
-
# Use GetColorHandler to get proper averaged color if needed
|
|
323
|
-
handler = GetColorHandler()
|
|
324
|
-
return handler.handle(device_state, None, res_required)
|
|
325
|
-
return []
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
class GetInfraredHandler(PacketHandler):
|
|
329
|
-
"""Handle LightGetInfrared (120) -> LightStateInfrared (121)."""
|
|
330
|
-
|
|
331
|
-
PKT_TYPE = Light.GetInfrared.PKT_TYPE
|
|
332
|
-
|
|
333
|
-
def handle(
|
|
334
|
-
self, device_state: DeviceState, packet: Any | None, res_required: bool
|
|
335
|
-
) -> list[Any]:
|
|
336
|
-
if not device_state.has_infrared:
|
|
337
|
-
return []
|
|
338
|
-
return [Light.StateInfrared(brightness=device_state.infrared_brightness)]
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
class SetInfraredHandler(PacketHandler):
|
|
342
|
-
"""Handle LightSetInfrared (122) -> LightStateInfrared (121)."""
|
|
343
|
-
|
|
344
|
-
PKT_TYPE = Light.SetInfrared.PKT_TYPE
|
|
345
|
-
|
|
346
|
-
def handle(
|
|
347
|
-
self,
|
|
348
|
-
device_state: DeviceState,
|
|
349
|
-
packet: Light.SetInfrared | None,
|
|
350
|
-
res_required: bool,
|
|
351
|
-
) -> list[Any]:
|
|
352
|
-
if not device_state.has_infrared:
|
|
353
|
-
return []
|
|
354
|
-
if packet:
|
|
355
|
-
device_state.infrared_brightness = packet.brightness
|
|
356
|
-
logger.info("Infrared brightness set to %s", packet.brightness)
|
|
357
|
-
|
|
358
|
-
if res_required:
|
|
359
|
-
return [Light.StateInfrared(brightness=device_state.infrared_brightness)]
|
|
360
|
-
return []
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
class GetHevCycleHandler(PacketHandler):
|
|
364
|
-
"""Handle LightGetHevCycle (142) -> LightStateHevCycle (144)."""
|
|
365
|
-
|
|
366
|
-
PKT_TYPE = Light.GetHevCycle.PKT_TYPE
|
|
367
|
-
|
|
368
|
-
def handle(
|
|
369
|
-
self, device_state: DeviceState, packet: Any | None, res_required: bool
|
|
370
|
-
) -> list[Any]:
|
|
371
|
-
if not device_state.has_hev:
|
|
372
|
-
return []
|
|
373
|
-
return [
|
|
374
|
-
Light.StateHevCycle(
|
|
375
|
-
duration_s=device_state.hev_cycle_duration_s,
|
|
376
|
-
remaining_s=device_state.hev_cycle_remaining_s,
|
|
377
|
-
last_power=device_state.hev_cycle_last_power,
|
|
378
|
-
)
|
|
379
|
-
]
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
class SetHevCycleHandler(PacketHandler):
|
|
383
|
-
"""Handle LightSetHevCycle (143) -> LightStateHevCycle (144)."""
|
|
384
|
-
|
|
385
|
-
PKT_TYPE = Light.SetHevCycle.PKT_TYPE
|
|
386
|
-
|
|
387
|
-
def handle(
|
|
388
|
-
self,
|
|
389
|
-
device_state: DeviceState,
|
|
390
|
-
packet: Light.SetHevCycle | None,
|
|
391
|
-
res_required: bool,
|
|
392
|
-
) -> list[Any]:
|
|
393
|
-
if not device_state.has_hev:
|
|
394
|
-
return []
|
|
395
|
-
if packet:
|
|
396
|
-
device_state.hev_cycle_duration_s = packet.duration_s
|
|
397
|
-
if packet.enable:
|
|
398
|
-
device_state.hev_cycle_remaining_s = packet.duration_s
|
|
399
|
-
else:
|
|
400
|
-
device_state.hev_cycle_remaining_s = 0
|
|
401
|
-
logger.info(
|
|
402
|
-
f"HEV cycle set: enable={packet.enable}, duration={packet.duration_s}s"
|
|
403
|
-
)
|
|
404
|
-
|
|
405
|
-
if res_required:
|
|
406
|
-
return [
|
|
407
|
-
Light.StateHevCycle(
|
|
408
|
-
duration_s=device_state.hev_cycle_duration_s,
|
|
409
|
-
remaining_s=device_state.hev_cycle_remaining_s,
|
|
410
|
-
last_power=device_state.hev_cycle_last_power,
|
|
411
|
-
)
|
|
412
|
-
]
|
|
413
|
-
return []
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
class GetHevCycleConfigurationHandler(PacketHandler):
|
|
417
|
-
"""Handle LightGetHevCycleConfiguration (145).
|
|
418
|
-
|
|
419
|
-
Returns LightStateHevCycleConfiguration (147).
|
|
420
|
-
"""
|
|
421
|
-
|
|
422
|
-
PKT_TYPE = Light.GetHevCycleConfiguration.PKT_TYPE
|
|
423
|
-
|
|
424
|
-
def handle(
|
|
425
|
-
self, device_state: DeviceState, packet: Any | None, res_required: bool
|
|
426
|
-
) -> list[Any]:
|
|
427
|
-
if not device_state.has_hev:
|
|
428
|
-
return []
|
|
429
|
-
return [
|
|
430
|
-
Light.StateHevCycleConfiguration(
|
|
431
|
-
indication=device_state.hev_indication,
|
|
432
|
-
duration_s=device_state.hev_cycle_duration_s,
|
|
433
|
-
)
|
|
434
|
-
]
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
class SetHevCycleConfigurationHandler(PacketHandler):
|
|
438
|
-
"""Handle LightSetHevCycleConfiguration (146).
|
|
439
|
-
|
|
440
|
-
Returns LightStateHevCycleConfiguration (147).
|
|
441
|
-
"""
|
|
442
|
-
|
|
443
|
-
PKT_TYPE = Light.SetHevCycleConfiguration.PKT_TYPE
|
|
444
|
-
|
|
445
|
-
def handle(
|
|
446
|
-
self,
|
|
447
|
-
device_state: DeviceState,
|
|
448
|
-
packet: Light.SetHevCycleConfiguration | None,
|
|
449
|
-
res_required: bool,
|
|
450
|
-
) -> list[Any]:
|
|
451
|
-
if not device_state.has_hev:
|
|
452
|
-
return []
|
|
453
|
-
if packet:
|
|
454
|
-
device_state.hev_indication = packet.indication
|
|
455
|
-
device_state.hev_cycle_duration_s = packet.duration_s
|
|
456
|
-
logger.info(
|
|
457
|
-
f"HEV config set: indication={packet.indication}, "
|
|
458
|
-
f"duration={packet.duration_s}s"
|
|
459
|
-
)
|
|
460
|
-
|
|
461
|
-
if res_required:
|
|
462
|
-
return [
|
|
463
|
-
Light.StateHevCycleConfiguration(
|
|
464
|
-
indication=device_state.hev_indication,
|
|
465
|
-
duration_s=device_state.hev_cycle_duration_s,
|
|
466
|
-
)
|
|
467
|
-
]
|
|
468
|
-
return []
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
class GetLastHevCycleResultHandler(PacketHandler):
|
|
472
|
-
"""Handle LightGetLastHevCycleResult (148) -> LightStateLastHevCycleResult (149)."""
|
|
473
|
-
|
|
474
|
-
PKT_TYPE = Light.GetLastHevCycleResult.PKT_TYPE
|
|
475
|
-
|
|
476
|
-
def handle(
|
|
477
|
-
self, device_state: DeviceState, packet: Any | None, res_required: bool
|
|
478
|
-
) -> list[Any]:
|
|
479
|
-
if not device_state.has_hev:
|
|
480
|
-
return []
|
|
481
|
-
return [
|
|
482
|
-
Light.StateLastHevCycleResult(
|
|
483
|
-
result=LightLastHevCycleResult(device_state.hev_last_result)
|
|
484
|
-
)
|
|
485
|
-
]
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
# List of all light handlers for easy registration
|
|
489
|
-
ALL_LIGHT_HANDLERS = [
|
|
490
|
-
GetColorHandler(),
|
|
491
|
-
SetColorHandler(),
|
|
492
|
-
GetPowerHandler(),
|
|
493
|
-
SetPowerHandler(),
|
|
494
|
-
SetWaveformHandler(),
|
|
495
|
-
SetWaveformOptionalHandler(),
|
|
496
|
-
GetInfraredHandler(),
|
|
497
|
-
SetInfraredHandler(),
|
|
498
|
-
GetHevCycleHandler(),
|
|
499
|
-
SetHevCycleHandler(),
|
|
500
|
-
GetHevCycleConfigurationHandler(),
|
|
501
|
-
SetHevCycleConfigurationHandler(),
|
|
502
|
-
GetLastHevCycleResultHandler(),
|
|
503
|
-
]
|