lifx-emulator 1.0.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.
Files changed (40) hide show
  1. lifx_emulator/__init__.py +31 -0
  2. lifx_emulator/__main__.py +607 -0
  3. lifx_emulator/api.py +1825 -0
  4. lifx_emulator/async_storage.py +308 -0
  5. lifx_emulator/constants.py +33 -0
  6. lifx_emulator/device.py +750 -0
  7. lifx_emulator/device_states.py +114 -0
  8. lifx_emulator/factories.py +380 -0
  9. lifx_emulator/handlers/__init__.py +39 -0
  10. lifx_emulator/handlers/base.py +49 -0
  11. lifx_emulator/handlers/device_handlers.py +340 -0
  12. lifx_emulator/handlers/light_handlers.py +372 -0
  13. lifx_emulator/handlers/multizone_handlers.py +249 -0
  14. lifx_emulator/handlers/registry.py +110 -0
  15. lifx_emulator/handlers/tile_handlers.py +309 -0
  16. lifx_emulator/observers.py +139 -0
  17. lifx_emulator/products/__init__.py +28 -0
  18. lifx_emulator/products/generator.py +771 -0
  19. lifx_emulator/products/registry.py +1446 -0
  20. lifx_emulator/products/specs.py +242 -0
  21. lifx_emulator/products/specs.yml +327 -0
  22. lifx_emulator/protocol/__init__.py +1 -0
  23. lifx_emulator/protocol/base.py +334 -0
  24. lifx_emulator/protocol/const.py +8 -0
  25. lifx_emulator/protocol/generator.py +1371 -0
  26. lifx_emulator/protocol/header.py +159 -0
  27. lifx_emulator/protocol/packets.py +1351 -0
  28. lifx_emulator/protocol/protocol_types.py +844 -0
  29. lifx_emulator/protocol/serializer.py +379 -0
  30. lifx_emulator/scenario_manager.py +402 -0
  31. lifx_emulator/scenario_persistence.py +206 -0
  32. lifx_emulator/server.py +482 -0
  33. lifx_emulator/state_restorer.py +259 -0
  34. lifx_emulator/state_serializer.py +130 -0
  35. lifx_emulator/storage_protocol.py +100 -0
  36. lifx_emulator-1.0.0.dist-info/METADATA +445 -0
  37. lifx_emulator-1.0.0.dist-info/RECORD +40 -0
  38. lifx_emulator-1.0.0.dist-info/WHEEL +4 -0
  39. lifx_emulator-1.0.0.dist-info/entry_points.txt +2 -0
  40. lifx_emulator-1.0.0.dist-info/licenses/LICENSE +35 -0
@@ -0,0 +1,844 @@
1
+ """Auto-generated LIFX protocol types.
2
+
3
+ DO NOT EDIT THIS FILE MANUALLY.
4
+ Generated from https://github.com/LIFX/public-protocol/blob/main/protocol.yml
5
+ by protocol/generator.py
6
+
7
+ Uses Pythonic naming conventions (snake_case fields, shortened enums) while
8
+ maintaining compatibility with the official LIFX protocol through mappings.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from enum import IntEnum
15
+
16
+
17
+ class DeviceService(IntEnum):
18
+ """Auto-generated enum."""
19
+
20
+ UDP = 1
21
+ RESERVED_0 = 2
22
+ RESERVED_1 = 3
23
+ RESERVED_2 = 4
24
+ RESERVED_3 = 5
25
+
26
+
27
+ class LightLastHevCycleResult(IntEnum):
28
+ """Auto-generated enum."""
29
+
30
+ SUCCESS = 0
31
+ BUSY = 1
32
+ INTERRUPTED_BY_RESET = 2
33
+ INTERRUPTED_BY_HOMEKIT = 3
34
+ INTERRUPTED_BY_LAN = 4
35
+ INTERRUPTED_BY_CLOUD = 5
36
+ NONE = 255
37
+
38
+
39
+ class LightWaveform(IntEnum):
40
+ """Auto-generated enum."""
41
+
42
+ SAW = 0
43
+ SINE = 1
44
+ HALF_SINE = 2
45
+ TRIANGLE = 3
46
+ PULSE = 4
47
+
48
+
49
+ class MultiZoneApplicationRequest(IntEnum):
50
+ """Auto-generated enum."""
51
+
52
+ NO_APPLY = 0
53
+ APPLY = 1
54
+ APPLY_ONLY = 2
55
+
56
+
57
+ class MultiZoneEffectType(IntEnum):
58
+ """Auto-generated enum."""
59
+
60
+ OFF = 0
61
+ MOVE = 1
62
+ RESERVED_0 = 2
63
+ RESERVED_1 = 3
64
+
65
+
66
+ class MultiZoneExtendedApplicationRequest(IntEnum):
67
+ """Auto-generated enum."""
68
+
69
+ NO_APPLY = 0
70
+ APPLY = 1
71
+ APPLY_ONLY = 2
72
+
73
+
74
+ class TileEffectSkyPalette(IntEnum):
75
+ """Auto-generated enum."""
76
+
77
+ CLOUDS_SKY = 0
78
+ NIGHT_SKY = 1
79
+ DAWN_SKY = 2
80
+ DAWN_SUN = 3
81
+ FULL_SUN = 4
82
+ FINAL_SUN = 5
83
+ NUM_COLOURS = 6
84
+
85
+
86
+ class TileEffectSkyType(IntEnum):
87
+ """Auto-generated enum."""
88
+
89
+ SUNRISE = 0
90
+ SUNSET = 1
91
+ CLOUDS = 2
92
+
93
+
94
+ class TileEffectType(IntEnum):
95
+ """Auto-generated enum."""
96
+
97
+ OFF = 0
98
+ RESERVED_0 = 1
99
+ MORPH = 2
100
+ FLAME = 3
101
+ RESERVED_1 = 4
102
+ SKY = 5
103
+
104
+
105
+ @dataclass
106
+ class DeviceStateHostFirmware:
107
+ """Auto-generated field structure."""
108
+
109
+ build: int
110
+ version_minor: int
111
+ version_major: int
112
+
113
+ def pack(self) -> bytes:
114
+ """Pack to bytes."""
115
+ from lifx_emulator.protocol import serializer
116
+
117
+ result = b""
118
+
119
+ # build: uint64
120
+ result += serializer.pack_value(self.build, "uint64")
121
+ # Reserved 8 bytes
122
+ result += serializer.pack_reserved(8)
123
+ # version_minor: uint16
124
+ result += serializer.pack_value(self.version_minor, "uint16")
125
+ # version_major: uint16
126
+ result += serializer.pack_value(self.version_major, "uint16")
127
+
128
+ return result
129
+
130
+ @classmethod
131
+ def unpack(
132
+ cls, data: bytes, offset: int = 0
133
+ ) -> tuple[DeviceStateHostFirmware, int]:
134
+ """Unpack from bytes."""
135
+ from lifx_emulator.protocol import serializer
136
+
137
+ current_offset = offset
138
+ # build: uint64
139
+ build, current_offset = serializer.unpack_value(data, "uint64", current_offset)
140
+ # Skip reserved 8 bytes
141
+ current_offset += 8
142
+ # version_minor: uint16
143
+ version_minor, current_offset = serializer.unpack_value(
144
+ data, "uint16", current_offset
145
+ )
146
+ # version_major: uint16
147
+ version_major, current_offset = serializer.unpack_value(
148
+ data, "uint16", current_offset
149
+ )
150
+
151
+ return cls(
152
+ build=build, version_minor=version_minor, version_major=version_major
153
+ ), current_offset
154
+
155
+
156
+ @dataclass
157
+ class DeviceStateVersion:
158
+ """Auto-generated field structure."""
159
+
160
+ vendor: int
161
+ product: int
162
+
163
+ def pack(self) -> bytes:
164
+ """Pack to bytes."""
165
+ from lifx_emulator.protocol import serializer
166
+
167
+ result = b""
168
+
169
+ # vendor: uint32
170
+ result += serializer.pack_value(self.vendor, "uint32")
171
+ # product: uint32
172
+ result += serializer.pack_value(self.product, "uint32")
173
+ # Reserved 4 bytes
174
+ result += serializer.pack_reserved(4)
175
+
176
+ return result
177
+
178
+ @classmethod
179
+ def unpack(cls, data: bytes, offset: int = 0) -> tuple[DeviceStateVersion, int]:
180
+ """Unpack from bytes."""
181
+ from lifx_emulator.protocol import serializer
182
+
183
+ current_offset = offset
184
+ # vendor: uint32
185
+ vendor, current_offset = serializer.unpack_value(data, "uint32", current_offset)
186
+ # product: uint32
187
+ product, current_offset = serializer.unpack_value(
188
+ data, "uint32", current_offset
189
+ )
190
+ # Skip reserved 4 bytes
191
+ current_offset += 4
192
+
193
+ return cls(vendor=vendor, product=product), current_offset
194
+
195
+
196
+ @dataclass
197
+ class LightHsbk:
198
+ """Auto-generated field structure."""
199
+
200
+ hue: int
201
+ saturation: int
202
+ brightness: int
203
+ kelvin: int
204
+
205
+ def pack(self) -> bytes:
206
+ """Pack to bytes."""
207
+ from lifx_emulator.protocol import serializer
208
+
209
+ result = b""
210
+
211
+ # hue: uint16
212
+ result += serializer.pack_value(self.hue, "uint16")
213
+ # saturation: uint16
214
+ result += serializer.pack_value(self.saturation, "uint16")
215
+ # brightness: uint16
216
+ result += serializer.pack_value(self.brightness, "uint16")
217
+ # kelvin: uint16
218
+ result += serializer.pack_value(self.kelvin, "uint16")
219
+
220
+ return result
221
+
222
+ @classmethod
223
+ def unpack(cls, data: bytes, offset: int = 0) -> tuple[LightHsbk, int]:
224
+ """Unpack from bytes."""
225
+ from lifx_emulator.protocol import serializer
226
+
227
+ current_offset = offset
228
+ # hue: uint16
229
+ hue, current_offset = serializer.unpack_value(data, "uint16", current_offset)
230
+ # saturation: uint16
231
+ saturation, current_offset = serializer.unpack_value(
232
+ data, "uint16", current_offset
233
+ )
234
+ # brightness: uint16
235
+ brightness, current_offset = serializer.unpack_value(
236
+ data, "uint16", current_offset
237
+ )
238
+ # kelvin: uint16
239
+ kelvin, current_offset = serializer.unpack_value(data, "uint16", current_offset)
240
+
241
+ return cls(
242
+ hue=hue, saturation=saturation, brightness=brightness, kelvin=kelvin
243
+ ), current_offset
244
+
245
+
246
+ @dataclass
247
+ class MultiZoneEffectParameter:
248
+ """Auto-generated field structure."""
249
+
250
+ parameter0: int
251
+ parameter1: int
252
+ parameter2: int
253
+ parameter3: int
254
+ parameter4: int
255
+ parameter5: int
256
+ parameter6: int
257
+ parameter7: int
258
+
259
+ def pack(self) -> bytes:
260
+ """Pack to bytes."""
261
+ from lifx_emulator.protocol import serializer
262
+
263
+ result = b""
264
+
265
+ # parameter0: uint32
266
+ result += serializer.pack_value(self.parameter0, "uint32")
267
+ # parameter1: uint32
268
+ result += serializer.pack_value(self.parameter1, "uint32")
269
+ # parameter2: uint32
270
+ result += serializer.pack_value(self.parameter2, "uint32")
271
+ # parameter3: uint32
272
+ result += serializer.pack_value(self.parameter3, "uint32")
273
+ # parameter4: uint32
274
+ result += serializer.pack_value(self.parameter4, "uint32")
275
+ # parameter5: uint32
276
+ result += serializer.pack_value(self.parameter5, "uint32")
277
+ # parameter6: uint32
278
+ result += serializer.pack_value(self.parameter6, "uint32")
279
+ # parameter7: uint32
280
+ result += serializer.pack_value(self.parameter7, "uint32")
281
+
282
+ return result
283
+
284
+ @classmethod
285
+ def unpack(
286
+ cls, data: bytes, offset: int = 0
287
+ ) -> tuple[MultiZoneEffectParameter, int]:
288
+ """Unpack from bytes."""
289
+ from lifx_emulator.protocol import serializer
290
+
291
+ current_offset = offset
292
+ # parameter0: uint32
293
+ parameter0, current_offset = serializer.unpack_value(
294
+ data, "uint32", current_offset
295
+ )
296
+ # parameter1: uint32
297
+ parameter1, current_offset = serializer.unpack_value(
298
+ data, "uint32", current_offset
299
+ )
300
+ # parameter2: uint32
301
+ parameter2, current_offset = serializer.unpack_value(
302
+ data, "uint32", current_offset
303
+ )
304
+ # parameter3: uint32
305
+ parameter3, current_offset = serializer.unpack_value(
306
+ data, "uint32", current_offset
307
+ )
308
+ # parameter4: uint32
309
+ parameter4, current_offset = serializer.unpack_value(
310
+ data, "uint32", current_offset
311
+ )
312
+ # parameter5: uint32
313
+ parameter5, current_offset = serializer.unpack_value(
314
+ data, "uint32", current_offset
315
+ )
316
+ # parameter6: uint32
317
+ parameter6, current_offset = serializer.unpack_value(
318
+ data, "uint32", current_offset
319
+ )
320
+ # parameter7: uint32
321
+ parameter7, current_offset = serializer.unpack_value(
322
+ data, "uint32", current_offset
323
+ )
324
+
325
+ return (
326
+ cls(
327
+ parameter0=parameter0,
328
+ parameter1=parameter1,
329
+ parameter2=parameter2,
330
+ parameter3=parameter3,
331
+ parameter4=parameter4,
332
+ parameter5=parameter5,
333
+ parameter6=parameter6,
334
+ parameter7=parameter7,
335
+ ),
336
+ current_offset,
337
+ )
338
+
339
+
340
+ @dataclass
341
+ class MultiZoneEffectSettings:
342
+ """Auto-generated field structure."""
343
+
344
+ instanceid: int
345
+ type: MultiZoneEffectType
346
+ speed: int
347
+ duration: int
348
+ parameter: MultiZoneEffectParameter
349
+
350
+ def pack(self) -> bytes:
351
+ """Pack to bytes."""
352
+ from lifx_emulator.protocol import serializer
353
+
354
+ result = b""
355
+
356
+ # instanceid: uint32
357
+ result += serializer.pack_value(self.instanceid, "uint32")
358
+ # type: MultiZoneEffectType (enum)
359
+ result += serializer.pack_value(int(self.type), "uint8")
360
+ # Reserved 2 bytes
361
+ result += serializer.pack_reserved(2)
362
+ # speed: uint32
363
+ result += serializer.pack_value(self.speed, "uint32")
364
+ # duration: uint64
365
+ result += serializer.pack_value(self.duration, "uint64")
366
+ # Reserved 4 bytes
367
+ result += serializer.pack_reserved(4)
368
+ # Reserved 4 bytes
369
+ result += serializer.pack_reserved(4)
370
+ # parameter: MultiZoneEffectParameter
371
+ result += self.parameter.pack()
372
+
373
+ return result
374
+
375
+ @classmethod
376
+ def unpack(
377
+ cls, data: bytes, offset: int = 0
378
+ ) -> tuple[MultiZoneEffectSettings, int]:
379
+ """Unpack from bytes."""
380
+ from lifx_emulator.protocol import serializer
381
+
382
+ current_offset = offset
383
+ # instanceid: uint32
384
+ instanceid, current_offset = serializer.unpack_value(
385
+ data, "uint32", current_offset
386
+ )
387
+ # type: MultiZoneEffectType (enum)
388
+ type_raw, current_offset = serializer.unpack_value(
389
+ data, "uint8", current_offset
390
+ )
391
+ type = MultiZoneEffectType(type_raw)
392
+ # Skip reserved 2 bytes
393
+ current_offset += 2
394
+ # speed: uint32
395
+ speed, current_offset = serializer.unpack_value(data, "uint32", current_offset)
396
+ # duration: uint64
397
+ duration, current_offset = serializer.unpack_value(
398
+ data, "uint64", current_offset
399
+ )
400
+ # Skip reserved 4 bytes
401
+ current_offset += 4
402
+ # Skip reserved 4 bytes
403
+ current_offset += 4
404
+ # parameter: MultiZoneEffectParameter
405
+ parameter, current_offset = MultiZoneEffectParameter.unpack(
406
+ data, current_offset
407
+ )
408
+
409
+ return (
410
+ cls(
411
+ instanceid=instanceid,
412
+ type=type,
413
+ speed=speed,
414
+ duration=duration,
415
+ parameter=parameter,
416
+ ),
417
+ current_offset,
418
+ )
419
+
420
+
421
+ @dataclass
422
+ class TileAccelMeas:
423
+ """Auto-generated field structure."""
424
+
425
+ x: int
426
+ y: int
427
+ z: int
428
+
429
+ def pack(self) -> bytes:
430
+ """Pack to bytes."""
431
+ from lifx_emulator.protocol import serializer
432
+
433
+ result = b""
434
+
435
+ # x: int16
436
+ result += serializer.pack_value(self.x, "int16")
437
+ # y: int16
438
+ result += serializer.pack_value(self.y, "int16")
439
+ # z: int16
440
+ result += serializer.pack_value(self.z, "int16")
441
+
442
+ return result
443
+
444
+ @classmethod
445
+ def unpack(cls, data: bytes, offset: int = 0) -> tuple[TileAccelMeas, int]:
446
+ """Unpack from bytes."""
447
+ from lifx_emulator.protocol import serializer
448
+
449
+ current_offset = offset
450
+ # x: int16
451
+ x, current_offset = serializer.unpack_value(data, "int16", current_offset)
452
+ # y: int16
453
+ y, current_offset = serializer.unpack_value(data, "int16", current_offset)
454
+ # z: int16
455
+ z, current_offset = serializer.unpack_value(data, "int16", current_offset)
456
+
457
+ return cls(x=x, y=y, z=z), current_offset
458
+
459
+
460
+ @dataclass
461
+ class TileBufferRect:
462
+ """Auto-generated field structure."""
463
+
464
+ fb_index: int
465
+ x: int
466
+ y: int
467
+ width: int
468
+
469
+ def pack(self) -> bytes:
470
+ """Pack to bytes."""
471
+ from lifx_emulator.protocol import serializer
472
+
473
+ result = b""
474
+
475
+ # fb_index: uint8
476
+ result += serializer.pack_value(self.fb_index, "uint8")
477
+ # x: uint8
478
+ result += serializer.pack_value(self.x, "uint8")
479
+ # y: uint8
480
+ result += serializer.pack_value(self.y, "uint8")
481
+ # width: uint8
482
+ result += serializer.pack_value(self.width, "uint8")
483
+
484
+ return result
485
+
486
+ @classmethod
487
+ def unpack(cls, data: bytes, offset: int = 0) -> tuple[TileBufferRect, int]:
488
+ """Unpack from bytes."""
489
+ from lifx_emulator.protocol import serializer
490
+
491
+ current_offset = offset
492
+ # fb_index: uint8
493
+ fb_index, current_offset = serializer.unpack_value(
494
+ data, "uint8", current_offset
495
+ )
496
+ # x: uint8
497
+ x, current_offset = serializer.unpack_value(data, "uint8", current_offset)
498
+ # y: uint8
499
+ y, current_offset = serializer.unpack_value(data, "uint8", current_offset)
500
+ # width: uint8
501
+ width, current_offset = serializer.unpack_value(data, "uint8", current_offset)
502
+
503
+ return cls(fb_index=fb_index, x=x, y=y, width=width), current_offset
504
+
505
+
506
+ @dataclass
507
+ class TileEffectParameter:
508
+ """Auto-generated field structure."""
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
518
+
519
+ def pack(self) -> bytes:
520
+ """Pack to bytes."""
521
+ from lifx_emulator.protocol import serializer
522
+
523
+ result = b""
524
+
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")
541
+
542
+ return result
543
+
544
+ @classmethod
545
+ def unpack(cls, data: bytes, offset: int = 0) -> tuple[TileEffectParameter, int]:
546
+ """Unpack from bytes."""
547
+ from lifx_emulator.protocol import serializer
548
+
549
+ 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
573
+ )
574
+ # parameter6: uint32
575
+ parameter6, current_offset = serializer.unpack_value(
576
+ data, "uint32", current_offset
577
+ )
578
+ # parameter7: uint32
579
+ parameter7, current_offset = serializer.unpack_value(
580
+ data, "uint32", current_offset
581
+ )
582
+
583
+ return (
584
+ 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,
593
+ ),
594
+ current_offset,
595
+ )
596
+
597
+
598
+ @dataclass
599
+ class TileEffectSettings:
600
+ """Auto-generated field structure."""
601
+
602
+ instanceid: int
603
+ type: TileEffectType
604
+ speed: int
605
+ duration: int
606
+ parameter: TileEffectParameter
607
+ palette_count: int
608
+ palette: list[LightHsbk]
609
+
610
+ def pack(self) -> bytes:
611
+ """Pack to bytes."""
612
+ from lifx_emulator.protocol import serializer
613
+
614
+ result = b""
615
+
616
+ # instanceid: uint32
617
+ result += serializer.pack_value(self.instanceid, "uint32")
618
+ # type: TileEffectType (enum)
619
+ result += serializer.pack_value(int(self.type), "uint8")
620
+ # speed: uint32
621
+ result += serializer.pack_value(self.speed, "uint32")
622
+ # duration: uint64
623
+ result += serializer.pack_value(self.duration, "uint64")
624
+ # Reserved 4 bytes
625
+ result += serializer.pack_reserved(4)
626
+ # Reserved 4 bytes
627
+ result += serializer.pack_reserved(4)
628
+ # parameter: TileEffectParameter
629
+ result += self.parameter.pack()
630
+ # palette_count: uint8
631
+ result += serializer.pack_value(self.palette_count, "uint8")
632
+ # palette: list[LightHsbk]
633
+ for item in self.palette:
634
+ result += item.pack()
635
+
636
+ return result
637
+
638
+ @classmethod
639
+ def unpack(cls, data: bytes, offset: int = 0) -> tuple[TileEffectSettings, int]:
640
+ """Unpack from bytes."""
641
+ from lifx_emulator.protocol import serializer
642
+
643
+ current_offset = offset
644
+ # instanceid: uint32
645
+ instanceid, current_offset = serializer.unpack_value(
646
+ data, "uint32", current_offset
647
+ )
648
+ # type: TileEffectType (enum)
649
+ type_raw, current_offset = serializer.unpack_value(
650
+ data, "uint8", current_offset
651
+ )
652
+ type = TileEffectType(type_raw)
653
+ # speed: uint32
654
+ speed, current_offset = serializer.unpack_value(data, "uint32", current_offset)
655
+ # duration: uint64
656
+ duration, current_offset = serializer.unpack_value(
657
+ data, "uint64", current_offset
658
+ )
659
+ # Skip reserved 4 bytes
660
+ current_offset += 4
661
+ # Skip reserved 4 bytes
662
+ current_offset += 4
663
+ # parameter: TileEffectParameter
664
+ parameter, current_offset = TileEffectParameter.unpack(data, current_offset)
665
+ # palette_count: uint8
666
+ palette_count, current_offset = serializer.unpack_value(
667
+ data, "uint8", current_offset
668
+ )
669
+ # palette: list[LightHsbk]
670
+ palette = []
671
+ for _ in range(16):
672
+ item, current_offset = LightHsbk.unpack(data, current_offset)
673
+ palette.append(item)
674
+
675
+ return (
676
+ cls(
677
+ instanceid=instanceid,
678
+ type=type,
679
+ speed=speed,
680
+ duration=duration,
681
+ parameter=parameter,
682
+ palette_count=palette_count,
683
+ palette=palette,
684
+ ),
685
+ current_offset,
686
+ )
687
+
688
+
689
+ @dataclass
690
+ class TileStateDevice:
691
+ """Auto-generated field structure."""
692
+
693
+ accel_meas: TileAccelMeas
694
+ user_x: float
695
+ user_y: float
696
+ width: int
697
+ height: int
698
+ device_version: DeviceStateVersion
699
+ firmware: DeviceStateHostFirmware
700
+
701
+ def pack(self) -> bytes:
702
+ """Pack to bytes."""
703
+ from lifx_emulator.protocol import serializer
704
+
705
+ result = b""
706
+
707
+ # accel_meas: TileAccelMeas
708
+ result += self.accel_meas.pack()
709
+ # Reserved 1 bytes
710
+ result += serializer.pack_reserved(1)
711
+ # Reserved 1 bytes
712
+ result += serializer.pack_reserved(1)
713
+ # user_x: float32
714
+ result += serializer.pack_value(self.user_x, "float32")
715
+ # user_y: float32
716
+ result += serializer.pack_value(self.user_y, "float32")
717
+ # width: uint8
718
+ result += serializer.pack_value(self.width, "uint8")
719
+ # height: uint8
720
+ result += serializer.pack_value(self.height, "uint8")
721
+ # Reserved 1 bytes
722
+ result += serializer.pack_reserved(1)
723
+ # device_version: DeviceStateVersion
724
+ result += self.device_version.pack()
725
+ # firmware: DeviceStateHostFirmware
726
+ result += self.firmware.pack()
727
+ # Reserved 4 bytes
728
+ result += serializer.pack_reserved(4)
729
+
730
+ return result
731
+
732
+ @classmethod
733
+ def unpack(cls, data: bytes, offset: int = 0) -> tuple[TileStateDevice, int]:
734
+ """Unpack from bytes."""
735
+ from lifx_emulator.protocol import serializer
736
+
737
+ current_offset = offset
738
+ # accel_meas: TileAccelMeas
739
+ accel_meas, current_offset = TileAccelMeas.unpack(data, current_offset)
740
+ # Skip reserved 1 bytes
741
+ current_offset += 1
742
+ # Skip reserved 1 bytes
743
+ current_offset += 1
744
+ # user_x: float32
745
+ user_x, current_offset = serializer.unpack_value(
746
+ data, "float32", current_offset
747
+ )
748
+ # user_y: float32
749
+ user_y, current_offset = serializer.unpack_value(
750
+ data, "float32", current_offset
751
+ )
752
+ # width: uint8
753
+ width, current_offset = serializer.unpack_value(data, "uint8", current_offset)
754
+ # height: uint8
755
+ height, current_offset = serializer.unpack_value(data, "uint8", current_offset)
756
+ # Skip reserved 1 bytes
757
+ current_offset += 1
758
+ # device_version: DeviceStateVersion
759
+ device_version, current_offset = DeviceStateVersion.unpack(data, current_offset)
760
+ # firmware: DeviceStateHostFirmware
761
+ firmware, current_offset = DeviceStateHostFirmware.unpack(data, current_offset)
762
+ # Skip reserved 4 bytes
763
+ current_offset += 4
764
+
765
+ return (
766
+ cls(
767
+ accel_meas=accel_meas,
768
+ user_x=user_x,
769
+ user_y=user_y,
770
+ width=width,
771
+ height=height,
772
+ device_version=device_version,
773
+ firmware=firmware,
774
+ ),
775
+ current_offset,
776
+ )
777
+
778
+
779
+ # Type aliases for convenience
780
+ TileDevice = TileStateDevice # Pythonic alias
781
+
782
+ # Field name mappings: Python name -> Protocol name
783
+ # Used by serializer to translate between conventions
784
+ FIELD_MAPPINGS: dict[str, dict[str, str]] = {
785
+ "DeviceStateHostFirmware": {
786
+ "build": "Build",
787
+ "version_minor": "VersionMinor",
788
+ "version_major": "VersionMajor",
789
+ },
790
+ "DeviceStateVersion": {"vendor": "Vendor", "product": "Product"},
791
+ "LightHsbk": {
792
+ "hue": "Hue",
793
+ "saturation": "Saturation",
794
+ "brightness": "Brightness",
795
+ "kelvin": "Kelvin",
796
+ },
797
+ "MultiZoneEffectParameter": {
798
+ "parameter0": "Parameter0",
799
+ "parameter1": "Parameter1",
800
+ "parameter2": "Parameter2",
801
+ "parameter3": "Parameter3",
802
+ "parameter4": "Parameter4",
803
+ "parameter5": "Parameter5",
804
+ "parameter6": "Parameter6",
805
+ "parameter7": "Parameter7",
806
+ },
807
+ "MultiZoneEffectSettings": {
808
+ "duration": "Duration",
809
+ "instanceid": "Instanceid",
810
+ "parameter": "Parameter",
811
+ "speed": "Speed",
812
+ "type": "Type",
813
+ },
814
+ "TileAccelMeas": {"x": "X", "y": "Y", "z": "Z"},
815
+ "TileBufferRect": {"fb_index": "FbIndex", "x": "X", "y": "Y", "width": "Width"},
816
+ "TileEffectParameter": {
817
+ "parameter0": "Parameter0",
818
+ "parameter1": "Parameter1",
819
+ "parameter2": "Parameter2",
820
+ "parameter3": "Parameter3",
821
+ "parameter4": "Parameter4",
822
+ "parameter5": "Parameter5",
823
+ "parameter6": "Parameter6",
824
+ "parameter7": "Parameter7",
825
+ },
826
+ "TileEffectSettings": {
827
+ "duration": "Duration",
828
+ "instanceid": "Instanceid",
829
+ "palette": "Palette",
830
+ "palette_count": "PaletteCount",
831
+ "parameter": "Parameter",
832
+ "speed": "Speed",
833
+ "type": "Type",
834
+ },
835
+ "TileStateDevice": {
836
+ "accel_meas": "AccelMeas",
837
+ "device_version": "DeviceVersion",
838
+ "firmware": "Firmware",
839
+ "height": "Height",
840
+ "user_x": "UserX",
841
+ "user_y": "UserY",
842
+ "width": "Width",
843
+ },
844
+ }