lifx-async 4.3.1__py3-none-any.whl → 4.3.3__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/devices/matrix.py CHANGED
@@ -371,45 +371,58 @@ class MatrixLight(Light):
371
371
 
372
372
  async def get64(
373
373
  self,
374
- tile_index: int,
375
- length: int,
376
- x: int,
377
- y: int,
378
- width: int,
379
- fb_index: int = 0,
374
+ tile_index: int = 0,
375
+ length: int = 1,
376
+ x: int = 0,
377
+ y: int = 0,
378
+ width: int | None = None,
380
379
  ) -> list[HSBK]:
381
380
  """Get up to 64 zones of color state from a tile.
382
381
 
382
+ For devices with ≤64 zones, returns all zones. For devices with >64 zones,
383
+ returns up to 64 zones due to protocol limitations.
384
+
383
385
  Args:
384
- tile_index: Index of the tile (0-based)
385
- length: Number of tiles to query (usually 1)
386
- x: X coordinate of the rectangle (0-based)
387
- y: Y coordinate of the rectangle (0-based)
388
- width: Width of the rectangle in zones
389
- fb_index: Frame buffer index (0 for display, 1 for temp buffer)
386
+ tile_index: Index of the tile (0-based). Defaults to 0.
387
+ length: Number of tiles to query (usually 1). Defaults to 1.
388
+ x: X coordinate of the rectangle (0-based). Defaults to 0.
389
+ y: Y coordinate of the rectangle (0-based). Defaults to 0.
390
+ width: Width of the rectangle in zones. Defaults to tile width.
390
391
 
391
392
  Returns:
392
- List of HSBK colors for the requested zones
393
+ List of HSBK colors for the requested zones. For tiles with ≤64 zones,
394
+ returns the actual zone count (e.g., 64 for 8x8, 16 for 4x4). For tiles
395
+ with >64 zones (e.g., 128 for 16x8 Ceiling), returns 64 (protocol limit).
393
396
 
394
397
  Example:
395
- >>> # Get colors from 8x8 tile (64 zones)
396
- >>> colors = await matrix.get64(tile_index=0, length=1, x=0, y=0, width=8)
398
+ >>> # Get all colors from first tile (no parameters needed)
399
+ >>> colors = await matrix.get64()
400
+ >>>
401
+ >>> # Get colors from specific region
402
+ >>> colors = await matrix.get64(y=4) # Start at row 4
397
403
  """
398
404
  # Validate parameters
399
405
  if x < 0:
400
406
  raise ValueError(f"x coordinate must be non-negative, got {x}")
401
407
  if y < 0:
402
408
  raise ValueError(f"y coordinate must be non-negative, got {y}")
403
- if width <= 0:
409
+ if width is not None and width <= 0:
404
410
  raise ValueError(f"width must be positive, got {width}")
405
411
 
412
+ if self._device_chain is None:
413
+ device_chain = await self.get_device_chain()
414
+ else:
415
+ device_chain = self._device_chain
416
+
417
+ if width is None:
418
+ width = device_chain[0].width
419
+
406
420
  _LOGGER.debug(
407
- "Getting 64 zones from tile %d (x=%d, y=%d, width=%d, fb=%d) for %s",
421
+ "Getting 64 zones from tile %d (x=%d, y=%d, width=%d) for %s",
408
422
  tile_index,
409
423
  x,
410
424
  y,
411
425
  width,
412
- fb_index,
413
426
  self.label or self.serial,
414
427
  )
415
428
 
@@ -417,12 +430,17 @@ class MatrixLight(Light):
417
430
  packets.Tile.Get64(
418
431
  tile_index=tile_index,
419
432
  length=length,
420
- rect=TileBufferRect(fb_index=fb_index, x=x, y=y, width=width),
433
+ rect=TileBufferRect(fb_index=0, x=x, y=y, width=width),
421
434
  )
422
435
  )
423
436
 
437
+ max_colors = device_chain[0].width * device_chain[0].height
438
+
424
439
  # Convert protocol colors to HSBK
425
- return [HSBK.from_protocol(proto_color) for proto_color in response.colors]
440
+ return [
441
+ HSBK.from_protocol(proto_color)
442
+ for proto_color in response.colors[:max_colors]
443
+ ]
426
444
 
427
445
  async def set64(
428
446
  self,
@@ -504,7 +522,11 @@ class MatrixLight(Light):
504
522
  )
505
523
 
506
524
  async def copy_frame_buffer(
507
- self, tile_index: int, source_fb: int = 1, target_fb: int = 0
525
+ self,
526
+ tile_index: int,
527
+ source_fb: int = 1,
528
+ target_fb: int = 0,
529
+ duration: float = 0.0,
508
530
  ) -> None:
509
531
  """Copy frame buffer (for tiles with >64 zones).
510
532
 
@@ -515,6 +537,7 @@ class MatrixLight(Light):
515
537
  tile_index: Index of the tile (0-based)
516
538
  source_fb: Source frame buffer index (usually 1)
517
539
  target_fb: Target frame buffer index (usually 0)
540
+ duration: time in seconds to transition if target_fb is 0
518
541
 
519
542
  Example:
520
543
  >>> # For 16x8 tile (128 zones):
@@ -541,7 +564,9 @@ class MatrixLight(Light):
541
564
  ... fb_index=1,
542
565
  ... )
543
566
  >>> # 3. Copy buffer 1 to buffer 0 (display)
544
- >>> await matrix.copy_frame_buffer(tile_index=0, source_fb=1, target_fb=0)
567
+ >>> await matrix.copy_frame_buffer(
568
+ ... tile_index=0, source_fb=1, target_fb=0, duration=2.0
569
+ ... )
545
570
  """
546
571
  _LOGGER.debug(
547
572
  "Copying frame buffer %d -> %d for tile %d on %s",
@@ -559,6 +584,7 @@ class MatrixLight(Light):
559
584
  raise ValueError(f"Invalid tile_index {tile_index}")
560
585
 
561
586
  tile = self._device_chain[tile_index]
587
+ duration_ms = round(duration * 1000 if duration else 0)
562
588
 
563
589
  await self.connection.send_packet(
564
590
  packets.Tile.CopyFrameBuffer(
@@ -572,7 +598,7 @@ class MatrixLight(Light):
572
598
  dst_y=0,
573
599
  width=tile.width,
574
600
  height=tile.height,
575
- duration=0,
601
+ duration=duration_ms,
576
602
  )
577
603
  )
578
604
 
@@ -723,7 +749,7 @@ class MatrixLight(Light):
723
749
  async def set_effect(
724
750
  self,
725
751
  effect_type: FirmwareEffect,
726
- speed: int = 3000,
752
+ speed: float = 3.0,
727
753
  duration: int = 0,
728
754
  palette: list[HSBK] | None = None,
729
755
  sky_type: TileEffectSkyType = TileEffectSkyType.SUNRISE,
@@ -734,7 +760,7 @@ class MatrixLight(Light):
734
760
 
735
761
  Args:
736
762
  effect_type: Type of effect (OFF, MORPH, FLAME, SKY)
737
- speed: Effect speed in milliseconds (default: 3000)
763
+ speed: Effect speed in seconds (default: 3)
738
764
  duration: Total effect duration in nanoseconds (0 for infinite)
739
765
  palette: Color palette for the effect (max 16 colors)
740
766
  sky_type: Sky effect type (SUNRISE, SUNSET, CLOUDS)
@@ -751,7 +777,7 @@ class MatrixLight(Light):
751
777
  ... ]
752
778
  >>> await matrix.set_effect(
753
779
  ... effect_type=FirmwareEffect.MORPH,
754
- ... speed=5000,
780
+ ... speed=5.0,
755
781
  ... palette=rainbow,
756
782
  ... )
757
783
  """
@@ -761,11 +787,12 @@ class MatrixLight(Light):
761
787
  speed,
762
788
  self.label or self.serial,
763
789
  )
790
+ speed_ms = round(speed * 1000) if speed else 3000
764
791
 
765
792
  # Create and validate MatrixEffect
766
793
  effect = MatrixEffect(
767
794
  effect_type=effect_type,
768
- speed=speed,
795
+ speed=speed_ms,
769
796
  duration=duration,
770
797
  palette=palette,
771
798
  sky_type=sky_type,
@@ -843,9 +870,23 @@ class MatrixLight(Light):
843
870
  # Create canvas and populate with theme colors
844
871
  canvas = Canvas()
845
872
  for tile in tiles:
846
- canvas.add_points_for_tile(None, theme)
847
- canvas.shuffle_points()
848
- canvas.blur_by_distance()
873
+ canvas.add_points_for_tile((int(tile.user_x), int(tile.user_y)), theme)
874
+ canvas.shuffle_points()
875
+ canvas.blur_by_distance()
876
+
877
+ # Create tile canvas and fill in gaps for smooth interpolation
878
+ tile_canvas = Canvas()
879
+ for tile in tiles:
880
+ tile_canvas.fill_in_points(
881
+ canvas,
882
+ int(tile.user_x),
883
+ int(tile.user_y),
884
+ tile.width,
885
+ tile.height,
886
+ )
887
+
888
+ # Final blur for smooth gradients
889
+ tile_canvas.blur()
849
890
 
850
891
  # Check if light is on
851
892
  is_on = await self.get_power()
@@ -853,7 +894,10 @@ class MatrixLight(Light):
853
894
  # Apply colors to each tile
854
895
  for tile in tiles:
855
896
  # Extract tile colors from canvas as 1D list
856
- colors = canvas.points_for_tile(None, width=tile.width, height=tile.height)
897
+ tile_coords = (int(tile.user_x), int(tile.user_y))
898
+ colors = tile_canvas.points_for_tile(
899
+ tile_coords, width=tile.width, height=tile.height
900
+ )
857
901
 
858
902
  # Apply with appropriate timing
859
903
  if power_on and not is_on:
lifx/effects/base.py CHANGED
@@ -59,6 +59,16 @@ class LIFXEffect(ABC):
59
59
  self.conductor: Conductor | None = None
60
60
  self.participants: list[Light] = []
61
61
 
62
+ @property
63
+ @abstractmethod
64
+ def name(self) -> str:
65
+ """Return the name of the effect.
66
+
67
+ Returns:
68
+ The effect name as a string
69
+ """
70
+ raise NotImplementedError("Subclasses must implement name property")
71
+
62
72
  async def async_perform(self, participants: list[Light]) -> None:
63
73
  """Perform common setup and play the effect.
64
74
 
lifx/effects/colorloop.py CHANGED
@@ -127,6 +127,15 @@ class EffectColorloop(LIFXEffect):
127
127
  self._running = False
128
128
  self._stop_event = asyncio.Event()
129
129
 
130
+ @property
131
+ def name(self) -> str:
132
+ """Return the name of the effect.
133
+
134
+ Returns:
135
+ The effect name 'colorloop'
136
+ """
137
+ return "colorloop"
138
+
130
139
  async def async_play(self) -> None:
131
140
  """Execute the colorloop effect continuously."""
132
141
  self._running = True
lifx/effects/pulse.py CHANGED
@@ -136,6 +136,15 @@ class EffectPulse(LIFXEffect):
136
136
  if self.cycles < 1:
137
137
  raise ValueError(f"Cycles must be 1 or higher, got {self.cycles}")
138
138
 
139
+ @property
140
+ def name(self) -> str:
141
+ """Return the name of the effect.
142
+
143
+ Returns:
144
+ The effect name 'pulse'
145
+ """
146
+ return "pulse"
147
+
139
148
  async def async_play(self) -> None:
140
149
  """Execute the pulse effect on all participants."""
141
150
  # Determine colors for each light
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lifx-async
3
- Version: 4.3.1
3
+ Version: 4.3.3
4
4
  Summary: A modern, type-safe, async Python library for controlling LIFX lights
5
5
  Author-email: Avi Miller <me@dje.li>
6
6
  Maintainer-email: Avi Miller <me@dje.li>
@@ -9,15 +9,15 @@ lifx/devices/base.py,sha256=bxnFRkYWmOfih9wDALKpiepOzlEabtuNS21tZhPVk6U,40765
9
9
  lifx/devices/hev.py,sha256=2zZNYm3TFrL755B4cRPNdYtcDLZEQwGl_22112WsSZc,9504
10
10
  lifx/devices/infrared.py,sha256=TrCgJyEioIPlFumMmcSmuGYmRsSGlQ5Rllg6_9Wtg4Y,4248
11
11
  lifx/devices/light.py,sha256=9rL24fa44Y7QrRBDSQuG6xpWsaPbQTTm4ExvrDnYWHo,27572
12
- lifx/devices/matrix.py,sha256=aN5e57R7uLA2YqcQi_mMjh1grrPiivMnT13M7_VDA0Q,30852
12
+ lifx/devices/matrix.py,sha256=zj_AE2C0WfSjMZw8X7Y6KkG1PUr0ViLC7zvVrVnWMPI,32504
13
13
  lifx/devices/multizone.py,sha256=c-lXcp8c1Mhs8me6smGkqQFrOOxdoGjWrOO5HnAVooY,27209
14
14
  lifx/effects/__init__.py,sha256=4DF31yp7RJic5JoltMlz5dCtF5KQobU6NOUtLUKkVKE,1509
15
- lifx/effects/base.py,sha256=3tYbACeQVP1Ed2x_aVZGaH6S4Q6HOTCgNfCmDrj2Pxo,10032
16
- lifx/effects/colorloop.py,sha256=vIyXIxpL00MXSla8QJmFCq2zWsCilKTm9w2kMfXHTm8,15659
15
+ lifx/effects/base.py,sha256=YO0Hbg2VYHKPtfYnWxmrtzYoPGOi9BUXhn8HVFKv5IM,10283
16
+ lifx/effects/colorloop.py,sha256=kuuyENJS2irAN8vZAFsDa2guQdDbmmc4PJNiyZTfFPE,15840
17
17
  lifx/effects/conductor.py,sha256=0Aizn2gpo2kTqwSF4p9Qat8S4F53xwHJwVjOJONduKc,15036
18
18
  lifx/effects/const.py,sha256=03LfL8v9PtoUs6-2IR3aa6nkyA4Otdno51SFJtntC-U,795
19
19
  lifx/effects/models.py,sha256=MS5D-cxD0Ar8XhqbqKAc9q2sk38IP1vPkYwd8V7jCr8,2446
20
- lifx/effects/pulse.py,sha256=dEFeiHhi7PXDlRwQGuyAA15Jxcdlrk-HvnSq3u2bMOc,8548
20
+ lifx/effects/pulse.py,sha256=t5eyjfFWG1xT-RXKghRqHYJ9CG_50tPu4jsDapJZ2mw,8721
21
21
  lifx/effects/state_manager.py,sha256=iDfYowiCN5IJqcR1s-pM0mQEJpe-RDsMcOOSMmtPVDE,8983
22
22
  lifx/network/__init__.py,sha256=uSyA8r8qISG7qXUHbX8uk9A2E8rvDADgCcf94QIZ9so,499
23
23
  lifx/network/connection.py,sha256=dgkTptyFf_3Cfd2_UCh_M75lVmIHTdGuU92nzqQcsdY,37358
@@ -40,7 +40,7 @@ lifx/theme/canvas.py,sha256=4h7lgN8iu_OdchObGDgbxTqQLCb-FRKC-M-YCWef_i4,8048
40
40
  lifx/theme/generators.py,sha256=L0X6_iApLx6XDboGlYunaVsl6nvUCqMfn23VQmRkyCk,6125
41
41
  lifx/theme/library.py,sha256=tKlKZNqJp8lRGDnilWyDm_Qr1vCRGGwuvWVS82anNpQ,21326
42
42
  lifx/theme/theme.py,sha256=qMEx_8E41C0Cc6f083XHiAXEglTv4YlXW0UFsG1rQKg,5521
43
- lifx_async-4.3.1.dist-info/METADATA,sha256=miSo_De7eFOGsGNt00VmNoOYEbilXpocG9RCR_KPHMU,2609
44
- lifx_async-4.3.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
- lifx_async-4.3.1.dist-info/licenses/LICENSE,sha256=eBz48GRA3gSiWn3rYZAz2Ewp35snnhV9cSqkVBq7g3k,1832
46
- lifx_async-4.3.1.dist-info/RECORD,,
43
+ lifx_async-4.3.3.dist-info/METADATA,sha256=gI8tKKKOoM36x6oeh3w5d8YBgDxeUIXlwTybiRRr7XE,2609
44
+ lifx_async-4.3.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
45
+ lifx_async-4.3.3.dist-info/licenses/LICENSE,sha256=eBz48GRA3gSiWn3rYZAz2Ewp35snnhV9cSqkVBq7g3k,1832
46
+ lifx_async-4.3.3.dist-info/RECORD,,