lifx-emulator 2.0.0__py3-none-any.whl → 2.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.
@@ -90,10 +90,17 @@ class MatrixState:
90
90
  tile_devices: list[dict[str, Any]]
91
91
  tile_width: int
92
92
  tile_height: int
93
- effect_type: int = 0 # 0=OFF, 2=MORPH, 3=FLAME
93
+ effect_type: int = 0 # 0=OFF, 2=MORPH, 3=FLAME, 5=SKY
94
94
  effect_speed: int = 5 # Duration of one cycle in seconds
95
95
  effect_palette_count: int = 0
96
96
  effect_palette: list[LightHsbk] = field(default_factory=list)
97
+ effect_sky_type: int = 0 # 0=SUNRISE, 1=SUNSET, 2=CLOUDS (only when effect_type=5)
98
+ effect_cloud_sat_min: int = (
99
+ 0 # Min cloud saturation 0-200 (only when effect_type=5)
100
+ )
101
+ effect_cloud_sat_max: int = (
102
+ 0 # Max cloud saturation 0-200 (only when effect_type=5)
103
+ )
97
104
 
98
105
 
99
106
  @dataclass
@@ -205,6 +212,9 @@ class DeviceState:
205
212
  "tile_effect_speed": ("matrix", "effect_speed"),
206
213
  "tile_effect_palette_count": ("matrix", "effect_palette_count"),
207
214
  "tile_effect_palette": ("matrix", "effect_palette"),
215
+ "tile_effect_sky_type": ("matrix", "effect_sky_type"),
216
+ "tile_effect_cloud_sat_min": ("matrix", "effect_cloud_sat_min"),
217
+ "tile_effect_cloud_sat_max": ("matrix", "effect_cloud_sat_max"),
208
218
  }
209
219
 
210
220
  # Default values for optional state attributes when state object is None
@@ -227,6 +237,9 @@ class DeviceState:
227
237
  "tile_effect_speed": 0,
228
238
  "tile_effect_palette_count": 0,
229
239
  "tile_effect_palette": [],
240
+ "tile_effect_sky_type": 0,
241
+ "tile_effect_cloud_sat_min": 0,
242
+ "tile_effect_cloud_sat_max": 0,
230
243
  }
231
244
 
232
245
  def get_target_bytes(self) -> bytes:
@@ -237,16 +237,24 @@ class GetEffectHandler(PacketHandler):
237
237
  while len(palette) < 16:
238
238
  palette.append(LightHsbk(hue=0, saturation=0, brightness=0, kelvin=3500))
239
239
 
240
- # Create effect settings
240
+ # Create effect settings with Sky parameters
241
+ from lifx_emulator.protocol.protocol_types import TileEffectSkyType
242
+
243
+ # Use defaults for SKY effect (type=5), otherwise use stored values
244
+ effect_type = device_state.tile_effect_type
245
+ if effect_type == 5: # SKY effect
246
+ sky_type = device_state.tile_effect_sky_type or 2 # Default to CLOUDS
247
+ cloud_sat_min = device_state.tile_effect_cloud_sat_min or 50
248
+ cloud_sat_max = device_state.tile_effect_cloud_sat_max or 180
249
+ else:
250
+ sky_type = device_state.tile_effect_sky_type
251
+ cloud_sat_min = device_state.tile_effect_cloud_sat_min
252
+ cloud_sat_max = device_state.tile_effect_cloud_sat_max
253
+
241
254
  parameter = TileEffectParameter(
242
- parameter0=0,
243
- parameter1=0,
244
- parameter2=0,
245
- parameter3=0,
246
- parameter4=0,
247
- parameter5=0,
248
- parameter6=0,
249
- parameter7=0,
255
+ sky_type=TileEffectSkyType(sky_type),
256
+ cloud_saturation_min=cloud_sat_min,
257
+ cloud_saturation_max=cloud_sat_max,
250
258
  )
251
259
  settings = TileEffectSettings(
252
260
  instanceid=0,
@@ -285,10 +293,22 @@ class SetEffectHandler(PacketHandler):
285
293
  )
286
294
  device_state.tile_effect_palette_count = packet.settings.palette_count
287
295
 
296
+ # Save Sky effect parameters
297
+ device_state.tile_effect_sky_type = int(packet.settings.parameter.sky_type)
298
+ device_state.tile_effect_cloud_sat_min = (
299
+ packet.settings.parameter.cloud_saturation_min
300
+ )
301
+ device_state.tile_effect_cloud_sat_max = (
302
+ packet.settings.parameter.cloud_saturation_max
303
+ )
304
+
288
305
  logger.info(
289
306
  f"Tile effect set: type={packet.settings.type}, "
290
307
  f"speed={packet.settings.speed}ms, "
291
- f"palette_count={packet.settings.palette_count}"
308
+ f"palette_count={packet.settings.palette_count}, "
309
+ f"sky_type={packet.settings.parameter.sky_type}, "
310
+ f"cloud_sat=[{packet.settings.parameter.cloud_saturation_min}, "
311
+ f"{packet.settings.parameter.cloud_saturation_max}]"
292
312
  )
293
313
 
294
314
  if res_required:
@@ -10,10 +10,13 @@ Performance optimizations:
10
10
 
11
11
  from __future__ import annotations
12
12
 
13
+ import logging
13
14
  import re
14
- from dataclasses import dataclass
15
+ from dataclasses import asdict, dataclass
15
16
  from typing import Any, ClassVar
16
17
 
18
+ _LOGGER = logging.getLogger(__name__)
19
+
17
20
  # Performance optimization: Pre-compiled regex patterns
18
21
  _CAMEL_TO_SNAKE_PATTERN = re.compile(r"(?<!^)(?=[A-Z])")
19
22
  _ARRAY_TYPE_PATTERN = re.compile(r"\[(\d+)\](.+)")
@@ -39,6 +42,11 @@ class Packet:
39
42
  _fields: ClassVar[list[dict[str, Any]]]
40
43
  _field_info: ClassVar[list[tuple[str, str, int]] | None] = None
41
44
 
45
+ @property
46
+ def as_dict(self) -> dict[str, Any]:
47
+ """Return packet as dictionary."""
48
+ return asdict(self)
49
+
42
50
  def pack(self) -> bytes:
43
51
  """Pack packet to bytes using field metadata.
44
52
 
@@ -76,9 +84,25 @@ class Packet:
76
84
  offset: Offset in bytes to start unpacking
77
85
 
78
86
  Returns:
79
- Packet instance
87
+ Packet instance with label fields decoded to strings
80
88
  """
81
89
  packet, _ = cls._unpack_internal(data, offset)
90
+
91
+ # Decode label fields from bytes to string in-place
92
+ # This ensures all State packets have human-readable labels
93
+ cls._decode_labels_inplace(packet)
94
+
95
+ # Log packet values after unpacking and decoding labels
96
+ packet_values = asdict(packet)
97
+ _LOGGER.debug(
98
+ {
99
+ "class": "Packet",
100
+ "method": "unpack",
101
+ "packet_type": type(packet).__name__,
102
+ "values": packet_values,
103
+ }
104
+ )
105
+
82
106
  return packet
83
107
 
84
108
  @classmethod
@@ -177,6 +201,9 @@ class Packet:
177
201
  "MultiZoneApplicationRequest",
178
202
  "MultiZoneEffectType",
179
203
  "MultiZoneExtendedApplicationRequest",
204
+ "TileEffectSkyPalette",
205
+ "TileEffectSkyType",
206
+ "TileEffectType",
180
207
  }
181
208
  is_enum = is_nested and base_type in enum_types
182
209
 
@@ -320,6 +347,9 @@ class Packet:
320
347
  MultiZoneApplicationRequest,
321
348
  MultiZoneEffectType,
322
349
  MultiZoneExtendedApplicationRequest,
350
+ TileEffectSkyPalette,
351
+ TileEffectSkyType,
352
+ TileEffectType,
323
353
  )
324
354
 
325
355
  base_type, array_count, is_nested = cls._parse_field_type(field_type)
@@ -331,6 +361,9 @@ class Packet:
331
361
  "MultiZoneApplicationRequest": MultiZoneApplicationRequest,
332
362
  "MultiZoneEffectType": MultiZoneEffectType,
333
363
  "MultiZoneExtendedApplicationRequest": MultiZoneExtendedApplicationRequest,
364
+ "TileEffectSkyPalette": TileEffectSkyPalette,
365
+ "TileEffectSkyType": TileEffectSkyType,
366
+ "TileEffectType": TileEffectType,
334
367
  }
335
368
 
336
369
  if array_count:
@@ -386,3 +419,28 @@ class Packet:
386
419
  # Cache the result
387
420
  _FIELD_TYPE_CACHE[field_type] = result
388
421
  return result
422
+
423
+ @staticmethod
424
+ def _decode_labels_inplace(packet: object) -> None:
425
+ """Decode label fields from bytes to string in-place.
426
+
427
+ Automatically finds and decodes any field named 'label' or ending with '_label'
428
+ for all State packets. This ensures human-readable labels in all contexts.
429
+
430
+ Args:
431
+ packet: Packet instance to process (modified in-place)
432
+ """
433
+ from dataclasses import fields, is_dataclass
434
+
435
+ if not is_dataclass(packet):
436
+ return
437
+
438
+ for field_info in fields(packet):
439
+ # Check if this looks like a label field
440
+ if field_info.name == "label" or field_info.name.endswith("_label"):
441
+ value = getattr(packet, field_info.name)
442
+ if isinstance(value, bytes):
443
+ # Decode: strip null terminator, decode UTF-8
444
+ decoded = value.rstrip(b"\x00").decode("utf-8")
445
+ # Use object.__setattr__ to bypass frozen dataclass if needed
446
+ object.__setattr__(packet, field_info.name, decoded)
@@ -505,16 +505,11 @@ class TileBufferRect:
505
505
 
506
506
  @dataclass
507
507
  class TileEffectParameter:
508
- """Auto-generated field structure."""
508
+ """Auto-generated field structure for Sky effects."""
509
509
 
510
- parameter0: int
511
- parameter1: int
512
- parameter2: int
513
- parameter3: int
514
- parameter4: int
515
- parameter5: int
516
- parameter6: int
517
- parameter7: int
510
+ sky_type: TileEffectSkyType
511
+ cloud_saturation_min: int
512
+ cloud_saturation_max: int
518
513
 
519
514
  def pack(self) -> bytes:
520
515
  """Pack to bytes."""
@@ -522,22 +517,18 @@ class TileEffectParameter:
522
517
 
523
518
  result = b""
524
519
 
525
- # parameter0: uint32
526
- result += serializer.pack_value(self.parameter0, "uint32")
527
- # parameter1: uint32
528
- result += serializer.pack_value(self.parameter1, "uint32")
529
- # parameter2: uint32
530
- result += serializer.pack_value(self.parameter2, "uint32")
531
- # parameter3: uint32
532
- result += serializer.pack_value(self.parameter3, "uint32")
533
- # parameter4: uint32
534
- result += serializer.pack_value(self.parameter4, "uint32")
535
- # parameter5: uint32
536
- result += serializer.pack_value(self.parameter5, "uint32")
537
- # parameter6: uint32
538
- result += serializer.pack_value(self.parameter6, "uint32")
539
- # parameter7: uint32
540
- result += serializer.pack_value(self.parameter7, "uint32")
520
+ # sky_type: TileEffectSkyType (enum)
521
+ result += serializer.pack_value(int(self.sky_type), "uint8")
522
+ # Reserved 3 bytes
523
+ result += serializer.pack_reserved(3)
524
+ # cloud_saturation_min: uint8
525
+ result += serializer.pack_value(self.cloud_saturation_min, "uint8")
526
+ # Reserved 3 bytes
527
+ result += serializer.pack_reserved(3)
528
+ # cloud_saturation_max: uint8
529
+ result += serializer.pack_value(self.cloud_saturation_max, "uint8")
530
+ # Reserved 23 bytes
531
+ result += serializer.pack_reserved(23)
541
532
 
542
533
  return result
543
534
 
@@ -547,49 +538,31 @@ class TileEffectParameter:
547
538
  from lifx_emulator.protocol import serializer
548
539
 
549
540
  current_offset = offset
550
- # parameter0: uint32
551
- parameter0, current_offset = serializer.unpack_value(
552
- data, "uint32", current_offset
553
- )
554
- # parameter1: uint32
555
- parameter1, current_offset = serializer.unpack_value(
556
- data, "uint32", current_offset
557
- )
558
- # parameter2: uint32
559
- parameter2, current_offset = serializer.unpack_value(
560
- data, "uint32", current_offset
561
- )
562
- # parameter3: uint32
563
- parameter3, current_offset = serializer.unpack_value(
564
- data, "uint32", current_offset
565
- )
566
- # parameter4: uint32
567
- parameter4, current_offset = serializer.unpack_value(
568
- data, "uint32", current_offset
569
- )
570
- # parameter5: uint32
571
- parameter5, current_offset = serializer.unpack_value(
572
- data, "uint32", current_offset
541
+ # sky_type: TileEffectSkyType (enum)
542
+ sky_type_raw, current_offset = serializer.unpack_value(
543
+ data, "uint8", current_offset
573
544
  )
574
- # parameter6: uint32
575
- parameter6, current_offset = serializer.unpack_value(
576
- data, "uint32", current_offset
545
+ sky_type = TileEffectSkyType(sky_type_raw)
546
+ # Skip reserved 3 bytes
547
+ current_offset += 3
548
+ # cloud_saturation_min: uint8
549
+ cloud_saturation_min, current_offset = serializer.unpack_value(
550
+ data, "uint8", current_offset
577
551
  )
578
- # parameter7: uint32
579
- parameter7, current_offset = serializer.unpack_value(
580
- data, "uint32", current_offset
552
+ # Skip reserved 3 bytes
553
+ current_offset += 3
554
+ # cloud_saturation_max: uint8
555
+ cloud_saturation_max, current_offset = serializer.unpack_value(
556
+ data, "uint8", current_offset
581
557
  )
558
+ # Skip reserved 23 bytes
559
+ current_offset += 23
582
560
 
583
561
  return (
584
562
  cls(
585
- parameter0=parameter0,
586
- parameter1=parameter1,
587
- parameter2=parameter2,
588
- parameter3=parameter3,
589
- parameter4=parameter4,
590
- parameter5=parameter5,
591
- parameter6=parameter6,
592
- parameter7=parameter7,
563
+ sky_type=sky_type,
564
+ cloud_saturation_min=cloud_saturation_min,
565
+ cloud_saturation_max=cloud_saturation_max,
593
566
  ),
594
567
  current_offset,
595
568
  )
lifx_emulator/server.py CHANGED
@@ -217,7 +217,7 @@ class EmulatedLifxServer:
217
217
  resp_packet_name = _get_packet_type_name(resp_header.pkt_type)
218
218
  resp_fields_str = _format_packet_fields(resp_packet)
219
219
  logger.debug(
220
- "→ TX %s to %s:%s (device=%s, seq=%s) [%s]",
220
+ "→ TX %s to %s:%s (target=%s, seq=%s) [%s]",
221
221
  resp_packet_name,
222
222
  addr[0],
223
223
  addr[1],
@@ -303,7 +303,9 @@ class EmulatedLifxServer:
303
303
 
304
304
  # Log received packet with details
305
305
  packet_name = _get_packet_type_name(header.pkt_type)
306
- target_str = "broadcast" if header.tagged else header.target.hex()
306
+ target_str = (
307
+ "broadcast" if header.tagged else header.target.hex().rstrip("0000")
308
+ )
307
309
  fields_str = _format_packet_fields(packet)
308
310
  logger.debug(
309
311
  "← RX %s from %s:%s (target=%s, seq=%s) [%s]",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lifx-emulator
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: LIFX Emulator for testing LIFX LAN protocol libraries
5
5
  Author-email: Avi Miller <me@dje.li>
6
6
  Maintainer-email: Avi Miller <me@dje.li>
@@ -1,7 +1,7 @@
1
1
  lifx_emulator/__init__.py,sha256=vjhtpAQRSsUZtaUGCQKbmPALvwZ_BF8Mko8w6jzVqBw,819
2
2
  lifx_emulator/__main__.py,sha256=zaul9OQhN5csqOqxGWXkVrlurfo2R_-YvM6URk4QAME,21680
3
3
  lifx_emulator/constants.py,sha256=DFZkUsdewE-x_3MgO28tMGkjUCWPeYc3xLj_EXViGOw,1032
4
- lifx_emulator/server.py,sha256=0bn7oDIlC6TTOJj9ULXLp9rCFAFcW4vM4whor7VTuRU,16391
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
7
  lifx_emulator/api/models.py,sha256=eBx80Ece_4Wv6aqxb1CsZEob9CF0WmR9oJGz3hh14x8,3973
@@ -21,7 +21,7 @@ lifx_emulator/devices/observers.py,sha256=-KnUgFcKdhlNo7CNVstP-u0wU2W0JAGg055ZPV
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
23
  lifx_emulator/devices/state_serializer.py,sha256=O4Cp3bbGkd4eZf5jzb0MKzWDTgiNhrSGgypmMWaB4dg,5097
24
- lifx_emulator/devices/states.py,sha256=ealrShXAqEeKYnyNclTGgWxV9uDf3VYyw4SbRHe1xEk,10205
24
+ lifx_emulator/devices/states.py,sha256=mVZz7FQeIHLpv2SokmhlQlSBIyVj3GuhGMHBVoFlJqk,10836
25
25
  lifx_emulator/factories/__init__.py,sha256=yN8i_Hu_cFEryWZmh0TiOQvWEYFVIApQSs4xeb0EfBk,1170
26
26
  lifx_emulator/factories/builder.py,sha256=ZSz5apcorsKpuPsdjFE4VLC1p41jVY8MWs1-nRBOLMk,11996
27
27
  lifx_emulator/factories/default_config.py,sha256=FTcxKDfeTmO49GTSki8nxnEIZQzR0Lg0hL_PwHUrkVQ,4828
@@ -34,19 +34,19 @@ lifx_emulator/handlers/device_handlers.py,sha256=1AmslA4Ut6L7b3SfduDdvnQizTpzUB3
34
34
  lifx_emulator/handlers/light_handlers.py,sha256=Ryz-_fzoVCT6DBkXhW9YCOYJYaMRcBOIguL3HrQXhAw,11471
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=D23dQVwukfKccryNEFrojMFhubcg4p-onMCXEDRyTlc,10039
37
+ lifx_emulator/handlers/tile_handlers.py,sha256=-DU4PufPgE7vfvKsZfxP_7vBtI3EtAeBF3-U2-1zyaQ,11294
38
38
  lifx_emulator/products/__init__.py,sha256=qcNop_kRYFF3zSjNemzQEgu3jPrIxfyQyLv9GsnaLEI,627
39
39
  lifx_emulator/products/generator.py,sha256=NYInVSGyYIxAYMpTihqBtXP06lAYVfbSYe0Wv5Hg9vQ,31758
40
40
  lifx_emulator/products/registry.py,sha256=qkm2xgGZo_ds3wAbYplLu4gb0cxhjZXjnCc1V8etpHw,46517
41
41
  lifx_emulator/products/specs.py,sha256=pfmQMrQxlCGqORs3MbsH_vmCvxdaDwjVzXUCVZCjFCI,7093
42
42
  lifx_emulator/products/specs.yml,sha256=uxzdKFREAHphk8XSPiCHvQE2vwoPfT2m1xy-zC4ZIl4,8552
43
43
  lifx_emulator/protocol/__init__.py,sha256=-wjC-wBcb7fxi5I-mJr2Ad8K2YRflJFdLLdobfD-W1Q,56
44
- lifx_emulator/protocol/base.py,sha256=8DyJBhJi9k5LH4qRe-9P-XBC0iUEH01lGodoADH6Za8,13209
44
+ lifx_emulator/protocol/base.py,sha256=V6t0baSgIXjrsz2dBuUn_V9xwradSqMxBFJHAUtnfCs,15368
45
45
  lifx_emulator/protocol/const.py,sha256=ilhv-KcQpHtKh2MDCaIbMLQAsxKO_uTaxyR63v1W8cc,226
46
46
  lifx_emulator/protocol/generator.py,sha256=LUkf-1Z5570Vg5iA1QhDZDWQOrABqmukUgk9qH-IJmg,49524
47
47
  lifx_emulator/protocol/header.py,sha256=RXMJ5YZG1jyxl4Mz46ZGJBYX41Jdp7J95BHuY-scYC0,5499
48
48
  lifx_emulator/protocol/packets.py,sha256=Yv4O-Uqbj0CR7n04vXhfalJVCmTTvJTWkvZBkcwPx-U,41553
49
- lifx_emulator/protocol/protocol_types.py,sha256=2Mccm9717EuTXQYaW44W_yReI4EtnlPp3-WEVASgdGY,24820
49
+ lifx_emulator/protocol/protocol_types.py,sha256=WX1p4fmFcNJURmEV_B7ubi7fgu-w9loXQ89q8DdbeSA,23970
50
50
  lifx_emulator/protocol/serializer.py,sha256=2bZz7TddxaMRO4_6LujRGCS1w7GxD4E3rRk3r-hpEIE,10738
51
51
  lifx_emulator/repositories/__init__.py,sha256=x-ncM6T_Q7jNrwhK4a1uAyMrTGHHGeUzPSLC4O-kEUw,645
52
52
  lifx_emulator/repositories/device_repository.py,sha256=KsXVg2sg7PGSTsK_PvDYeHHwEPM9Qx2ZZF_ORncBrYQ,3929
@@ -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.0.0.dist-info/METADATA,sha256=u29qYpMQ0IbZju5mormUZu6Nye04gpQnxBMWnmSYNiM,4549
59
- lifx_emulator-2.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
60
- lifx_emulator-2.0.0.dist-info/entry_points.txt,sha256=R9C_K_tTgt6yXEmhzH4r2Yx2Tu1rLlnYzeG4RFUVzSc,62
61
- lifx_emulator-2.0.0.dist-info/licenses/LICENSE,sha256=eBz48GRA3gSiWn3rYZAz2Ewp35snnhV9cSqkVBq7g3k,1832
62
- lifx_emulator-2.0.0.dist-info/RECORD,,
58
+ lifx_emulator-2.1.0.dist-info/METADATA,sha256=cen3ovCv4G8WJyDQpy46bpFndEZ1OZH09NFuaoE0-mw,4549
59
+ lifx_emulator-2.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
60
+ lifx_emulator-2.1.0.dist-info/entry_points.txt,sha256=R9C_K_tTgt6yXEmhzH4r2Yx2Tu1rLlnYzeG4RFUVzSc,62
61
+ lifx_emulator-2.1.0.dist-info/licenses/LICENSE,sha256=eBz48GRA3gSiWn3rYZAz2Ewp35snnhV9cSqkVBq7g3k,1832
62
+ lifx_emulator-2.1.0.dist-info/RECORD,,