lifx-emulator 2.2.1__py3-none-any.whl → 2.3.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/__main__.py +2 -2
- lifx_emulator/api/models.py +2 -2
- lifx_emulator/api/templates/dashboard.html +8 -8
- lifx_emulator/devices/device.py +9 -3
- lifx_emulator/devices/state_serializer.py +27 -0
- lifx_emulator/devices/states.py +31 -0
- lifx_emulator/factories/builder.py +2 -2
- lifx_emulator/factories/factory.py +4 -4
- lifx_emulator/handlers/light_handlers.py +155 -20
- lifx_emulator/handlers/tile_handlers.py +147 -22
- lifx_emulator/products/generator.py +2 -2
- lifx_emulator/products/specs.py +2 -2
- lifx_emulator/products/specs.yml +8 -8
- {lifx_emulator-2.2.1.dist-info → lifx_emulator-2.3.1.dist-info}/METADATA +1 -1
- {lifx_emulator-2.2.1.dist-info → lifx_emulator-2.3.1.dist-info}/RECORD +18 -18
- {lifx_emulator-2.2.1.dist-info → lifx_emulator-2.3.1.dist-info}/WHEEL +0 -0
- {lifx_emulator-2.2.1.dist-info → lifx_emulator-2.3.1.dist-info}/entry_points.txt +0 -0
- {lifx_emulator-2.2.1.dist-info → lifx_emulator-2.3.1.dist-info}/licenses/LICENSE +0 -0
lifx_emulator/__main__.py
CHANGED
|
@@ -286,9 +286,9 @@ async def run(
|
|
|
286
286
|
tile: Number of tile/matrix chain devices.
|
|
287
287
|
tile_count: Number of tiles per device. Uses product defaults if not
|
|
288
288
|
specified (5 for Tile, 1 for Candle/Ceiling).
|
|
289
|
-
tile_width: Width of each tile in
|
|
289
|
+
tile_width: Width of each tile in zones. Uses product defaults if not
|
|
290
290
|
specified (8 for most devices).
|
|
291
|
-
tile_height: Height of each tile in
|
|
291
|
+
tile_height: Height of each tile in zones. Uses product defaults if
|
|
292
292
|
not specified (8 for most devices).
|
|
293
293
|
serial_prefix: Serial number prefix as 6 hex characters.
|
|
294
294
|
serial_start: Starting serial suffix for auto-incrementing device serials.
|
lifx_emulator/api/models.py
CHANGED
|
@@ -25,10 +25,10 @@ class DeviceCreateRequest(BaseModel):
|
|
|
25
25
|
None, description="Number of tiles for matrix devices", ge=0, le=100
|
|
26
26
|
)
|
|
27
27
|
tile_width: int | None = Field(
|
|
28
|
-
None, description="Width of each tile in
|
|
28
|
+
None, description="Width of each tile in zones", ge=1, le=256
|
|
29
29
|
)
|
|
30
30
|
tile_height: int | None = Field(
|
|
31
|
-
None, description="Height of each tile in
|
|
31
|
+
None, description="Height of each tile in zones", ge=1, le=256
|
|
32
32
|
)
|
|
33
33
|
firmware_major: int | None = Field(
|
|
34
34
|
None, description="Firmware major version", ge=0, le=255
|
|
@@ -163,7 +163,7 @@
|
|
|
163
163
|
gap: 2px;
|
|
164
164
|
margin-top: 4px;
|
|
165
165
|
}
|
|
166
|
-
.tile-
|
|
166
|
+
.tile-zone {
|
|
167
167
|
width: 8px;
|
|
168
168
|
height: 8px;
|
|
169
169
|
border-radius: 1px;
|
|
@@ -644,7 +644,7 @@
|
|
|
644
644
|
`;
|
|
645
645
|
} else if (dev.has_matrix && dev.tile_devices &&
|
|
646
646
|
dev.tile_devices.length > 0) {
|
|
647
|
-
// Render actual tile
|
|
647
|
+
// Render actual tile zones
|
|
648
648
|
const tilesHtml = dev.tile_devices.map((tile, tileIndex) => {
|
|
649
649
|
if (!tile.colors || tile.colors.length === 0) {
|
|
650
650
|
return '<div style="color: #666;">No color data</div>';
|
|
@@ -652,14 +652,14 @@
|
|
|
652
652
|
|
|
653
653
|
const width = tile.width || 8;
|
|
654
654
|
const height = tile.height || 8;
|
|
655
|
-
const
|
|
655
|
+
const totalzones = width * height;
|
|
656
656
|
|
|
657
|
-
// Create grid of
|
|
658
|
-
const slicedColors = tile.colors.slice(0,
|
|
659
|
-
const
|
|
657
|
+
// Create grid of zones
|
|
658
|
+
const slicedColors = tile.colors.slice(0, totalzones);
|
|
659
|
+
const zonesHtml = slicedColors.map(color => {
|
|
660
660
|
const rgb = hsbkToRgb(color);
|
|
661
661
|
const bgStyle = `background: ${rgb};`;
|
|
662
|
-
return `<div class="tile-
|
|
662
|
+
return `<div class="tile-zone" style="${bgStyle}"></div>`;
|
|
663
663
|
}).join('');
|
|
664
664
|
|
|
665
665
|
const labelStyle = (
|
|
@@ -675,7 +675,7 @@
|
|
|
675
675
|
T${tileIndex + 1}
|
|
676
676
|
</div>
|
|
677
677
|
<div class="tile-grid" style="${gridStyle}">
|
|
678
|
-
${
|
|
678
|
+
${zonesHtml}
|
|
679
679
|
</div>
|
|
680
680
|
</div>
|
|
681
681
|
`;
|
lifx_emulator/devices/device.py
CHANGED
|
@@ -9,7 +9,7 @@ import time
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
|
|
11
11
|
from lifx_emulator.constants import LIFX_HEADER_SIZE
|
|
12
|
-
from lifx_emulator.devices.states import DeviceState
|
|
12
|
+
from lifx_emulator.devices.states import DeviceState, TileFramebuffers
|
|
13
13
|
from lifx_emulator.handlers import HandlerRegistry, create_default_registry
|
|
14
14
|
from lifx_emulator.protocol.header import LifxHeader
|
|
15
15
|
from lifx_emulator.protocol.packets import (
|
|
@@ -90,10 +90,10 @@ class EmulatedLifxDevice:
|
|
|
90
90
|
if self.state.has_matrix and self.state.tile_count > 0:
|
|
91
91
|
if not self.state.tile_devices:
|
|
92
92
|
for i in range(self.state.tile_count):
|
|
93
|
-
|
|
93
|
+
zones = self.state.tile_width * self.state.tile_height
|
|
94
94
|
tile_colors = [
|
|
95
95
|
LightHsbk(hue=0, saturation=0, brightness=32768, kelvin=3500)
|
|
96
|
-
for _ in range(
|
|
96
|
+
for _ in range(zones)
|
|
97
97
|
]
|
|
98
98
|
|
|
99
99
|
self.state.tile_devices.append(
|
|
@@ -114,6 +114,12 @@ class EmulatedLifxDevice:
|
|
|
114
114
|
}
|
|
115
115
|
)
|
|
116
116
|
|
|
117
|
+
# Initialize framebuffer storage for each tile (framebuffers 1-7)
|
|
118
|
+
# Framebuffer 0 is stored in tile_devices[i]["colors"]
|
|
119
|
+
if not self.state.tile_framebuffers:
|
|
120
|
+
for i in range(self.state.tile_count):
|
|
121
|
+
self.state.tile_framebuffers.append(TileFramebuffers(tile_index=i))
|
|
122
|
+
|
|
117
123
|
# Save initial state if persistence is enabled
|
|
118
124
|
# This ensures newly created devices are immediately persisted
|
|
119
125
|
if self.storage:
|
|
@@ -97,6 +97,17 @@ def serialize_device_state(device_state: Any) -> dict[str, Any]:
|
|
|
97
97
|
}
|
|
98
98
|
for t in device_state.tile_devices
|
|
99
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
|
+
]
|
|
100
111
|
|
|
101
112
|
return state_dict
|
|
102
113
|
|
|
@@ -127,4 +138,20 @@ def deserialize_device_state(state_dict: dict[str, Any]) -> dict[str, Any]:
|
|
|
127
138
|
for tile_dict in state_dict["tile_devices"]:
|
|
128
139
|
tile_dict["colors"] = [deserialize_hsbk(c) for c in tile_dict["colors"]]
|
|
129
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
|
+
|
|
130
157
|
return state_dict
|
lifx_emulator/devices/states.py
CHANGED
|
@@ -82,6 +82,32 @@ class MultiZoneState:
|
|
|
82
82
|
effect_speed: int = 5 # Duration of one cycle in seconds
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
@dataclass
|
|
86
|
+
class TileFramebuffers:
|
|
87
|
+
"""Internal storage for non-visible tile framebuffers (1-7).
|
|
88
|
+
|
|
89
|
+
Framebuffer 0 is stored in tile_devices[i]["colors"] (the visible buffer).
|
|
90
|
+
Framebuffers 1-7 are stored here for Set64/CopyFrameBuffer operations.
|
|
91
|
+
Each framebuffer is a list of LightHsbk colors with length = width * height.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
tile_index: int # Which tile this belongs to
|
|
95
|
+
framebuffers: dict[int, list[LightHsbk]] = field(default_factory=dict)
|
|
96
|
+
|
|
97
|
+
def get_framebuffer(
|
|
98
|
+
self, fb_index: int, width: int, height: int
|
|
99
|
+
) -> list[LightHsbk]:
|
|
100
|
+
"""Get framebuffer by index, creating it if needed."""
|
|
101
|
+
if fb_index not in self.framebuffers:
|
|
102
|
+
# Initialize with default black color
|
|
103
|
+
zones = width * height
|
|
104
|
+
self.framebuffers[fb_index] = [
|
|
105
|
+
LightHsbk(hue=0, saturation=0, brightness=0, kelvin=3500)
|
|
106
|
+
for _ in range(zones)
|
|
107
|
+
]
|
|
108
|
+
return self.framebuffers[fb_index]
|
|
109
|
+
|
|
110
|
+
|
|
85
111
|
@dataclass
|
|
86
112
|
class MatrixState:
|
|
87
113
|
"""Matrix (tile/candle) capability state."""
|
|
@@ -101,6 +127,9 @@ class MatrixState:
|
|
|
101
127
|
effect_cloud_sat_max: int = (
|
|
102
128
|
0 # Max cloud saturation 0-200 (only when effect_type=5)
|
|
103
129
|
)
|
|
130
|
+
# Internal storage for non-visible framebuffers (1-7) per tile
|
|
131
|
+
# Framebuffer 0 remains in tile_devices[i]["colors"]
|
|
132
|
+
tile_framebuffers: list[TileFramebuffers] = field(default_factory=list)
|
|
104
133
|
|
|
105
134
|
|
|
106
135
|
@dataclass
|
|
@@ -215,6 +244,7 @@ class DeviceState:
|
|
|
215
244
|
"tile_effect_sky_type": ("matrix", "effect_sky_type"),
|
|
216
245
|
"tile_effect_cloud_sat_min": ("matrix", "effect_cloud_sat_min"),
|
|
217
246
|
"tile_effect_cloud_sat_max": ("matrix", "effect_cloud_sat_max"),
|
|
247
|
+
"tile_framebuffers": "matrix",
|
|
218
248
|
}
|
|
219
249
|
|
|
220
250
|
# Default values for optional state attributes when state object is None
|
|
@@ -240,6 +270,7 @@ class DeviceState:
|
|
|
240
270
|
"tile_effect_sky_type": 0,
|
|
241
271
|
"tile_effect_cloud_sat_min": 0,
|
|
242
272
|
"tile_effect_cloud_sat_max": 0,
|
|
273
|
+
"tile_framebuffers": [],
|
|
243
274
|
}
|
|
244
275
|
|
|
245
276
|
def get_target_bytes(self) -> bytes:
|
|
@@ -136,8 +136,8 @@ class DeviceBuilder:
|
|
|
136
136
|
"""Set tile dimensions for matrix devices.
|
|
137
137
|
|
|
138
138
|
Args:
|
|
139
|
-
width: Tile width in
|
|
140
|
-
height: Tile height in
|
|
139
|
+
width: Tile width in zones
|
|
140
|
+
height: Tile height in zones
|
|
141
141
|
|
|
142
142
|
Returns:
|
|
143
143
|
Self for method chaining
|
|
@@ -104,8 +104,8 @@ def create_tile_device(
|
|
|
104
104
|
Args:
|
|
105
105
|
serial: Optional serial
|
|
106
106
|
tile_count: Optional tile count (uses product default)
|
|
107
|
-
tile_width: Optional tile width in
|
|
108
|
-
tile_height: Optional tile height in
|
|
107
|
+
tile_width: Optional tile width in zones (uses product default)
|
|
108
|
+
tile_height: Optional tile height in zones (uses product default)
|
|
109
109
|
firmware_version: Optional firmware version tuple (major, minor)
|
|
110
110
|
storage: Optional storage for persistence
|
|
111
111
|
scenario_manager: Optional scenario manager
|
|
@@ -164,8 +164,8 @@ def create_device(
|
|
|
164
164
|
zone_count: Number of zones for multizone devices (auto-determined)
|
|
165
165
|
extended_multizone: Enable extended multizone requests
|
|
166
166
|
tile_count: Number of tiles for matrix devices (default: 5)
|
|
167
|
-
tile_width: Width of each tile in
|
|
168
|
-
tile_height: Height of each tile in
|
|
167
|
+
tile_width: Width of each tile in zones (default: 8)
|
|
168
|
+
tile_height: Height of each tile in zones (default: 8)
|
|
169
169
|
firmware_version: Optional firmware version tuple (major, minor).
|
|
170
170
|
If not specified, uses 3.70 for extended_multizone
|
|
171
171
|
or 2.60 otherwise
|
|
@@ -7,7 +7,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
7
7
|
|
|
8
8
|
from lifx_emulator.handlers.base import PacketHandler
|
|
9
9
|
from lifx_emulator.protocol.packets import Light
|
|
10
|
-
from lifx_emulator.protocol.protocol_types import LightLastHevCycleResult
|
|
10
|
+
from lifx_emulator.protocol.protocol_types import LightHsbk, LightLastHevCycleResult
|
|
11
11
|
|
|
12
12
|
if TYPE_CHECKING:
|
|
13
13
|
from lifx_emulator.devices import DeviceState
|
|
@@ -15,6 +15,72 @@ if TYPE_CHECKING:
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
17
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
|
+
|
|
18
84
|
class GetColorHandler(PacketHandler):
|
|
19
85
|
"""Handle LightGet (101) -> LightState (107)."""
|
|
20
86
|
|
|
@@ -23,9 +89,24 @@ class GetColorHandler(PacketHandler):
|
|
|
23
89
|
def handle(
|
|
24
90
|
self, device_state: DeviceState, packet: Any | None, res_required: bool
|
|
25
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
|
+
|
|
26
107
|
return [
|
|
27
108
|
Light.StateColor(
|
|
28
|
-
color=
|
|
109
|
+
color=color_to_return,
|
|
29
110
|
power=device_state.power_level,
|
|
30
111
|
label=device_state.label,
|
|
31
112
|
)
|
|
@@ -46,10 +127,36 @@ class SetColorHandler(PacketHandler):
|
|
|
46
127
|
if packet:
|
|
47
128
|
device_state.color = packet.color
|
|
48
129
|
c = packet.color
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
+
)
|
|
53
160
|
|
|
54
161
|
if res_required:
|
|
55
162
|
return [
|
|
@@ -120,6 +227,17 @@ class SetWaveformHandler(PacketHandler):
|
|
|
120
227
|
if not packet.transient:
|
|
121
228
|
device_state.color = packet.color
|
|
122
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
|
+
|
|
123
241
|
logger.info(
|
|
124
242
|
f"Waveform set: type={packet.waveform}, "
|
|
125
243
|
f"transient={packet.transient}, period={packet.period}ms, "
|
|
@@ -127,13 +245,9 @@ class SetWaveformHandler(PacketHandler):
|
|
|
127
245
|
)
|
|
128
246
|
|
|
129
247
|
if res_required:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
power=device_state.power_level,
|
|
134
|
-
label=device_state.label,
|
|
135
|
-
)
|
|
136
|
-
]
|
|
248
|
+
# Use GetColorHandler to get proper averaged color if needed
|
|
249
|
+
handler = GetColorHandler()
|
|
250
|
+
return handler.handle(device_state, None, res_required)
|
|
137
251
|
return []
|
|
138
252
|
|
|
139
253
|
|
|
@@ -168,6 +282,31 @@ class SetWaveformOptionalHandler(PacketHandler):
|
|
|
168
282
|
if packet.set_kelvin:
|
|
169
283
|
device_state.color.kelvin = packet.color.kelvin
|
|
170
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
|
+
|
|
171
310
|
# Store the waveform color (all components)
|
|
172
311
|
device_state.waveform_color = packet.color
|
|
173
312
|
|
|
@@ -180,13 +319,9 @@ class SetWaveformOptionalHandler(PacketHandler):
|
|
|
180
319
|
)
|
|
181
320
|
|
|
182
321
|
if res_required:
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
power=device_state.power_level,
|
|
187
|
-
label=device_state.label,
|
|
188
|
-
)
|
|
189
|
-
]
|
|
322
|
+
# Use GetColorHandler to get proper averaged color if needed
|
|
323
|
+
handler = GetColorHandler()
|
|
324
|
+
return handler.handle(device_state, None, res_required)
|
|
190
325
|
return []
|
|
191
326
|
|
|
192
327
|
|
|
@@ -12,6 +12,7 @@ from lifx_emulator.protocol.protocol_types import (
|
|
|
12
12
|
DeviceStateVersion,
|
|
13
13
|
LightHsbk,
|
|
14
14
|
TileAccelMeas,
|
|
15
|
+
TileBufferRect,
|
|
15
16
|
TileEffectParameter,
|
|
16
17
|
TileEffectSettings,
|
|
17
18
|
TileEffectType,
|
|
@@ -137,13 +138,17 @@ class Get64Handler(PacketHandler):
|
|
|
137
138
|
tile_width = tile["width"]
|
|
138
139
|
tile_height = tile["height"]
|
|
139
140
|
|
|
140
|
-
#
|
|
141
|
+
# Get64 always returns framebuffer 0 (the visible buffer)
|
|
142
|
+
# regardless of which fb_index is in the request
|
|
143
|
+
tile_colors = tile["colors"]
|
|
144
|
+
|
|
145
|
+
# Calculate how many rows fit in 64 zones
|
|
141
146
|
rows_to_return = 64 // rect.width if rect.width > 0 else 1
|
|
142
147
|
rows_to_return = min(rows_to_return, tile_height - rect.y)
|
|
143
148
|
|
|
144
149
|
# Extract colors from the requested rectangle
|
|
145
150
|
colors = []
|
|
146
|
-
|
|
151
|
+
zones_extracted = 0
|
|
147
152
|
|
|
148
153
|
for row in range(rows_to_return):
|
|
149
154
|
y = rect.y + row
|
|
@@ -152,28 +157,35 @@ class Get64Handler(PacketHandler):
|
|
|
152
157
|
|
|
153
158
|
for col in range(rect.width):
|
|
154
159
|
x = rect.x + col
|
|
155
|
-
if x >= tile_width or
|
|
160
|
+
if x >= tile_width or zones_extracted >= 64:
|
|
156
161
|
colors.append(
|
|
157
162
|
LightHsbk(hue=0, saturation=0, brightness=0, kelvin=3500)
|
|
158
163
|
)
|
|
159
|
-
|
|
164
|
+
zones_extracted += 1
|
|
160
165
|
continue
|
|
161
166
|
|
|
162
|
-
# Calculate
|
|
163
|
-
|
|
164
|
-
if
|
|
165
|
-
colors.append(
|
|
167
|
+
# Calculate zone index in flat color array
|
|
168
|
+
zone_idx = y * tile_width + x
|
|
169
|
+
if zone_idx < len(tile_colors):
|
|
170
|
+
colors.append(tile_colors[zone_idx])
|
|
166
171
|
else:
|
|
167
172
|
colors.append(
|
|
168
173
|
LightHsbk(hue=0, saturation=0, brightness=0, kelvin=3500)
|
|
169
174
|
)
|
|
170
|
-
|
|
175
|
+
zones_extracted += 1
|
|
171
176
|
|
|
172
177
|
# Pad to exactly 64 colors
|
|
173
178
|
while len(colors) < 64:
|
|
174
179
|
colors.append(LightHsbk(hue=0, saturation=0, brightness=0, kelvin=3500))
|
|
175
180
|
|
|
176
|
-
|
|
181
|
+
# Return with fb_index forced to 0 (visible buffer)
|
|
182
|
+
return_rect = TileBufferRect(
|
|
183
|
+
fb_index=0, # Always return FB0
|
|
184
|
+
x=rect.x,
|
|
185
|
+
y=rect.y,
|
|
186
|
+
width=rect.width,
|
|
187
|
+
)
|
|
188
|
+
return [Tile.State64(tile_index=tile_index, rect=return_rect, colors=colors)]
|
|
177
189
|
|
|
178
190
|
|
|
179
191
|
class Set64Handler(PacketHandler):
|
|
@@ -188,16 +200,58 @@ class Set64Handler(PacketHandler):
|
|
|
188
200
|
return []
|
|
189
201
|
|
|
190
202
|
tile_index = packet.tile_index
|
|
203
|
+
fb_index = packet.rect.fb_index
|
|
191
204
|
|
|
192
|
-
if tile_index
|
|
193
|
-
|
|
194
|
-
for i, color in enumerate(packet.colors[:64]):
|
|
195
|
-
if i < len(device_state.tile_devices[tile_index]["colors"]):
|
|
196
|
-
device_state.tile_devices[tile_index]["colors"][i] = color
|
|
205
|
+
if tile_index >= len(device_state.tile_devices):
|
|
206
|
+
return []
|
|
197
207
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
tile = device_state.tile_devices[tile_index]
|
|
209
|
+
tile_width = tile["width"]
|
|
210
|
+
tile_height = tile["height"]
|
|
211
|
+
rect = packet.rect
|
|
212
|
+
|
|
213
|
+
# Determine which framebuffer to update
|
|
214
|
+
if fb_index == 0:
|
|
215
|
+
# Update visible framebuffer (stored in tile_devices)
|
|
216
|
+
target_colors = tile["colors"]
|
|
217
|
+
else:
|
|
218
|
+
# Update non-visible framebuffer (stored in tile_framebuffers)
|
|
219
|
+
if tile_index < len(device_state.tile_framebuffers):
|
|
220
|
+
fb_storage = device_state.tile_framebuffers[tile_index]
|
|
221
|
+
target_colors = fb_storage.get_framebuffer(
|
|
222
|
+
fb_index, tile_width, tile_height
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
logger.warning(f"Tile {tile_index} framebuffer storage not initialized")
|
|
226
|
+
return []
|
|
227
|
+
|
|
228
|
+
# Update colors in the specified rectangle
|
|
229
|
+
# Calculate how many rows fit in 64 zones
|
|
230
|
+
rows_to_write = 64 // rect.width if rect.width > 0 else 1
|
|
231
|
+
rows_to_write = min(rows_to_write, tile_height - rect.y)
|
|
232
|
+
|
|
233
|
+
zones_written = 0
|
|
234
|
+
for row in range(rows_to_write):
|
|
235
|
+
y = rect.y + row
|
|
236
|
+
if y >= tile_height:
|
|
237
|
+
break
|
|
238
|
+
|
|
239
|
+
for col in range(rect.width):
|
|
240
|
+
x = rect.x + col
|
|
241
|
+
if x >= tile_width or zones_written >= 64:
|
|
242
|
+
zones_written += 1
|
|
243
|
+
continue
|
|
244
|
+
|
|
245
|
+
# Calculate zone index in flat color array
|
|
246
|
+
zone_idx = y * tile_width + x
|
|
247
|
+
if zone_idx < len(target_colors) and zones_written < len(packet.colors):
|
|
248
|
+
target_colors[zone_idx] = packet.colors[zones_written]
|
|
249
|
+
zones_written += 1
|
|
250
|
+
|
|
251
|
+
logger.info(
|
|
252
|
+
f"Tile {tile_index} FB{fb_index} set {zones_written} colors at "
|
|
253
|
+
f"({rect.x},{rect.y}), duration={packet.duration}ms"
|
|
254
|
+
)
|
|
201
255
|
|
|
202
256
|
# Tiles never return a response to Set64 regardless of res_required
|
|
203
257
|
# https://lan.developer.lifx.com/docs/changing-a-device#set64---packet-715
|
|
@@ -212,12 +266,83 @@ class CopyFrameBufferHandler(PacketHandler):
|
|
|
212
266
|
def handle(
|
|
213
267
|
self, device_state: DeviceState, packet: Any | None, res_required: bool
|
|
214
268
|
) -> list[Any]:
|
|
215
|
-
if not device_state.has_matrix:
|
|
269
|
+
if not device_state.has_matrix or not packet:
|
|
216
270
|
return []
|
|
217
271
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
272
|
+
tile_index = packet.tile_index
|
|
273
|
+
if tile_index >= len(device_state.tile_devices):
|
|
274
|
+
return []
|
|
275
|
+
|
|
276
|
+
tile = device_state.tile_devices[tile_index]
|
|
277
|
+
tile_width = tile["width"]
|
|
278
|
+
tile_height = tile["height"]
|
|
279
|
+
|
|
280
|
+
src_fb_index = packet.src_fb_index
|
|
281
|
+
dst_fb_index = packet.dst_fb_index
|
|
282
|
+
|
|
283
|
+
# Get source framebuffer
|
|
284
|
+
if src_fb_index == 0:
|
|
285
|
+
src_colors = tile["colors"]
|
|
286
|
+
else:
|
|
287
|
+
if tile_index < len(device_state.tile_framebuffers):
|
|
288
|
+
fb_storage = device_state.tile_framebuffers[tile_index]
|
|
289
|
+
src_colors = fb_storage.get_framebuffer(
|
|
290
|
+
src_fb_index, tile_width, tile_height
|
|
291
|
+
)
|
|
292
|
+
else:
|
|
293
|
+
logger.warning(f"Tile {tile_index} framebuffer storage not initialized")
|
|
294
|
+
return []
|
|
295
|
+
|
|
296
|
+
# Get destination framebuffer
|
|
297
|
+
if dst_fb_index == 0:
|
|
298
|
+
dst_colors = tile["colors"]
|
|
299
|
+
else:
|
|
300
|
+
if tile_index < len(device_state.tile_framebuffers):
|
|
301
|
+
fb_storage = device_state.tile_framebuffers[tile_index]
|
|
302
|
+
dst_colors = fb_storage.get_framebuffer(
|
|
303
|
+
dst_fb_index, tile_width, tile_height
|
|
304
|
+
)
|
|
305
|
+
else:
|
|
306
|
+
logger.warning(f"Tile {tile_index} framebuffer storage not initialized")
|
|
307
|
+
return []
|
|
308
|
+
|
|
309
|
+
# Copy the specified rectangle from source to destination
|
|
310
|
+
src_x = packet.src_x
|
|
311
|
+
src_y = packet.src_y
|
|
312
|
+
dst_x = packet.dst_x
|
|
313
|
+
dst_y = packet.dst_y
|
|
314
|
+
width = packet.width
|
|
315
|
+
height = packet.height
|
|
316
|
+
|
|
317
|
+
zones_copied = 0
|
|
318
|
+
for row in range(height):
|
|
319
|
+
src_row = src_y + row
|
|
320
|
+
dst_row = dst_y + row
|
|
321
|
+
|
|
322
|
+
if src_row >= tile_height or dst_row >= tile_height:
|
|
323
|
+
break
|
|
324
|
+
|
|
325
|
+
for col in range(width):
|
|
326
|
+
src_col = src_x + col
|
|
327
|
+
dst_col = dst_x + col
|
|
328
|
+
|
|
329
|
+
if src_col >= tile_width or dst_col >= tile_width:
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
src_idx = src_row * tile_width + src_col
|
|
333
|
+
dst_idx = dst_row * tile_width + dst_col
|
|
334
|
+
|
|
335
|
+
if src_idx < len(src_colors) and dst_idx < len(dst_colors):
|
|
336
|
+
dst_colors[dst_idx] = src_colors[src_idx]
|
|
337
|
+
zones_copied += 1
|
|
338
|
+
|
|
339
|
+
logger.info(
|
|
340
|
+
f"Tile {tile_index} copied {zones_copied} zones from "
|
|
341
|
+
f"FB{src_fb_index}({src_x},{src_y}) to "
|
|
342
|
+
f"FB{dst_fb_index}({dst_x},{dst_y}), "
|
|
343
|
+
f"size={width}x{height}, duration={packet.duration}ms"
|
|
344
|
+
)
|
|
345
|
+
|
|
221
346
|
return []
|
|
222
347
|
|
|
223
348
|
|
|
@@ -806,8 +806,8 @@ def _generate_yaml_header() -> list[str]:
|
|
|
806
806
|
"# default_tile_count: <number> # Default number of tiles in chain",
|
|
807
807
|
"# min_tile_count: <number> # Minimum tiles supported",
|
|
808
808
|
"# max_tile_count: <number> # Maximum tiles supported",
|
|
809
|
-
"# tile_width: <number> # Width of each tile in
|
|
810
|
-
"# tile_height: <number> # Height of each tile in
|
|
809
|
+
"# tile_width: <number> # Width of each tile in zones",
|
|
810
|
+
"# tile_height: <number> # Height of each tile in zones",
|
|
811
811
|
"#",
|
|
812
812
|
"# # Host firmware version (optional, overrides auto firmware selection)",
|
|
813
813
|
"# default_firmware_major: <number> # Firmware major version (e.g., 3)",
|
lifx_emulator/products/specs.py
CHANGED
|
@@ -25,8 +25,8 @@ class ProductSpecs:
|
|
|
25
25
|
default_tile_count: Default number of tiles in chain
|
|
26
26
|
min_tile_count: Minimum tiles supported
|
|
27
27
|
max_tile_count: Maximum tiles supported
|
|
28
|
-
tile_width: Width of each tile in
|
|
29
|
-
tile_height: Height of each tile in
|
|
28
|
+
tile_width: Width of each tile in zones
|
|
29
|
+
tile_height: Height of each tile in zones
|
|
30
30
|
default_firmware_major: Default firmware major version
|
|
31
31
|
default_firmware_minor: Default firmware minor version
|
|
32
32
|
notes: Human-readable notes about this product
|
lifx_emulator/products/specs.yml
CHANGED
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
# default_tile_count: <number> # Default number of tiles in chain
|
|
22
22
|
# min_tile_count: <number> # Minimum tiles supported
|
|
23
23
|
# max_tile_count: <number> # Maximum tiles supported
|
|
24
|
-
# tile_width: <number> # Width of each tile in
|
|
25
|
-
# tile_height: <number> # Height of each tile in
|
|
24
|
+
# tile_width: <number> # Width of each tile in zones
|
|
25
|
+
# tile_height: <number> # Height of each tile in zones
|
|
26
26
|
#
|
|
27
27
|
# # Host firmware version (optional, overrides automatic firmware selection)
|
|
28
28
|
# default_firmware_major: <number> # Firmware major version (e.g., 3)
|
|
@@ -167,29 +167,29 @@ products:
|
|
|
167
167
|
# Matrix Products (Tiles, Candles, etc.)
|
|
168
168
|
# ========================================
|
|
169
169
|
|
|
170
|
-
55: # LIFX Tile, 8x8
|
|
170
|
+
55: # LIFX Tile, 8x8 zone matrix, chainable up to 5
|
|
171
171
|
default_tile_count: 5
|
|
172
172
|
min_tile_count: 1
|
|
173
173
|
max_tile_count: 5
|
|
174
174
|
tile_width: 8
|
|
175
175
|
tile_height: 8
|
|
176
|
-
notes: LIFX Tile, 8x8
|
|
176
|
+
notes: LIFX Tile, 8x8 zone matrix, chainable up to 5
|
|
177
177
|
|
|
178
|
-
57: # LIFX Candle, 5x6
|
|
178
|
+
57: # LIFX Candle, 5x6 zone matrix, single unit
|
|
179
179
|
default_tile_count: 1
|
|
180
180
|
min_tile_count: 1
|
|
181
181
|
max_tile_count: 1
|
|
182
182
|
tile_width: 5
|
|
183
183
|
tile_height: 6
|
|
184
|
-
notes: LIFX Candle, 5x6
|
|
184
|
+
notes: LIFX Candle, 5x6 zone matrix, single unit
|
|
185
185
|
|
|
186
|
-
68: # LIFX Candle variant, 5x6
|
|
186
|
+
68: # LIFX Candle variant, 5x6 zone matrix
|
|
187
187
|
default_tile_count: 1
|
|
188
188
|
min_tile_count: 1
|
|
189
189
|
max_tile_count: 1
|
|
190
190
|
tile_width: 5
|
|
191
191
|
tile_height: 6
|
|
192
|
-
notes: LIFX Candle variant, 5x6
|
|
192
|
+
notes: LIFX Candle variant, 5x6 zone matrix
|
|
193
193
|
|
|
194
194
|
137: # LIFX Candle Color US 5x6 variant
|
|
195
195
|
default_tile_count: 1
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
lifx_emulator/__init__.py,sha256=vjhtpAQRSsUZtaUGCQKbmPALvwZ_BF8Mko8w6jzVqBw,819
|
|
2
|
-
lifx_emulator/__main__.py,sha256=
|
|
2
|
+
lifx_emulator/__main__.py,sha256=C1Khr8MdrDJnRUM5Y-CV1MPf7CqS2qM-vynGDJjdMYg,21678
|
|
3
3
|
lifx_emulator/constants.py,sha256=DFZkUsdewE-x_3MgO28tMGkjUCWPeYc3xLj_EXViGOw,1032
|
|
4
4
|
lifx_emulator/server.py,sha256=r2JYFcpZIqqhue-Nfq7FbN0KfC3XDf3XDb6b43DsiCk,16438
|
|
5
5
|
lifx_emulator/api/__init__.py,sha256=FoEPw_In5-H_BDQ-XIIONvgj-UqIDVtejIEVRv9qmV8,647
|
|
6
6
|
lifx_emulator/api/app.py,sha256=IxK8sC7MgdtkoLz8iXcEt02nPDaVgdKJgEiGnzTs-YE,4880
|
|
7
|
-
lifx_emulator/api/models.py,sha256=
|
|
7
|
+
lifx_emulator/api/models.py,sha256=qFNo0sOl31yuZLtWmLroSW6f6jck-RhP05tx972xsWA,3971
|
|
8
8
|
lifx_emulator/api/mappers/__init__.py,sha256=ZPCOQR9odcwn0C58AjFW6RvBXe5gOll_QS5lAabgorQ,152
|
|
9
9
|
lifx_emulator/api/mappers/device_mapper.py,sha256=EGOpdao9ZS-vT4T8IoV-AoN5WucTnqpQO92dYizo3vw,4151
|
|
10
10
|
lifx_emulator/api/routers/__init__.py,sha256=kbMefnuXrEsYeMA9J4YK_wVs87_XcH7hwkEifR-zgMc,369
|
|
@@ -13,33 +13,33 @@ lifx_emulator/api/routers/monitoring.py,sha256=qgVBNm6iMESf1W6EE22DvLalMnxkr0pRb
|
|
|
13
13
|
lifx_emulator/api/routers/scenarios.py,sha256=0axSQ9r6rByvXLvqRqOU2ma5nTvZgZ0IIzEXdtzoPnM,9743
|
|
14
14
|
lifx_emulator/api/services/__init__.py,sha256=ttjjZfAxbDQC_Ep0LkXjopNiVZOFPsFDSOHhBN98v5s,277
|
|
15
15
|
lifx_emulator/api/services/device_service.py,sha256=r3uFWApC8sVQMCuuzkyjm27K4LDpZnnHmQNgXWX40ok,6294
|
|
16
|
-
lifx_emulator/api/templates/dashboard.html,sha256=
|
|
16
|
+
lifx_emulator/api/templates/dashboard.html,sha256=h-PeOH_La5bVOUBcXmTY2leRlMdL8D8yJ-NCx3S16-A,33792
|
|
17
17
|
lifx_emulator/devices/__init__.py,sha256=QlBTPnFErJcSKLvGyeDwemh7xcpjYvB_L5siKsjr3s8,1089
|
|
18
|
-
lifx_emulator/devices/device.py,sha256=
|
|
18
|
+
lifx_emulator/devices/device.py,sha256=24rknbLw_EWF8dheED89wvovKjvC2CdfifLvG7g3SiQ,13648
|
|
19
19
|
lifx_emulator/devices/manager.py,sha256=XDrT82um5sgNpNihLj5RsNvHqdVI1bK9YY2eBzWIcf0,8162
|
|
20
20
|
lifx_emulator/devices/observers.py,sha256=-KnUgFcKdhlNo7CNVstP-u0wU2W0JAGg055ZPV15Sj0,3874
|
|
21
21
|
lifx_emulator/devices/persistence.py,sha256=9Mhj46-xrweOmyzjORCi2jKIwa8XJWpQ5CgaKcw6U98,10513
|
|
22
22
|
lifx_emulator/devices/state_restorer.py,sha256=eDsRSW-2RviP_0Qlk2DHqMaB-zhV0X1cNQECv2lD1qc,9809
|
|
23
|
-
lifx_emulator/devices/state_serializer.py,sha256=
|
|
24
|
-
lifx_emulator/devices/states.py,sha256=
|
|
23
|
+
lifx_emulator/devices/state_serializer.py,sha256=aws4LUmXBJS8oBrQziJtlV0XMvCTm5X4dGkGlO_QHcM,6281
|
|
24
|
+
lifx_emulator/devices/states.py,sha256=szWmarFjTBZO1UljEdjvS4W-nanYOzgE3P5df36T5bY,12092
|
|
25
25
|
lifx_emulator/factories/__init__.py,sha256=yN8i_Hu_cFEryWZmh0TiOQvWEYFVIApQSs4xeb0EfBk,1170
|
|
26
|
-
lifx_emulator/factories/builder.py,sha256=
|
|
26
|
+
lifx_emulator/factories/builder.py,sha256=6b0frEUMnM-RE2yjoVJzKUav3xn9bOElJPOETSG4NWk,12054
|
|
27
27
|
lifx_emulator/factories/default_config.py,sha256=FTcxKDfeTmO49GTSki8nxnEIZQzR0Lg0hL_PwHUrkVQ,4828
|
|
28
|
-
lifx_emulator/factories/factory.py,sha256=
|
|
28
|
+
lifx_emulator/factories/factory.py,sha256=Q2Yr21EC2bLOWwLyqqoUIsJKwWt7MTNODERhTRH6llk,7579
|
|
29
29
|
lifx_emulator/factories/firmware_config.py,sha256=tPN5Hq-uNb1xzW9Q0A9jD-G0-NaGfINcD0i1XZRUMoE,2711
|
|
30
30
|
lifx_emulator/factories/serial_generator.py,sha256=MbaXoommsj76ho8_ZoKuUDnffDf98YvwQiXZSWsUsEs,2507
|
|
31
31
|
lifx_emulator/handlers/__init__.py,sha256=3Hj1hRo3yL3E7GKwG9TaYh33ymk_N3bRiQ8nvqSQULA,1306
|
|
32
32
|
lifx_emulator/handlers/base.py,sha256=0avCLXY_rNlw16PpJ5JrRCwXNE4uMpBqF3PfSfNJ0b8,1654
|
|
33
33
|
lifx_emulator/handlers/device_handlers.py,sha256=1AmslA4Ut6L7b3SfduDdvnQizTpzUB3KKWBXmp4WYLQ,9462
|
|
34
|
-
lifx_emulator/handlers/light_handlers.py,sha256=
|
|
34
|
+
lifx_emulator/handlers/light_handlers.py,sha256=255aoiIjSIL63kbHQa6wqUpEwFzFFx7SG6P1nWM9jgU,17769
|
|
35
35
|
lifx_emulator/handlers/multizone_handlers.py,sha256=2dYsitq0KzEaxEAJmz7ixtir1tvFMOAnfkBQqslqbPM,7914
|
|
36
36
|
lifx_emulator/handlers/registry.py,sha256=s1ht4PmPhXhAcwu1hoY4yW39wy3SPJBMY-9Uxd0FWuE,3292
|
|
37
|
-
lifx_emulator/handlers/tile_handlers.py,sha256=
|
|
37
|
+
lifx_emulator/handlers/tile_handlers.py,sha256=L4fNKGTSSIxRuqKrfDrMSrNPvDJr3aIuaEqbhRCOt04,17176
|
|
38
38
|
lifx_emulator/products/__init__.py,sha256=qcNop_kRYFF3zSjNemzQEgu3jPrIxfyQyLv9GsnaLEI,627
|
|
39
|
-
lifx_emulator/products/generator.py,sha256=
|
|
39
|
+
lifx_emulator/products/generator.py,sha256=WsbAr2dXXMtLyOlFFkt-xM9kT5WpiUzj6_FKRf16Tng,33536
|
|
40
40
|
lifx_emulator/products/registry.py,sha256=qkm2xgGZo_ds3wAbYplLu4gb0cxhjZXjnCc1V8etpHw,46517
|
|
41
|
-
lifx_emulator/products/specs.py,sha256=
|
|
42
|
-
lifx_emulator/products/specs.yml,sha256
|
|
41
|
+
lifx_emulator/products/specs.py,sha256=epqz2DPyNOOOFHhmI_wlk7iEbgN0vCugHz-hWx9FlAI,8728
|
|
42
|
+
lifx_emulator/products/specs.yml,sha256=-91JNzGhwcO_zybOWY8dFBncN2TnnxtSkkHdi31KT94,9675
|
|
43
43
|
lifx_emulator/protocol/__init__.py,sha256=-wjC-wBcb7fxi5I-mJr2Ad8K2YRflJFdLLdobfD-W1Q,56
|
|
44
44
|
lifx_emulator/protocol/base.py,sha256=V6t0baSgIXjrsz2dBuUn_V9xwradSqMxBFJHAUtnfCs,15368
|
|
45
45
|
lifx_emulator/protocol/const.py,sha256=ilhv-KcQpHtKh2MDCaIbMLQAsxKO_uTaxyR63v1W8cc,226
|
|
@@ -55,8 +55,8 @@ lifx_emulator/scenarios/__init__.py,sha256=CGjudoWvyysvFj2xej11N2cr3mYROGtRb9zVH
|
|
|
55
55
|
lifx_emulator/scenarios/manager.py,sha256=1esxRdz74UynNk1wb86MGZ2ZFAuMzByuu74nRe3D-Og,11163
|
|
56
56
|
lifx_emulator/scenarios/models.py,sha256=BKS_fGvrbkGe-vK3arZ0w2f9adS1UZhiOoKpu7GENnc,4099
|
|
57
57
|
lifx_emulator/scenarios/persistence.py,sha256=3vjtPNFYfag38tUxuqxkGpWhQ7uBitc1rLroSAuw9N8,8881
|
|
58
|
-
lifx_emulator-2.
|
|
59
|
-
lifx_emulator-2.
|
|
60
|
-
lifx_emulator-2.
|
|
61
|
-
lifx_emulator-2.
|
|
62
|
-
lifx_emulator-2.
|
|
58
|
+
lifx_emulator-2.3.1.dist-info/METADATA,sha256=L3tETYNKBl5pR1wfSXTsWijV0olIPspASI1du2HMO94,4549
|
|
59
|
+
lifx_emulator-2.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
60
|
+
lifx_emulator-2.3.1.dist-info/entry_points.txt,sha256=R9C_K_tTgt6yXEmhzH4r2Yx2Tu1rLlnYzeG4RFUVzSc,62
|
|
61
|
+
lifx_emulator-2.3.1.dist-info/licenses/LICENSE,sha256=eBz48GRA3gSiWn3rYZAz2Ewp35snnhV9cSqkVBq7g3k,1832
|
|
62
|
+
lifx_emulator-2.3.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|