group.py
  1  # SPDX-FileCopyrightText: 2020 Kattni Rembor for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  """
  6  `adafruit_led_animation.group`
  7  ================================================================================
  8  
  9  Animation group helper for CircuitPython helper library for LED animations..
 10  
 11  
 12  * Author(s): Kattni Rembor
 13  
 14  Implementation Notes
 15  --------------------
 16  
 17  **Hardware:**
 18  
 19  * `Adafruit NeoPixels <https://www.adafruit.com/category/168>`_
 20  * `Adafruit DotStars <https://www.adafruit.com/category/885>`_
 21  
 22  **Software and Dependencies:**
 23  
 24  * Adafruit CircuitPython firmware for the supported boards:
 25    https://circuitpython.org/downloads
 26  
 27  """
 28  
 29  __version__ = "0.0.0-auto.0"
 30  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git"
 31  
 32  from adafruit_led_animation.animation import Animation
 33  
 34  
 35  class AnimationGroup:
 36      """
 37      AnimationGroup synchronizes multiple animations.  Allows for multiple animations to be kept in
 38      sync, whether or not the same animation or pixel object is in use.
 39  
 40      :param members: The animation objects or groups.
 41      :param bool sync: Synchronises when draw is called for all members of the group to the settings
 42                        of the first member of the group. Defaults to ``False``.
 43  
 44  
 45      Example:
 46  
 47          .. code-block::
 48  
 49              import board
 50              import neopixel
 51              from adafruit_circuitplayground import cp
 52              from adafruit_led_animation.animation.blink import Blink
 53              from adafruit_led_animation.animation.comet import Comet
 54              from adafruit_led_animation.animation.chase import Chase
 55              from adafruit_led_animation.group import AnimationGroup
 56              from adafruit_led_animation.sequence import AnimationSequence
 57  
 58              import adafruit_led_animation.color as color
 59  
 60              strip_pixels = neopixel.NeoPixel(board.A1, 30, brightness=0.5, auto_write=False)
 61              cp.pixels.brightness = 0.5
 62  
 63              animations = AnimationSequence(
 64                  # Synchronized to 0.5 seconds. Ignores the second animation setting of 3 seconds.
 65                  AnimationGroup(
 66                      Blink(cp.pixels, 0.5, color.CYAN),
 67                      Blink(strip_pixels, 3.0, color.AMBER),
 68                      sync=True,
 69                  ),
 70                  # Different speeds
 71                  AnimationGroup(
 72                      Comet(cp.pixels, 0.1, color.MAGENTA, tail_length=5),
 73                      Comet(strip_pixels, 0.01, color.MAGENTA, tail_length=15),
 74                  ),
 75                  # Sequential animations on the built-in NeoPixels then the NeoPixel strip
 76                  Chase(cp.pixels, 0.05, size=2, spacing=3, color=color.PURPLE),
 77                  Chase(strip_pixels, 0.05, size=2, spacing=3, color=color.PURPLE),
 78                  advance_interval=3.0,
 79                  auto_clear=True,
 80                  auto_reset=True,
 81              )
 82  
 83              while True:
 84                  animations.animate()
 85      """
 86  
 87      def __init__(self, *members, sync=False, name=None):
 88          if not members:
 89              raise ValueError("At least one member required in an AnimationGroup")
 90          self.draw_count = 0
 91          """Number of animation frames drawn."""
 92          self.cycle_count = 0
 93          """Number of animation cycles completed."""
 94          self.notify_cycles = 1
 95          """Number of cycles to trigger additional cycle_done notifications after"""
 96          self._members = list(members)
 97          self._sync = sync
 98          self._also_notify = []
 99          self.cycle_count = 0
100          self.name = name
101          if sync:
102              main = members[0]
103              main.peers = members[1:]
104  
105          # Catch cycle_complete on the last animation.
106          self._members[-1].add_cycle_complete_receiver(self._group_done)
107          self.on_cycle_complete_supported = self._members[-1].on_cycle_complete_supported
108  
109      def __str__(self):
110          return "<AnimationGroup %s: %s>" % (self.__class__.__name__, self.name)
111  
112      def _group_done(self, animation):  # pylint: disable=unused-argument
113          self.on_cycle_complete()
114  
115      def on_cycle_complete(self):
116          """
117          Called by some animations when they complete an animation cycle.
118          Animations that support cycle complete notifications will have X property set to False.
119          Override as needed.
120          """
121          self.cycle_count += 1
122          if self.cycle_count % self.notify_cycles == 0:
123              for callback in self._also_notify:
124                  callback(self)
125  
126      def add_cycle_complete_receiver(self, callback):
127          """
128          Adds an additional callback when the cycle completes.
129  
130          :param callback: Additional callback to trigger when a cycle completes.  The callback
131                           is passed the animation object instance.
132          """
133          self._also_notify.append(callback)
134  
135      def animate(self, show=True):
136          """
137          Call animate() from your code's main loop.  It will draw all of the animations
138          in the group.
139  
140          :return: True if any animation draw cycle was triggered, otherwise False.
141          """
142          if self._sync:
143              result = self._members[0].animate(show=False)
144              if result and show:
145                  last_strip = None
146                  for member in self._members:
147                      if isinstance(member, Animation):
148                          if last_strip != member.pixel_object:
149                              member.pixel_object.show()
150                              last_strip = member.pixel_object
151                      else:
152                          member.show()
153              return result
154  
155          return any(item.animate(show) for item in self._members)
156  
157      @property
158      def color(self):
159          """
160          Use this property to change the color of all members of the animation group.
161          """
162          return None
163  
164      @color.setter
165      def color(self, color):
166          for item in self._members:
167              item.color = color
168  
169      def fill(self, color):
170          """
171          Fills all pixel objects in the group with a color.
172          """
173          for item in self._members:
174              item.fill(color)
175  
176      def freeze(self):
177          """
178          Freeze all animations in the group.
179          """
180          for item in self._members:
181              item.freeze()
182  
183      def resume(self):
184          """
185          Resume all animations in the group.
186          """
187          for item in self._members:
188              item.resume()
189  
190      def reset(self):
191          """
192          Resets the animations in the group.
193          """
194          for item in self._members:
195              item.reset()
196  
197      def show(self):
198          """
199          Draws the current animation group members.
200          """
201          for item in self._members:
202              item.show()