__init__.py
  1  # SPDX-FileCopyrightText: 2020 Kattni Rembor for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  """
  6  `adafruit_led_animation.animation`
  7  ================================================================================
  8  
  9  Animation base class for CircuitPython helper library for LED animations.
 10  
 11  * Author(s): Kattni Rembor
 12  
 13  Implementation Notes
 14  --------------------
 15  
 16  **Hardware:**
 17  
 18  * `Adafruit NeoPixels <https://www.adafruit.com/category/168>`_
 19  * `Adafruit DotStars <https://www.adafruit.com/category/885>`_
 20  
 21  **Software and Dependencies:**
 22  
 23  * Adafruit CircuitPython firmware for the supported boards:
 24    https://circuitpython.org/downloads
 25  
 26  """
 27  
 28  __version__ = "0.0.0-auto.0"
 29  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git"
 30  
 31  from adafruit_led_animation import MS_PER_SECOND, monotonic_ms
 32  
 33  
 34  class Animation:
 35      # pylint: disable=too-many-instance-attributes
 36      """
 37      Base class for animations.
 38      """
 39      on_cycle_complete_supported = False
 40  
 41      # pylint: disable=too-many-arguments
 42      def __init__(self, pixel_object, speed, color, peers=None, paused=False, name=None):
 43          self.pixel_object = pixel_object
 44          self.pixel_object.auto_write = False
 45          self._peers = [self] + peers if peers is not None else [self]
 46          self._speed_ms = 0
 47          self._color = None
 48          self._paused = paused
 49          self._next_update = monotonic_ms()
 50          self._time_left_at_pause = 0
 51          self._also_notify = []
 52          self.speed = speed  # sets _speed_ms
 53          self.color = color  # Triggers _set_color
 54          self.name = name
 55          self.cycle_complete = False
 56          self.notify_cycles = 1
 57          """Number of cycles to trigger additional cycle_done notifications after"""
 58          self.draw_count = 0
 59          """Number of animation frames drawn."""
 60          self.cycle_count = 0
 61          """Number of animation cycles completed."""
 62  
 63      def __str__(self):
 64          return "<%s: %s>" % (self.__class__.__name__, self.name)
 65  
 66      def animate(self, show=True):
 67          """
 68          Call animate() from your code's main loop.  It will draw the animation draw() at intervals
 69          configured by the speed property (set from init).
 70  
 71          :param bool show: Whether to automatically call show on the pixel object when an animation
 72                            fires.  Default True.
 73          :return: True if the animation draw cycle was triggered, otherwise False.
 74          """
 75          if self._paused:
 76              return False
 77  
 78          now = monotonic_ms()
 79          if now < self._next_update:
 80              return False
 81  
 82          # Draw related animations together
 83          for anim in self._peers:
 84              anim.draw_count += 1
 85              anim.draw()
 86              anim.after_draw()
 87  
 88          if show:
 89              for anim in self._peers:
 90                  anim.show()
 91  
 92          # Note that the main animation cycle_complete flag is used, not the peer flag.
 93          for anim in self._peers:
 94              if anim.cycle_complete:
 95                  anim.cycle_complete = False
 96                  anim.on_cycle_complete()
 97  
 98          self._next_update = now + self._speed_ms
 99          return True
100  
101      def draw(self):
102          """
103          Animation subclasses must implement draw() to render the animation sequence.
104          Animations should not call show(), as animate() will do so, after after_draw().
105          Animations should set .cycle_done = True when an animation cycle is completed.
106          """
107          raise NotImplementedError()
108  
109      def after_draw(self):
110          """
111          Animation subclasses may implement after_draw() to do operations after the main draw()
112          is called.
113          """
114  
115      def show(self):
116          """
117          Displays the updated pixels.  Called during animates with changes.
118          """
119          self.pixel_object.show()
120  
121      @property
122      def peers(self):
123          """
124          Get the animation's peers.  Peers are drawn, then shown together.
125          """
126          return self._peers[1:]
127  
128      @peers.setter
129      def peers(self, peer_list):
130          """
131          Set the animation's peers.
132          :param list peer_list: List of peer animations.
133          """
134          if peer_list is not None:
135              self._peers = [self] + list(peer_list)
136  
137      def freeze(self):
138          """
139          Stops the animation until resumed.
140          """
141          self._paused = True
142          self._time_left_at_pause = max(0, monotonic_ms() - self._next_update)
143  
144      def resume(self):
145          """
146          Resumes the animation.
147          """
148          self._next_update = monotonic_ms() + self._time_left_at_pause
149          self._time_left_at_pause = 0
150          self._paused = False
151  
152      def fill(self, color):
153          """
154          Fills the pixel object with a color.
155          """
156          self.pixel_object.fill(color)
157          self.pixel_object.show()
158  
159      @property
160      def color(self):
161          """
162          The current color.
163          """
164          return self._color
165  
166      @color.setter
167      def color(self, color):
168          if self._color == color:
169              return
170          if isinstance(color, int):
171              color = (color >> 16 & 0xFF, color >> 8 & 0xFF, color & 0xFF)
172          self._set_color(color)
173  
174      def _set_color(self, color):
175          """
176          Called after the color is changed, which includes at initialization.
177          Override as needed.
178          """
179          self._color = color
180  
181      @property
182      def speed(self):
183          """
184          The animation speed in fractional seconds.
185          """
186          return self._speed_ms / MS_PER_SECOND
187  
188      @speed.setter
189      def speed(self, seconds):
190          self._speed_ms = int(seconds * MS_PER_SECOND)
191  
192      def on_cycle_complete(self):
193          """
194          Called by some animations when they complete an animation cycle.
195          Animations that support cycle complete notifications will have X property set to False.
196          Override as needed.
197          """
198          self.cycle_count += 1
199          if self.cycle_count % self.notify_cycles == 0:
200              for callback in self._also_notify:
201                  callback(self)
202  
203      def add_cycle_complete_receiver(self, callback):
204          """
205          Adds an additional callback when the cycle completes.
206  
207          :param callback: Additional callback to trigger when a cycle completes.  The callback
208                           is passed the animation object instance.
209          """
210          self._also_notify.append(callback)
211  
212      def reset(self):
213          """
214          Resets the animation sequence.
215          """