__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 """