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,375 +0,0 @@
|
|
|
1
|
-
"""Device builder with fluent API for creating emulated LIFX devices."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import time
|
|
6
|
-
from typing import TYPE_CHECKING
|
|
7
|
-
|
|
8
|
-
from lifx_emulator.devices import DeviceState, EmulatedLifxDevice
|
|
9
|
-
from lifx_emulator.devices.state_restorer import StateRestorer
|
|
10
|
-
from lifx_emulator.devices.states import (
|
|
11
|
-
CoreDeviceState,
|
|
12
|
-
GroupState,
|
|
13
|
-
HevState,
|
|
14
|
-
InfraredState,
|
|
15
|
-
LocationState,
|
|
16
|
-
MatrixState,
|
|
17
|
-
MultiZoneState,
|
|
18
|
-
NetworkState,
|
|
19
|
-
WaveformState,
|
|
20
|
-
)
|
|
21
|
-
from lifx_emulator.factories.default_config import DefaultColorConfig
|
|
22
|
-
from lifx_emulator.factories.firmware_config import FirmwareConfig
|
|
23
|
-
from lifx_emulator.factories.serial_generator import SerialGenerator
|
|
24
|
-
from lifx_emulator.products.specs import (
|
|
25
|
-
get_default_tile_count,
|
|
26
|
-
get_default_zone_count,
|
|
27
|
-
get_tile_dimensions,
|
|
28
|
-
)
|
|
29
|
-
from lifx_emulator.protocol.protocol_types import LightHsbk
|
|
30
|
-
|
|
31
|
-
if TYPE_CHECKING:
|
|
32
|
-
from lifx_emulator.devices import DevicePersistenceAsyncFile
|
|
33
|
-
from lifx_emulator.products.registry import ProductInfo
|
|
34
|
-
from lifx_emulator.scenarios import HierarchicalScenarioManager
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
class DeviceBuilder:
|
|
38
|
-
"""Fluent API builder for creating emulated LIFX devices.
|
|
39
|
-
|
|
40
|
-
This builder separates device construction into discrete, testable steps:
|
|
41
|
-
1. Product configuration (serial, firmware, color)
|
|
42
|
-
2. Capability-specific configuration (zones, tiles)
|
|
43
|
-
3. State composition
|
|
44
|
-
4. Device creation
|
|
45
|
-
|
|
46
|
-
Examples:
|
|
47
|
-
>>> from lifx_emulator.products.registry import get_product
|
|
48
|
-
>>> product = get_product(27) # LIFX A19
|
|
49
|
-
>>> builder = DeviceBuilder(product)
|
|
50
|
-
>>> device = builder.with_serial("d073d5000001").build()
|
|
51
|
-
|
|
52
|
-
>>> # Multizone device with custom zones
|
|
53
|
-
>>> product = get_product(32) # LIFX Z
|
|
54
|
-
>>> device = (
|
|
55
|
-
... DeviceBuilder(product)
|
|
56
|
-
... .with_zone_count(24)
|
|
57
|
-
... .with_extended_multizone(False)
|
|
58
|
-
... .build()
|
|
59
|
-
... )
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
def __init__(self, product_info: ProductInfo):
|
|
63
|
-
"""Initialize builder with product information.
|
|
64
|
-
|
|
65
|
-
Args:
|
|
66
|
-
product_info: Product information from registry
|
|
67
|
-
"""
|
|
68
|
-
self._product_info = product_info
|
|
69
|
-
|
|
70
|
-
# Configuration state
|
|
71
|
-
self._serial: str | None = None
|
|
72
|
-
self._zone_count: int | None = None
|
|
73
|
-
self._extended_multizone: bool | None = None
|
|
74
|
-
self._tile_count: int | None = None
|
|
75
|
-
self._tile_width: int | None = None
|
|
76
|
-
self._tile_height: int | None = None
|
|
77
|
-
self._firmware_version: tuple[int, int] | None = None
|
|
78
|
-
self._storage: DevicePersistenceAsyncFile | None = None
|
|
79
|
-
self._scenario_manager: HierarchicalScenarioManager | None = None
|
|
80
|
-
self._color: LightHsbk | None = None
|
|
81
|
-
|
|
82
|
-
# Helper services
|
|
83
|
-
self._serial_generator = SerialGenerator()
|
|
84
|
-
self._color_config = DefaultColorConfig()
|
|
85
|
-
self._firmware_config = FirmwareConfig()
|
|
86
|
-
|
|
87
|
-
def with_serial(self, serial: str) -> DeviceBuilder:
|
|
88
|
-
"""Set device serial number.
|
|
89
|
-
|
|
90
|
-
Args:
|
|
91
|
-
serial: 12-character hex serial number
|
|
92
|
-
|
|
93
|
-
Returns:
|
|
94
|
-
Self for method chaining
|
|
95
|
-
"""
|
|
96
|
-
self._serial = serial
|
|
97
|
-
return self
|
|
98
|
-
|
|
99
|
-
def with_zone_count(self, zone_count: int) -> DeviceBuilder:
|
|
100
|
-
"""Set zone count for multizone devices.
|
|
101
|
-
|
|
102
|
-
Args:
|
|
103
|
-
zone_count: Number of zones
|
|
104
|
-
|
|
105
|
-
Returns:
|
|
106
|
-
Self for method chaining
|
|
107
|
-
"""
|
|
108
|
-
self._zone_count = zone_count
|
|
109
|
-
return self
|
|
110
|
-
|
|
111
|
-
def with_extended_multizone(self, extended: bool) -> DeviceBuilder:
|
|
112
|
-
"""Enable/disable extended multizone support.
|
|
113
|
-
|
|
114
|
-
Args:
|
|
115
|
-
extended: Whether to enable extended multizone
|
|
116
|
-
|
|
117
|
-
Returns:
|
|
118
|
-
Self for method chaining
|
|
119
|
-
"""
|
|
120
|
-
self._extended_multizone = extended
|
|
121
|
-
return self
|
|
122
|
-
|
|
123
|
-
def with_tile_count(self, tile_count: int) -> DeviceBuilder:
|
|
124
|
-
"""Set tile count for matrix devices.
|
|
125
|
-
|
|
126
|
-
Args:
|
|
127
|
-
tile_count: Number of tiles
|
|
128
|
-
|
|
129
|
-
Returns:
|
|
130
|
-
Self for method chaining
|
|
131
|
-
"""
|
|
132
|
-
self._tile_count = tile_count
|
|
133
|
-
return self
|
|
134
|
-
|
|
135
|
-
def with_tile_dimensions(self, width: int, height: int) -> DeviceBuilder:
|
|
136
|
-
"""Set tile dimensions for matrix devices.
|
|
137
|
-
|
|
138
|
-
Args:
|
|
139
|
-
width: Tile width in zones
|
|
140
|
-
height: Tile height in zones
|
|
141
|
-
|
|
142
|
-
Returns:
|
|
143
|
-
Self for method chaining
|
|
144
|
-
"""
|
|
145
|
-
self._tile_width = width
|
|
146
|
-
self._tile_height = height
|
|
147
|
-
return self
|
|
148
|
-
|
|
149
|
-
def with_firmware_version(self, major: int, minor: int) -> DeviceBuilder:
|
|
150
|
-
"""Set firmware version.
|
|
151
|
-
|
|
152
|
-
Args:
|
|
153
|
-
major: Major version number
|
|
154
|
-
minor: Minor version number
|
|
155
|
-
|
|
156
|
-
Returns:
|
|
157
|
-
Self for method chaining
|
|
158
|
-
"""
|
|
159
|
-
self._firmware_version = (major, minor)
|
|
160
|
-
return self
|
|
161
|
-
|
|
162
|
-
def with_storage(self, storage: DevicePersistenceAsyncFile) -> DeviceBuilder:
|
|
163
|
-
"""Enable persistent storage.
|
|
164
|
-
|
|
165
|
-
Args:
|
|
166
|
-
storage: Async storage backend
|
|
167
|
-
|
|
168
|
-
Returns:
|
|
169
|
-
Self for method chaining
|
|
170
|
-
"""
|
|
171
|
-
self._storage = storage
|
|
172
|
-
return self
|
|
173
|
-
|
|
174
|
-
def with_scenario_manager(
|
|
175
|
-
self, scenario_manager: HierarchicalScenarioManager
|
|
176
|
-
) -> DeviceBuilder:
|
|
177
|
-
"""Set scenario manager for testing.
|
|
178
|
-
|
|
179
|
-
Args:
|
|
180
|
-
scenario_manager: Scenario manager instance
|
|
181
|
-
|
|
182
|
-
Returns:
|
|
183
|
-
Self for method chaining
|
|
184
|
-
"""
|
|
185
|
-
self._scenario_manager = scenario_manager
|
|
186
|
-
return self
|
|
187
|
-
|
|
188
|
-
def with_color(self, color: LightHsbk) -> DeviceBuilder:
|
|
189
|
-
"""Set initial device color.
|
|
190
|
-
|
|
191
|
-
Args:
|
|
192
|
-
color: Initial color
|
|
193
|
-
|
|
194
|
-
Returns:
|
|
195
|
-
Self for method chaining
|
|
196
|
-
"""
|
|
197
|
-
self._color = color
|
|
198
|
-
return self
|
|
199
|
-
|
|
200
|
-
def build(self) -> EmulatedLifxDevice:
|
|
201
|
-
"""Build the emulated device.
|
|
202
|
-
|
|
203
|
-
Returns:
|
|
204
|
-
Configured EmulatedLifxDevice instance
|
|
205
|
-
"""
|
|
206
|
-
# 1. Generate/validate serial
|
|
207
|
-
serial = self._serial or self._serial_generator.generate(self._product_info)
|
|
208
|
-
|
|
209
|
-
# 2. Apply product-specific defaults
|
|
210
|
-
self._apply_product_defaults()
|
|
211
|
-
|
|
212
|
-
# 3. Determine firmware version
|
|
213
|
-
version_major, version_minor = self._firmware_config.get_firmware_version(
|
|
214
|
-
product_id=self._product_info.pid,
|
|
215
|
-
extended_multizone=self._extended_multizone,
|
|
216
|
-
override=self._firmware_version,
|
|
217
|
-
)
|
|
218
|
-
|
|
219
|
-
# 4. Get default color
|
|
220
|
-
color = self._color or self._color_config.get_default_color(self._product_info)
|
|
221
|
-
|
|
222
|
-
# 5. Create core state
|
|
223
|
-
core = self._create_core_state(serial, color, version_major, version_minor)
|
|
224
|
-
|
|
225
|
-
# 6. Create basic states
|
|
226
|
-
network = NetworkState()
|
|
227
|
-
location = LocationState()
|
|
228
|
-
group = GroupState()
|
|
229
|
-
waveform = WaveformState()
|
|
230
|
-
|
|
231
|
-
# 7. Create capability-specific states
|
|
232
|
-
infrared_state = self._create_infrared_state()
|
|
233
|
-
hev_state = self._create_hev_state()
|
|
234
|
-
multizone_state = self._create_multizone_state()
|
|
235
|
-
matrix_state = self._create_matrix_state()
|
|
236
|
-
|
|
237
|
-
# 8. Determine extended multizone support
|
|
238
|
-
firmware_version_int = (version_major << 16) | version_minor
|
|
239
|
-
has_extended_multizone = self._product_info.supports_extended_multizone(
|
|
240
|
-
firmware_version_int
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
# 9. Compose device state
|
|
244
|
-
state = DeviceState(
|
|
245
|
-
core=core,
|
|
246
|
-
network=network,
|
|
247
|
-
location=location,
|
|
248
|
-
group=group,
|
|
249
|
-
waveform=waveform,
|
|
250
|
-
infrared=infrared_state,
|
|
251
|
-
hev=hev_state,
|
|
252
|
-
multizone=multizone_state,
|
|
253
|
-
matrix=matrix_state,
|
|
254
|
-
has_color=self._product_info.has_color,
|
|
255
|
-
has_infrared=self._product_info.has_infrared,
|
|
256
|
-
has_multizone=self._product_info.has_multizone,
|
|
257
|
-
has_extended_multizone=has_extended_multizone,
|
|
258
|
-
has_matrix=self._product_info.has_matrix,
|
|
259
|
-
has_hev=self._product_info.has_hev,
|
|
260
|
-
has_relays=self._product_info.has_relays,
|
|
261
|
-
has_buttons=self._product_info.has_buttons,
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
# 10. Restore saved state if persistence enabled
|
|
265
|
-
if self._storage:
|
|
266
|
-
restorer = StateRestorer(self._storage)
|
|
267
|
-
restorer.restore_if_available(state)
|
|
268
|
-
|
|
269
|
-
# 11. Create device
|
|
270
|
-
return EmulatedLifxDevice(
|
|
271
|
-
state, storage=self._storage, scenario_manager=self._scenario_manager
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
def _apply_product_defaults(self):
|
|
275
|
-
"""Apply product-specific defaults from specs."""
|
|
276
|
-
# Zone count for multizone devices
|
|
277
|
-
if self._product_info.has_multizone and self._zone_count is None:
|
|
278
|
-
self._zone_count = get_default_zone_count(self._product_info.pid) or 16
|
|
279
|
-
|
|
280
|
-
# Tile configuration for matrix devices
|
|
281
|
-
if self._product_info.has_matrix:
|
|
282
|
-
# Get tile dimensions from specs (always use specs for dimensions)
|
|
283
|
-
tile_dims = get_tile_dimensions(self._product_info.pid)
|
|
284
|
-
if tile_dims:
|
|
285
|
-
self._tile_width, self._tile_height = tile_dims
|
|
286
|
-
else:
|
|
287
|
-
# Fallback to standard 8x8 tiles
|
|
288
|
-
if self._tile_width is None:
|
|
289
|
-
self._tile_width = 8
|
|
290
|
-
if self._tile_height is None:
|
|
291
|
-
self._tile_height = 8
|
|
292
|
-
|
|
293
|
-
# Get default tile count from specs
|
|
294
|
-
if self._tile_count is None:
|
|
295
|
-
specs_tile_count = get_default_tile_count(self._product_info.pid)
|
|
296
|
-
self._tile_count = (
|
|
297
|
-
specs_tile_count if specs_tile_count is not None else 5
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
def _create_core_state(
|
|
301
|
-
self, serial: str, color: LightHsbk, version_major: int, version_minor: int
|
|
302
|
-
) -> CoreDeviceState:
|
|
303
|
-
"""Create core device state.
|
|
304
|
-
|
|
305
|
-
Args:
|
|
306
|
-
serial: Device serial number
|
|
307
|
-
color: Initial color
|
|
308
|
-
version_major: Firmware major version
|
|
309
|
-
version_minor: Firmware minor version
|
|
310
|
-
|
|
311
|
-
Returns:
|
|
312
|
-
CoreDeviceState instance
|
|
313
|
-
"""
|
|
314
|
-
label = f"{self._product_info.name} {serial[-6:]}"
|
|
315
|
-
|
|
316
|
-
return CoreDeviceState(
|
|
317
|
-
serial=serial,
|
|
318
|
-
label=label,
|
|
319
|
-
power_level=65535, # Default to on
|
|
320
|
-
color=color,
|
|
321
|
-
vendor=self._product_info.vendor,
|
|
322
|
-
product=self._product_info.pid,
|
|
323
|
-
version_major=version_major,
|
|
324
|
-
version_minor=version_minor,
|
|
325
|
-
build_timestamp=int(time.time()),
|
|
326
|
-
mac_address=bytes.fromhex(serial[:12]),
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
def _create_infrared_state(self) -> InfraredState | None:
|
|
330
|
-
"""Create infrared state if product has infrared capability.
|
|
331
|
-
|
|
332
|
-
Returns:
|
|
333
|
-
InfraredState instance or None
|
|
334
|
-
"""
|
|
335
|
-
if self._product_info.has_infrared:
|
|
336
|
-
return InfraredState(infrared_brightness=16384)
|
|
337
|
-
return None
|
|
338
|
-
|
|
339
|
-
def _create_hev_state(self) -> HevState | None:
|
|
340
|
-
"""Create HEV state if product has HEV capability.
|
|
341
|
-
|
|
342
|
-
Returns:
|
|
343
|
-
HevState instance or None
|
|
344
|
-
"""
|
|
345
|
-
if self._product_info.has_hev:
|
|
346
|
-
return HevState()
|
|
347
|
-
return None
|
|
348
|
-
|
|
349
|
-
def _create_multizone_state(self) -> MultiZoneState | None:
|
|
350
|
-
"""Create multizone state if product has multizone capability.
|
|
351
|
-
|
|
352
|
-
Returns:
|
|
353
|
-
MultiZoneState instance or None
|
|
354
|
-
"""
|
|
355
|
-
if self._product_info.has_multizone and self._zone_count:
|
|
356
|
-
return MultiZoneState(
|
|
357
|
-
zone_count=self._zone_count,
|
|
358
|
-
zone_colors=[], # Will be initialized by EmulatedLifxDevice
|
|
359
|
-
)
|
|
360
|
-
return None
|
|
361
|
-
|
|
362
|
-
def _create_matrix_state(self) -> MatrixState | None:
|
|
363
|
-
"""Create matrix state if product has matrix capability.
|
|
364
|
-
|
|
365
|
-
Returns:
|
|
366
|
-
MatrixState instance or None
|
|
367
|
-
"""
|
|
368
|
-
if self._product_info.has_matrix and self._tile_count:
|
|
369
|
-
return MatrixState(
|
|
370
|
-
tile_count=self._tile_count,
|
|
371
|
-
tile_devices=[], # Will be initialized by EmulatedLifxDevice
|
|
372
|
-
tile_width=self._tile_width or 8,
|
|
373
|
-
tile_height=self._tile_height or 8,
|
|
374
|
-
)
|
|
375
|
-
return None
|
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
"""Default configuration helpers for device factory."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from typing import TYPE_CHECKING
|
|
6
|
-
|
|
7
|
-
from lifx_emulator.protocol.protocol_types import LightHsbk
|
|
8
|
-
|
|
9
|
-
if TYPE_CHECKING:
|
|
10
|
-
from lifx_emulator.products.registry import ProductInfo
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class DefaultColorConfig:
|
|
14
|
-
"""Determines default colors for devices based on product capabilities.
|
|
15
|
-
|
|
16
|
-
Different device types get unique default colors for visual identification:
|
|
17
|
-
- Brightness-only: White at 2700K
|
|
18
|
-
- Color temperature: White at 3500K (middle of range)
|
|
19
|
-
- Matrix devices: Cyan
|
|
20
|
-
- Multizone devices: Red
|
|
21
|
-
- HEV devices: Green
|
|
22
|
-
- Infrared devices: Red
|
|
23
|
-
- Color devices: Orange
|
|
24
|
-
|
|
25
|
-
Examples:
|
|
26
|
-
>>> from lifx_emulator.products.registry import get_product
|
|
27
|
-
>>> config = DefaultColorConfig()
|
|
28
|
-
>>> product = get_product(27) # LIFX A19 (color)
|
|
29
|
-
>>> color = config.get_default_color(product)
|
|
30
|
-
>>> color.saturation
|
|
31
|
-
65535
|
|
32
|
-
"""
|
|
33
|
-
|
|
34
|
-
# Hue values for different device types (0-65535)
|
|
35
|
-
HUE_CYAN = 43690
|
|
36
|
-
HUE_RED = 0
|
|
37
|
-
HUE_GREEN = 32768
|
|
38
|
-
HUE_ORANGE = 21845
|
|
39
|
-
|
|
40
|
-
# Default brightness (50%)
|
|
41
|
-
DEFAULT_BRIGHTNESS = 32768
|
|
42
|
-
|
|
43
|
-
# Default kelvin values
|
|
44
|
-
KELVIN_WARM = 2700
|
|
45
|
-
KELVIN_NEUTRAL = 3500
|
|
46
|
-
|
|
47
|
-
def get_default_color(self, product_info: ProductInfo) -> LightHsbk:
|
|
48
|
-
"""Get default color for a product.
|
|
49
|
-
|
|
50
|
-
Args:
|
|
51
|
-
product_info: Product information from registry
|
|
52
|
-
|
|
53
|
-
Returns:
|
|
54
|
-
Default LightHsbk color for the device
|
|
55
|
-
"""
|
|
56
|
-
if not product_info.has_color and self._is_brightness_only(product_info):
|
|
57
|
-
return self._brightness_only_color()
|
|
58
|
-
elif not product_info.has_color and self._is_temperature_adjustable(
|
|
59
|
-
product_info
|
|
60
|
-
):
|
|
61
|
-
return self._temperature_adjustable_color()
|
|
62
|
-
else:
|
|
63
|
-
return self._color_device_color(product_info)
|
|
64
|
-
|
|
65
|
-
def _is_brightness_only(self, product_info: ProductInfo) -> bool:
|
|
66
|
-
"""Check if device has fixed color temperature.
|
|
67
|
-
|
|
68
|
-
Args:
|
|
69
|
-
product_info: Product information from registry
|
|
70
|
-
|
|
71
|
-
Returns:
|
|
72
|
-
True if temperature range has identical min/max
|
|
73
|
-
"""
|
|
74
|
-
return (
|
|
75
|
-
product_info.temperature_range is not None
|
|
76
|
-
and product_info.temperature_range.min == product_info.temperature_range.max
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
def _is_temperature_adjustable(self, product_info: ProductInfo) -> bool:
|
|
80
|
-
"""Check if device has adjustable color temperature.
|
|
81
|
-
|
|
82
|
-
Args:
|
|
83
|
-
product_info: Product information from registry
|
|
84
|
-
|
|
85
|
-
Returns:
|
|
86
|
-
True if temperature range has different min/max
|
|
87
|
-
"""
|
|
88
|
-
return (
|
|
89
|
-
product_info.temperature_range is not None
|
|
90
|
-
and product_info.temperature_range.min != product_info.temperature_range.max
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
def _brightness_only_color(self) -> LightHsbk:
|
|
94
|
-
"""Get default color for brightness-only devices.
|
|
95
|
-
|
|
96
|
-
Returns:
|
|
97
|
-
White at 2700K with 50% brightness
|
|
98
|
-
"""
|
|
99
|
-
return LightHsbk(
|
|
100
|
-
hue=0,
|
|
101
|
-
saturation=0,
|
|
102
|
-
brightness=self.DEFAULT_BRIGHTNESS,
|
|
103
|
-
kelvin=self.KELVIN_WARM,
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
def _temperature_adjustable_color(self) -> LightHsbk:
|
|
107
|
-
"""Get default color for temperature-adjustable devices.
|
|
108
|
-
|
|
109
|
-
Returns:
|
|
110
|
-
White at 3500K with 50% brightness
|
|
111
|
-
"""
|
|
112
|
-
return LightHsbk(
|
|
113
|
-
hue=0,
|
|
114
|
-
saturation=0,
|
|
115
|
-
brightness=self.DEFAULT_BRIGHTNESS,
|
|
116
|
-
kelvin=self.KELVIN_NEUTRAL,
|
|
117
|
-
)
|
|
118
|
-
|
|
119
|
-
def _color_device_color(self, product_info: ProductInfo) -> LightHsbk:
|
|
120
|
-
"""Get default color for color-capable devices.
|
|
121
|
-
|
|
122
|
-
Different hues based on device capabilities for easy visual identification.
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
product_info: Product information from registry
|
|
126
|
-
|
|
127
|
-
Returns:
|
|
128
|
-
Colored LightHsbk with full saturation
|
|
129
|
-
"""
|
|
130
|
-
hue = self._get_hue_for_capability(product_info)
|
|
131
|
-
return LightHsbk(
|
|
132
|
-
hue=hue,
|
|
133
|
-
saturation=65535,
|
|
134
|
-
brightness=self.DEFAULT_BRIGHTNESS,
|
|
135
|
-
kelvin=self.KELVIN_NEUTRAL,
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
def _get_hue_for_capability(self, product_info: ProductInfo) -> int:
|
|
139
|
-
"""Determine hue based on product capabilities.
|
|
140
|
-
|
|
141
|
-
Precedence: matrix > multizone > hev > infrared > default
|
|
142
|
-
|
|
143
|
-
Args:
|
|
144
|
-
product_info: Product information from registry
|
|
145
|
-
|
|
146
|
-
Returns:
|
|
147
|
-
Hue value (0-65535)
|
|
148
|
-
"""
|
|
149
|
-
if product_info.has_matrix:
|
|
150
|
-
return self.HUE_CYAN
|
|
151
|
-
elif product_info.has_multizone:
|
|
152
|
-
return self.HUE_RED
|
|
153
|
-
elif product_info.has_hev:
|
|
154
|
-
return self.HUE_GREEN
|
|
155
|
-
elif product_info.has_infrared:
|
|
156
|
-
return self.HUE_RED
|
|
157
|
-
else:
|
|
158
|
-
return self.HUE_ORANGE
|