From faca5bc5b1bc3401de550080c6060fd259637e88 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Sat, 14 Dec 2019 16:02:21 -0500 Subject: [PATCH 01/46] Add more animations, add AggregatePixels. --- adafruit_led_animation/__init__.py | 9 +- adafruit_led_animation/animation.py | 225 +++++++++++++++++++++------- adafruit_led_animation/color.py | 18 +++ adafruit_led_animation/helper.py | 86 +++++++++++ 4 files changed, 281 insertions(+), 57 deletions(-) create mode 100644 adafruit_led_animation/helper.py diff --git a/adafruit_led_animation/__init__.py b/adafruit_led_animation/__init__.py index c508980..d39f136 100644 --- a/adafruit_led_animation/__init__.py +++ b/adafruit_led_animation/__init__.py @@ -2,4 +2,11 @@ Adafruit LED Animation library. """ -NANOS_PER_SECOND = 1000000000 +try: + from micropython import const +except ImportError: + def const(value): # pylint: disable=missing-docstring + return value + +NANOS_PER_SECOND = const(1000000000) +NANOS_PER_MS = const(1000000) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 242bd8f..0e82f1a 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -47,7 +47,7 @@ from math import ceil from . import NANOS_PER_SECOND -from .color import BLACK, RAINBOW +from .color import BLACK, RAINBOW, wheel try: from time import monotonic_ns except ImportError: @@ -59,7 +59,6 @@ def monotonic_ns(): """ return int(time.time() * NANOS_PER_SECOND) - __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" @@ -69,7 +68,7 @@ class Animation: Base class for animations. """ # pylint: disable=too-many-arguments - def __init__(self, pixel_object, speed, color, peers=None, paused=False): + def __init__(self, pixel_object, speed, color, peers=None, paused=False, name=None): self.pixel_object = pixel_object self.pixel_object.auto_write = False self.peers = peers if peers else [] @@ -80,6 +79,11 @@ def __init__(self, pixel_object, speed, color, peers=None, paused=False): self._time_left_at_pause = 0 self.speed = speed # sets _speed_ns self.color = color # Triggers _recompute_color + self.done_cycle_handler = None + self.name = name + + def __str__(self): + return "" % (self.__class__.__name__, self.name) def animate(self): """ @@ -173,6 +177,14 @@ def _recompute_color(self, color): Override as needed. """ + def _cycle_done(self): + """ + Called by some animations when they complete an animation cycle. + Calls done_cycle_handler if one is set. + """ + if self.done_cycle_handler: + self.done_cycle_handler(self) # pylint: disable=not-callable + class ColorCycle(Animation): """ @@ -183,9 +195,9 @@ class ColorCycle(Animation): :param colors: A list of colors to cycle through in ``(r, g, b)`` tuple, or ``0x000000`` hex format. Defaults to a rainbow color cycle. """ - def __init__(self, pixel_object, speed, colors=RAINBOW): + def __init__(self, pixel_object, speed, colors=RAINBOW, name=None): self.colors = colors - super(ColorCycle, self).__init__(pixel_object, speed, colors[0]) + super(ColorCycle, self).__init__(pixel_object, speed, colors[0], name=name) self._generator = self._color_generator() def draw(self): @@ -199,6 +211,8 @@ def _color_generator(self): self._color = self.colors[index] yield index = (index + 1) % len(self.colors) + if index == len(self.colors): + self._cycle_done() class Blink(ColorCycle): @@ -209,8 +223,8 @@ class Blink(ColorCycle): :param int speed: Animation speed in seconds, e.g. ``0.1``. :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. """ - def __init__(self, pixel_object, speed, color): - super(Blink, self).__init__(pixel_object, speed, [color, BLACK]) + def __init__(self, pixel_object, speed, color, name=None): + super(Blink, self).__init__(pixel_object, speed, [color, BLACK], name=name) def _recompute_color(self, color): self.colors = [color, BLACK] @@ -223,12 +237,15 @@ class Solid(ColorCycle): :param pixel_object: The initialised LED object. :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. """ - def __init__(self, pixel_object, color): - super(Solid, self).__init__(pixel_object, speed=1, colors=[color]) + def __init__(self, pixel_object, color, name=None): + super(Solid, self).__init__(pixel_object, speed=1, colors=[color], name=name) def _recompute_color(self, color): self.colors = [color] + def _cycle_done(self): + pass + class Comet(Animation): """ @@ -244,7 +261,8 @@ class Comet(Animation): :param bool bounce: Comet will bounce back and forth. Defaults to ``True``. """ # pylint: disable=too-many-arguments - def __init__(self, pixel_object, speed, color, tail_length=10, reverse=False, bounce=False): + def __init__(self, pixel_object, speed, color, tail_length=10, reverse=False, bounce=False, + name=None): self._tail_length = tail_length + 1 self._color_step = 0.9 / tail_length self._color_offset = 0.1 @@ -252,19 +270,21 @@ def __init__(self, pixel_object, speed, color, tail_length=10, reverse=False, bo self._reverse_comet_colors = None self.reverse = reverse self.bounce = bounce - # Super is called late because it needs ._color to be initialized. - super(Comet, self).__init__(pixel_object, speed, color) - # _recompute_color needs calling before creating the generator, so setup the generator - # afterwards + self._computed_color = color self._generator = self._comet_generator() + super(Comet, self).__init__(pixel_object, speed, color, name=name) def _recompute_color(self, color): + pass + + def __recompute_color(self, color): self._comet_colors = [BLACK] + [ [int(color[rgb] * ((n * self._color_step) + self._color_offset)) for rgb in range(len(color)) ] for n in range(self._tail_length - 1) ] self._reverse_comet_colors = list(reversed(self._comet_colors)) + self._computed_color = color def _get_range(self, num_pixels): if self.reverse: @@ -273,7 +293,10 @@ def _get_range(self, num_pixels): def _comet_generator(self): num_pixels = len(self.pixel_object) + cycle_passes = 0 while True: + if self._color != self._computed_color or not self._comet_colors: + self.__recompute_color(self._color) colors = self._reverse_comet_colors if self.reverse else self._comet_colors for start in self._get_range(num_pixels): @@ -288,8 +311,12 @@ def _comet_generator(self): self.pixel_object[start:start + end] = colors[0:end] self.show() yield + cycle_passes += 1 if self.bounce: self.reverse = not self.reverse + if not self.bounce or cycle_passes == 2: + self._cycle_done() + cycle_passes = 0 def draw(self): next(self._generator) @@ -303,12 +330,12 @@ class Sparkle(Animation): :param int speed: Animation speed in seconds, e.g. ``0.1``. :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. """ - def __init__(self, pixel_object, speed, color): + def __init__(self, pixel_object, speed, color, name=None): if len(pixel_object) < 2: raise ValueError("Sparkle needs at least 2 pixels") self._half_color = None self._dim_color = None - super(Sparkle, self).__init__(pixel_object, speed, color) + super(Sparkle, self).__init__(pixel_object, speed, color, name=name) def _recompute_color(self, color): half_color = tuple(color[rgb] // 4 for rgb in range(len(color))) @@ -343,29 +370,80 @@ class Pulse(Animation): """ # pylint: disable=too-many-arguments - def __init__(self, pixel_object, speed, color, period=5, max_intensity=1, min_intensity=0): - self.max_intensity = max_intensity - self.min_intensity = min_intensity - self._period = period - self._intensity_delta = max_intensity - min_intensity - self._half_period = period / 2 - self._position_factor = 1 / self._half_period - self._bpp = len(pixel_object[0]) - self._last_update = monotonic_ns() - self._cycle_position = 0 - super(Pulse, self).__init__(pixel_object, speed, color) + def __init__(self, pixel_object, speed, color, period=5, name=None): + super(Pulse, self).__init__(pixel_object, speed, color, name=name) + self._generator = self._pulse_generator(period) + + def _pulse_generator(self, period): + period = int(period * NANOS_PER_SECOND) + white = len(self.pixel_object[0]) > 3 + half_period = period // 2 + period = period + + last_update = monotonic_ns() + cycle_position = 0 + last_pos = 0 + while True: + fill_color = list(self.color) + now = monotonic_ns() + time_since_last_draw = now - last_update + last_update = now + pos = cycle_position = (cycle_position + time_since_last_draw) % period + if pos < last_pos: + self._cycle_done() + last_pos = pos + if pos > half_period: + pos = period - pos + intensity = (pos / half_period) + if white: + fill_color[3] = int(fill_color[3] * intensity) + fill_color[0] = int(fill_color[0] * intensity) + fill_color[1] = int(fill_color[1] * intensity) + fill_color[2] = int(fill_color[2] * intensity) + self.fill(fill_color) + self.show() + yield def draw(self): - now = monotonic_ns() - time_since_last_draw = (now - self._last_update) / NANOS_PER_SECOND - self._last_update = now - pos = self._cycle_position = (self._cycle_position + time_since_last_draw) % self._period - if pos > self._half_period: - pos = self._period - pos - intensity = self.min_intensity + (pos * self._intensity_delta * self._position_factor) - color = [int(self.color[n] * intensity) for n in range(self._bpp)] - self.fill(color) - self.show() + next(self._generator) + + +class ColorWheel(Animation): + """ + The classic adafruit colorwheel. + + :param pixel_object: The initialised LED object. + :param int speed: Animation refresh rate in seconds, e.g. ``0.1``. + :param period: Period to cycle the colorwheel over. Default 5. + """ + + # pylint: disable=too-many-arguments + def __init__(self, pixel_object, speed, period=5, name=None): + super(ColorWheel, self).__init__(pixel_object, speed, BLACK, name=name) + self._generator = self._wheel_generator(period) + + def _wheel_generator(self, period): + period = int(period * NANOS_PER_SECOND) + + last_update = monotonic_ns() + cycle_position = 0 + last_pos = 0 + while True: + now = monotonic_ns() + time_since_last_draw = now - last_update + last_update = now + pos = cycle_position = (cycle_position + time_since_last_draw) % period + if pos < last_pos: + self._cycle_done() + last_pos = pos + wheel_index = int((pos / period) * 256) + self.pixel_object[:] = [wheel((i + wheel_index) % 255) + for i, _ in enumerate(self.pixel_object)] + self.show() + yield + + def draw(self): + next(self._generator) class Chase(Animation): @@ -381,7 +459,7 @@ class Chase(Animation): """ # pylint: disable=too-many-arguments - def __init__(self, pixel_object, speed, color, size=2, spacing=3, reverse=False): + def __init__(self, pixel_object, speed, color, size=2, spacing=3, reverse=False, name=None): self._size = size self._spacing = spacing self._repeat_width = size + spacing @@ -390,7 +468,7 @@ def __init__(self, pixel_object, speed, color, size=2, spacing=3, reverse=False) self._direction = 1 if not reverse else -1 self._reverse = reverse self._n = 0 - super(Chase, self).__init__(pixel_object, speed, color) + super(Chase, self).__init__(pixel_object, speed, color, name=name) @property def reverse(self): @@ -410,7 +488,10 @@ def draw(self): n = (self._n + i) % self._repeat_width num = len(self.pixel_object[n::self._repeat_width]) self.pixel_object[n::self._repeat_width] = [self.group_color(n) for n in range(num)] - self._n = (self._n + self._direction) % self._repeat_width + _n = (self._n + self._direction) % self._repeat_width + if _n < self._n: + self._cycle_done() + self._n = _n self.show() def group_color(self, n): # pylint: disable=unused-argument @@ -430,6 +511,7 @@ class AnimationSequence: :param members: The animation objects or groups. :param int advance_interval: Time in seconds between animations if cycling automatically. Defaults to ``None``. + :param random_order: Switch to a different animation each advance. .. code-block:: python @@ -449,7 +531,7 @@ class AnimationSequence: while True: animations.animate() """ - def __init__(self, *members, advance_interval=None, auto_clear=False): + def __init__(self, *members, advance_interval=None, auto_clear=False, random_order=False): self._members = members self._advance_interval = advance_interval * NANOS_PER_SECOND if advance_interval else None self._last_advance = monotonic_ns() @@ -458,6 +540,15 @@ def __init__(self, *members, advance_interval=None, auto_clear=False): self.clear_color = BLACK self._paused = False self._paused_at = 0 + self._random = random_order + self._color = None + for item in self._members: + item.done_cycle_handler = self.done_handler + + def done_handler(self, animation): + """ + Called when an animation sequence is done. + """ def _auto_advance(self): if not self._advance_interval: @@ -465,15 +556,32 @@ def _auto_advance(self): now = monotonic_ns() if now - self._last_advance > self._advance_interval: self._last_advance = now - self.next() + if self._random: + self.random() + else: + self.next() - def next(self): + def activate(self, index): """ - Jump to the next animation. + Activates a specific animation. """ + self._current = index if self._auto_clear: self.fill(self.clear_color) - self._current = (self._current + 1) % len(self._members) + if self._color: + self.current_animation.color = self._color + + def next(self): + """ + Jump to the next animation. + """ + self.activate((self._current + 1) % len(self._members)) + + def random(self): + """ + Jump to a random animation. + """ + self.activate(random.randint(0, len(self._members)-1)) def animate(self): """ @@ -496,14 +604,14 @@ def current_animation(self): @property def color(self): """ - Use this property to change the color of all members of the animation. + Use this property to change the color of all animations in the sequence. """ - return None + return self._color @color.setter def color(self, color): - for item in self._members: - item.color = color + self._color = color + self.current_animation.color = color def fill(self, color): """ @@ -534,6 +642,12 @@ def resume(self): self._paused_at = 0 self.current_animation.resume() + def animation_done(self, animation): + """ + Called by some animations when they finish a sequence. + """ + pass + class AnimationGroup: """ @@ -564,10 +678,6 @@ def animate(self): return any([item.animate() for item in self._members]) - def _for_all(self, method, *args, **kwargs): - for item in self._members: - getattr(item, method)(*args, **kwargs) - @property def color(self): """ @@ -584,16 +694,19 @@ def fill(self, color): """ Fills all pixel objects in the group with a color. """ - self._for_all('fill', color) + for item in self._members: + item.fill(color) def freeze(self): """ Freeze all animations in the group. """ - self._for_all('freeze') + for item in self._members: + item.freeze() def resume(self): """ Resume all animations in the group. """ - self._for_all('resume') + for item in self._members: + item.resume() diff --git a/adafruit_led_animation/color.py b/adafruit_led_animation/color.py index b34513c..116168e 100644 --- a/adafruit_led_animation/color.py +++ b/adafruit_led_animation/color.py @@ -21,3 +21,21 @@ AMBER = (255, 100, 0) RAINBOW = (RED, ORANGE, YELLOW, GREEN, BLUE, PURPLE) + + +try: + from _pixelbuf import wheel # pylint: disable=unused-import +except ImportError: + # Ensure we have a wheel if not built in + def wheel(pos): # pylint: disable=missing-docstring + # Input a value 0 to 255 to get a color value. + # The colours are a transition r - g - b - back to r. + if pos < 0 or pos > 255: + return 0, 0, 0 + if pos < 85: + return int(255 - pos * 3), int(pos * 3), 0 + if pos < 170: + pos -= 85 + return 0, int(255 - pos * 3), int(pos * 3) + pos -= 170 + return int(pos * 3), 0, int(255 - (pos * 3)) diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py new file mode 100644 index 0000000..ab9eebe --- /dev/null +++ b/adafruit_led_animation/helper.py @@ -0,0 +1,86 @@ +""" +Helper classes for making complex animations. +""" +import math + + +class AggregatePixels: + """ + AggregatePixels lets you treat ranges of pixels as single pixels for animation purposes. + """ + def __init__(self, strip, pixel_ranges): + self._pixels = strip + self._ranges = list(sorted(pixel_ranges)) + self.n = len(self._ranges) + + def __repr__(self): + return "[" + ", ".join([str(x) for x in self]) + "]" + + def __setitem__(self, index, val): + if isinstance(index, slice): + start, stop, step = index.indices(len(self._ranges)) + length = stop - start + if step != 0: + length = math.ceil(length / step) + if len(val) != length: + raise ValueError("Slice and input sequence size do not match.") + for val_i, in_i in enumerate(range(start, stop, step)): + range_start, range_stop = self._ranges[in_i] + self._pixels[range_start:range_stop] = [val[val_i]] * (range_stop - range_start) + else: + range_start, range_stop = self._ranges[index] + self._pixels[range_start:range_stop] = [val] * (range_stop - range_start) + + if self._pixels.auto_write: + self.show() + + def __getitem__(self, index): + if isinstance(index, slice): + out = [] + for in_i in range(*index.indices(len(self._ranges))): + out.append(self._pixels[self._ranges[in_i][0]]) + return out + if index < 0: + index += len(self) + if index >= self.n or index < 0: + raise IndexError + return self._pixels[self._ranges[index][0]] + + def __len__(self): + return len(self._ranges) + + @property + def brightness(self): + """ + brightness from the underlying strip. + """ + return self._pixels.brightness + + @brightness.setter + def brightness(self, brightness): + # pylint: disable=attribute-defined-outside-init + self._pixels.brightness = min(max(brightness, 0.0), 1.0) + + def fill(self, color): + """ + Fill the used pixel ranges with color. + """ + for start, stop in self._ranges: + self._pixels[start:stop] = [color] * (stop - start) + + def show(self): + """ + Shows the pixels on the underlying strip. + """ + self._pixels.show() + + @property + def auto_write(self): + """ + auto_write from the underlying strip. + """ + return self._pixels.auto_write + + @auto_write.setter + def auto_write(self, value): + self._pixels.auto_write = value From 2a67891a6edc1a2658cb77f515781979a36a7b44 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Mon, 16 Dec 2019 14:14:01 -0500 Subject: [PATCH 02/46] fix lint --- adafruit_led_animation/animation.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 0e82f1a..be7f0b5 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -378,7 +378,6 @@ def _pulse_generator(self, period): period = int(period * NANOS_PER_SECOND) white = len(self.pixel_object[0]) > 3 half_period = period // 2 - period = period last_update = monotonic_ns() cycle_position = 0 @@ -646,7 +645,6 @@ def animation_done(self, animation): """ Called by some animations when they finish a sequence. """ - pass class AnimationGroup: From 3297a7fd869b81251cfe2b547907083b8b4f26ad Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Mon, 16 Dec 2019 14:20:20 -0500 Subject: [PATCH 03/46] make AggregatePixels be able to be individual pixels --- adafruit_led_animation/helper.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index ab9eebe..1511904 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -8,14 +8,23 @@ class AggregatePixels: """ AggregatePixels lets you treat ranges of pixels as single pixels for animation purposes. """ - def __init__(self, strip, pixel_ranges): + def __init__(self, strip, pixel_ranges, individual_pixels=False): self._pixels = strip - self._ranges = list(sorted(pixel_ranges)) + self._ranges = pixel_ranges self.n = len(self._ranges) + self._individual_pixels = individual_pixels def __repr__(self): return "[" + ", ".join([str(x) for x in self]) + "]" + def _set_pixels(self, index, val): + if self._individual_pixels: + for pixel in self._ranges[index]: + self._pixels[pixel] = [val] + else: + range_start, range_stop = self._ranges[index] + self._pixels[range_start:range_stop] = [val] * (range_stop - range_start) + def __setitem__(self, index, val): if isinstance(index, slice): start, stop, step = index.indices(len(self._ranges)) @@ -25,11 +34,9 @@ def __setitem__(self, index, val): if len(val) != length: raise ValueError("Slice and input sequence size do not match.") for val_i, in_i in enumerate(range(start, stop, step)): - range_start, range_stop = self._ranges[in_i] - self._pixels[range_start:range_stop] = [val[val_i]] * (range_stop - range_start) + self._set_pixels(in_i, val[val_i]) else: - range_start, range_stop = self._ranges[index] - self._pixels[range_start:range_stop] = [val] * (range_stop - range_start) + self._set_pixels(index, val) if self._pixels.auto_write: self.show() From f1b1fc76a4998342fb06e6ef13cf67bdb4cfcb79 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Mon, 16 Dec 2019 14:44:58 -0500 Subject: [PATCH 04/46] fixes to individual pixel aggregates --- adafruit_led_animation/helper.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 1511904..cbe6efc 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -20,7 +20,7 @@ def __repr__(self): def _set_pixels(self, index, val): if self._individual_pixels: for pixel in self._ranges[index]: - self._pixels[pixel] = [val] + self._pixels[pixel] = val else: range_start, range_stop = self._ranges[index] self._pixels[range_start:range_stop] = [val] * (range_stop - range_start) @@ -72,8 +72,13 @@ def fill(self, color): """ Fill the used pixel ranges with color. """ - for start, stop in self._ranges: - self._pixels[start:stop] = [color] * (stop - start) + if self._individual_pixels: + for pixels in self._ranges: + for pixel in pixels: + self._pixels[pixel] = color + else: + for start, stop in self._ranges: + self._pixels[start:stop] = [color] * (stop - start) def show(self): """ From 437c4476d06df38386cb4d445d5882007357f6e8 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Mon, 16 Dec 2019 14:49:54 -0500 Subject: [PATCH 05/46] Add more sparkles --- adafruit_led_animation/animation.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index be7f0b5..a275229 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -329,12 +329,15 @@ class Sparkle(Animation): :param pixel_object: The initialised LED object. :param int speed: Animation speed in seconds, e.g. ``0.1``. :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. + :param num_sparkles: Number of sparkles to generate per animation cycle. """ - def __init__(self, pixel_object, speed, color, name=None): + # pylint: disable=too-many-arguments + def __init__(self, pixel_object, speed, color, num_sparkles=1, name=None): if len(pixel_object) < 2: raise ValueError("Sparkle needs at least 2 pixels") self._half_color = None self._dim_color = None + self._num_sparkles = num_sparkles super(Sparkle, self).__init__(pixel_object, speed, color, name=name) def _recompute_color(self, color): @@ -349,11 +352,14 @@ def _recompute_color(self, color): self._dim_color = dim_color def draw(self): - pixel = random.randint(0, (len(self.pixel_object) - 2)) - self.pixel_object[pixel] = self._color + pixels = [random.randint(0, (len(self.pixel_object) - 2)) + for n in range(self._num_sparkles)] + for pixel in pixels: + self.pixel_object[pixel] = self._color self.show() - self.pixel_object[pixel] = self._half_color - self.pixel_object[pixel + 1] = self._dim_color + for pixel in pixels: + self.pixel_object[pixel] = self._half_color + self.pixel_object[pixel + 1] = self._dim_color self.show() From 1753ee30b15f92134cfeb85658f450a882162e08 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Mon, 16 Dec 2019 15:16:55 -0500 Subject: [PATCH 06/46] allow switching by name, fix random order --- adafruit_led_animation/animation.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index a275229..12ed201 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -546,6 +546,8 @@ def __init__(self, *members, advance_interval=None, auto_clear=False, random_ord self._paused = False self._paused_at = 0 self._random = random_order + if random_order: + self._current = random.randint(0, len(self._members) - 1) self._color = None for item in self._members: item.done_cycle_handler = self.done_handler @@ -570,7 +572,10 @@ def activate(self, index): """ Activates a specific animation. """ - self._current = index + if isinstance(index, str): + self._current = [member.name for member in self._members].index(index) + else: + self._current = index if self._auto_clear: self.fill(self.clear_color) if self._color: From f3a6eab3c91c11a34de50179418a9954fe1dddb5 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Mon, 16 Dec 2019 15:28:20 -0500 Subject: [PATCH 07/46] Add SubsetPixels that lets you manipulate a subset of the pixels as if they were a strip. --- adafruit_led_animation/helper.py | 71 ++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index cbe6efc..16756e0 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -96,3 +96,74 @@ def auto_write(self): @auto_write.setter def auto_write(self, value): self._pixels.auto_write = value + + +class SubsetPixels: + """ + SubsetPixels lets you work with a subset of a pixel object. + """ + def __init__(self, strip, start, end): + self._pixels = strip + self._start = start + self._end = end + self.n = self._end - self._start + + def __repr__(self): + return "[" + ", ".join([str(x) for x in self]) + "]" + + def __setitem__(self, index, val): + if isinstance(index, slice): + start, stop, step = index.indices(self.n) + self._pixels[start + self._start:stop + self._start:step] = val + else: + self._pixels[index + self._start] = val + + if self._pixels.auto_write: + self.show() + + def __getitem__(self, index): + if isinstance(index, slice): + start, stop, step = index.indices(self.n) + return self._pixels[start + self._start:stop + self._start:step] + if index < 0: + index += len(self) + if index >= self.n or index < 0: + raise IndexError + return self._pixels[index] + + def __len__(self): + return self.n + + @property + def brightness(self): + """ + brightness from the underlying strip. + """ + return self._pixels.brightness + + @brightness.setter + def brightness(self, brightness): + self._pixels.brightness = min(max(brightness, 0.0), 1.0) + + def fill(self, color): + """ + Fill the used pixel ranges with color. + """ + self._pixels[self._start:self._end] = [color] * (self.n) + + def show(self): + """ + Shows the pixels on the underlying strip. + """ + self._pixels.show() + + @property + def auto_write(self): + """ + auto_write from the underlying strip. + """ + return self._pixels.auto_write + + @auto_write.setter + def auto_write(self, value): + self._pixels.auto_write = value From 1ee581d609e51c75a57b563f6d6dba9f391140a4 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Sat, 21 Dec 2019 16:19:43 -0500 Subject: [PATCH 08/46] more features to make coordinating animations easier --- adafruit_led_animation/animation.py | 89 +++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 12ed201..898634b 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -185,6 +185,11 @@ def _cycle_done(self): if self.done_cycle_handler: self.done_cycle_handler(self) # pylint: disable=not-callable + def reset(self): + """ + Resets the animation sequence. + """ + class ColorCycle(Animation): """ @@ -214,6 +219,12 @@ def _color_generator(self): if index == len(self.colors): self._cycle_done() + def reset(self): + """ + Resets to the first color. + """ + self._generator = self._color_generator() + class Blink(ColorCycle): """ @@ -321,6 +332,12 @@ def _comet_generator(self): def draw(self): next(self._generator) + def reset(self): + """ + Resets to the first color. + """ + self._generator = self._comet_generator() + class Sparkle(Animation): """ @@ -378,6 +395,7 @@ class Pulse(Animation): # pylint: disable=too-many-arguments def __init__(self, pixel_object, speed, color, period=5, name=None): super(Pulse, self).__init__(pixel_object, speed, color, name=name) + self._period = period self._generator = self._pulse_generator(period) def _pulse_generator(self, period): @@ -412,6 +430,12 @@ def _pulse_generator(self, period): def draw(self): next(self._generator) + def reset(self): + """ + Resets the animation. + """ + self._generator = self._pulse_generator(self._period) + class ColorWheel(Animation): """ @@ -450,6 +474,12 @@ def _wheel_generator(self, period): def draw(self): next(self._generator) + def reset(self): + """ + Resets the animation. + """ + self._generator = self._wheel_generator(period) + class Chase(Animation): """ @@ -473,6 +503,14 @@ def __init__(self, pixel_object, speed, color, size=2, spacing=3, reverse=False, self._direction = 1 if not reverse else -1 self._reverse = reverse self._n = 0 + + def resetter(): + self._n = 0 + self._reverse = reverse + self._direction = 1 if not reverse else -1 + + self._reset = resetter + super(Chase, self).__init__(pixel_object, speed, color, name=name) @property @@ -507,6 +545,12 @@ def group_color(self, n): # pylint: disable=unused-argument """ return self.color + def reset(self): + """ + Reset the animation. + """ + self._reset() + class AnimationSequence: """ @@ -549,6 +593,7 @@ def __init__(self, *members, advance_interval=None, auto_clear=False, random_ord if random_order: self._current = random.randint(0, len(self._members) - 1) self._color = None + self.done_cycle_handler = None for item in self._members: item.done_cycle_handler = self.done_handler @@ -585,7 +630,10 @@ def next(self): """ Jump to the next animation. """ + current = self._current self.activate((self._current + 1) % len(self._members)) + if current > self._current: + self._cycle_done() def random(self): """ @@ -657,6 +705,20 @@ def animation_done(self, animation): Called by some animations when they finish a sequence. """ + def _cycle_done(self): + """ + Called when the (first) member animation cycles. + Calls done_cycle_handler if one is set. + """ + if self.done_cycle_handler: + self.done_cycle_handler(self) # pylint: disable=not-callable + + def reset(self): + """ + Resets the current animation. + """ + self.current_animation.reset() + class AnimationGroup: """ @@ -674,6 +736,11 @@ def __init__(self, *members, sync=False): if sync: main = members[0] main.peers = members[1:] + # Register the done handler on the last animation. + self.done_cycle_handler = None + if not self._members: + return + self._members[-1].done_cycle_handler = self.done_handler def animate(self): """ @@ -719,3 +786,25 @@ def resume(self): """ for item in self._members: item.resume() + + def done_handler(self, animation): + """ + Called by some animations when they complete a cycle. For an AnimationGroup this is the + first member of the group, if any. + """ + self._cycle_done() + + def _cycle_done(self): + """ + Called when the (first) member animation cycles. + Calls done_cycle_handler if one is set. + """ + if self.done_cycle_handler: + self.done_cycle_handler(self) # pylint: disable=not-callable + + def reset(self): + """ + Resets the animations in the group. + """ + for item in self._members: + item.reset() From bad8defb8a276b018e9caee3338467cbcb636108 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Wed, 1 Jan 2020 17:48:24 -0500 Subject: [PATCH 09/46] fix for dotstars --- adafruit_led_animation/animation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 898634b..dc3522a 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -400,7 +400,7 @@ def __init__(self, pixel_object, speed, color, period=5, name=None): def _pulse_generator(self, period): period = int(period * NANOS_PER_SECOND) - white = len(self.pixel_object[0]) > 3 + white = len(self.pixel_object[0]) > 3 and type(self.pixel_object[0][-1]) is not float half_period = period // 2 last_update = monotonic_ns() From ed7ababf00fae9bff05b64c39ff0aa8d8970f975 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Sat, 4 Jan 2020 00:09:00 -0500 Subject: [PATCH 10/46] update cookiecutter and address lint --- CODE_OF_CONDUCT.md | 10 +-- LICENSE | 1 + README.rst | 93 ++++++++++++---------------- adafruit_led_animation/animation.py | 41 ++++++------ docs/api.rst | 4 +- docs/conf.py | 19 +++--- docs/index.rst | 8 +-- examples/led_animation_simpletest.py | 23 +++---- setup.py | 60 ++++++++++++++++++ 9 files changed, 147 insertions(+), 112 deletions(-) create mode 100644 setup.py diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index a9b258d..7ca3a1d 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -40,8 +40,8 @@ Examples of unacceptable behavior by participants include: * Other conduct which could reasonably be considered inappropriate The goal of the standards and moderation guidelines outlined here is to build -and maintain a respectful community. We ask that you don’t just aim to be -"technically unimpeachable", but rather try to be your best self. +and maintain a respectful community. We ask that you don’t just aim to be +"technically unimpeachable", but rather try to be your best self. We value many things beyond technical expertise, including collaboration and supporting others within our community. Providing a positive experience for @@ -72,7 +72,7 @@ You may report in the following ways: In any situation, you may send an email to . On the Adafruit Discord, you may send an open message from any channel -to all Community Helpers by tagging @community helpers. You may also send an +to all Community Helpers by tagging @community moderators. You may also send an open message from any channel, or a direct message to @kattni#1507, @tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or @Andon#8175. @@ -83,7 +83,7 @@ In situations on Discord where the issue is particularly egregious, possibly illegal, requires immediate action, or violates the Discord terms of service, you should also report the message directly to Discord. -These are the steps for upholding our community’s standards of conduct. +These are the steps for upholding our community’s standards of conduct. 1. Any member of the community may report any situation that violates the Adafruit Community Code of Conduct. All reports will be reviewed and @@ -124,4 +124,4 @@ For other projects adopting the Adafruit Community Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your -own moderation policy so as to avoid confusion. \ No newline at end of file +own moderation policy so as to avoid confusion. diff --git a/LICENSE b/LICENSE index f1ccab1..3367ac5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ The MIT License (MIT) Copyright (c) 2017 Adam Patt +Copyright (c) 2019-2020 Roy Hooper, Kattni Rembor Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.rst b/README.rst index 13a9f67..c8000fa 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,8 @@ Introduction ============ -.. image:: https://readthedocs.org/projects/adafruit_circuitpython_led_animation/badge/?version=latest - :target: https://adafruit_circuitpython_led_animation.readthedocs.io/ +.. image:: https://readthedocs.org/projects/adafruit-circuitpython-led_animation/badge/?version=latest + :target: https://circuitpython.readthedocs.io/projects/led_animation/en/latest/ :alt: Documentation Status .. image:: https://img.shields.io/discord/327254708534116352.svg @@ -13,7 +13,8 @@ Introduction :target: https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation/actions :alt: Build Status -Perform a variety of LED animation tasks +A library to easily generate LED animations + Dependencies ============= @@ -23,76 +24,60 @@ This driver depends on: Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading -`the Adafruit library and driver bundle `_. - -Usage Example -============= - -.. code-block:: python - - import adafruit_dotstar as dotstar - import board - from led_animation import color - # setup the pixel - dot = dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1, brightness=.2) - # set the color by name - dot[0] = color.GOLD - # show the pixel - dot.show() +`the Adafruit library and driver bundle `_. -Contributing -============ +Installing from PyPI +===================== +.. note:: This library is not available on PyPI yet. Install documentation is included + as a standard element. Stay tuned for PyPI availability! -Contributions are welcome! Please read our `Code of Conduct -`_ -before contributing to help this project stay welcoming. +On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from +PyPI `_. To install for current user: -Building locally -================ +.. code-block:: shell -Zip release files ------------------ + pip3 install adafruit-circuitpython-led-animation -To build this library locally you'll need to install the -`circuitpython-build-tools `_ package. +To install system-wide (this may be required in some cases): .. code-block:: shell - python3 -m venv .env - source .env/bin/activate - pip install circuitpython-build-tools + sudo pip3 install adafruit-circuitpython-led-animation -Once installed, make sure you are in the virtual environment: +To install in a virtual environment in your current project: .. code-block:: shell + mkdir project-name && cd project-name + python3 -m venv .env source .env/bin/activate + pip3 install adafruit-circuitpython-led-animation -Then run the build: - -.. code-block:: shell - - circuitpython-build-bundles --filename_prefix circuitpython-led_animation --library_location . +Usage Example +============= -Sphinx documentation ------------------------ +.. code-block:: python -Sphinx is used to build the documentation based on rST files and comments in the code. First, -install dependencies (feel free to reuse the virtual environment from above): + from adafruit_led_animation.animation import Comet, AnimationSequence, Chase + import neopixel + import board -.. code-block:: shell + pixels = neopixel.NeoPixel(board.D6, 32, brightness=0.2, auto_write=False) + comet = Comet(pixels, speed=0.01, color=PURPLE, tail_length=10, bounce=True) + chase = Chase(pixels, speed=0.1, size=3, spacing=6, color=WHITE) + animations = AnimationSequence(comet, chase, advance_interval=15) - python3 -m venv .env - source .env/bin/activate - pip install Sphinx sphinx-rtd-theme + while True: + animations.animate() -Now, once you have the virtual environment activated: +Contributing +============ -.. code-block:: shell +Contributions are welcome! Please read our `Code of Conduct +`_ +before contributing to help this project stay welcoming. - cd docs - sphinx-build -E -W -b html . _build/html +Documentation +============= -This will output the documentation to ``docs/_build/html``. Open the index.html in your browser to -view them. It will also (due to -W) error out on any warning like Travis will. This is a good way to -locally verify it will pass. +For information on building library documentation, please check out `this guide `_. diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index dc3522a..21afd1f 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -196,7 +196,7 @@ class ColorCycle(Animation): Animate a sequence of one or more colors, cycling at the specified speed. :param pixel_object: The initialised LED object. - :param int speed: Animation speed in seconds, e.g. ``0.1``. + :param float speed: Animation speed in seconds, e.g. ``0.1``. :param colors: A list of colors to cycle through in ``(r, g, b)`` tuple, or ``0x000000`` hex format. Defaults to a rainbow color cycle. """ @@ -231,7 +231,7 @@ class Blink(ColorCycle): Blink a color on and off. :param pixel_object: The initialised LED object. - :param int speed: Animation speed in seconds, e.g. ``0.1``. + :param float speed: Animation speed in seconds, e.g. ``0.1``. :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. """ def __init__(self, pixel_object, speed, color, name=None): @@ -263,7 +263,7 @@ class Comet(Animation): A comet animation. :param pixel_object: The initialised LED object. - :param int speed: Animation speed in seconds, e.g. ``0.1``. + :param float speed: Animation speed in seconds, e.g. ``0.1``. :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. :param int tail_length: The length of the comet. Defaults to 10. Cannot exceed the number of pixels present in the pixel object, e.g. if the strip is 30 pixels @@ -344,7 +344,7 @@ class Sparkle(Animation): Sparkle animation of a single color. :param pixel_object: The initialised LED object. - :param int speed: Animation speed in seconds, e.g. ``0.1``. + :param float speed: Animation speed in seconds, e.g. ``0.1``. :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. :param num_sparkles: Number of sparkles to generate per animation cycle. """ @@ -385,22 +385,20 @@ class Pulse(Animation): Pulse all pixels a single color. :param pixel_object: The initialised LED object. - :param int speed: Animation refresh rate in seconds, e.g. ``0.1``. + :param float speed: Animation refresh rate in seconds, e.g. ``0.1``. :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. :param period: Period to pulse the LEDs over. Default 5. - :param max_intensity: The maximum intensity to pulse, between 0 and 1.0. Default 1. - :param min_intensity: The minimum intensity to pulse, between 0 and 1.0. Default 0. """ # pylint: disable=too-many-arguments def __init__(self, pixel_object, speed, color, period=5, name=None): super(Pulse, self).__init__(pixel_object, speed, color, name=name) self._period = period - self._generator = self._pulse_generator(period) + self._generator = self._pulse_generator() - def _pulse_generator(self, period): - period = int(period * NANOS_PER_SECOND) - white = len(self.pixel_object[0]) > 3 and type(self.pixel_object[0][-1]) is not float + def _pulse_generator(self): + period = int(self._period * NANOS_PER_SECOND) + white = len(self.pixel_object[0]) > 3 and isinstance(self.pixel_object[0][-1], float) half_period = period // 2 last_update = monotonic_ns() @@ -434,7 +432,7 @@ def reset(self): """ Resets the animation. """ - self._generator = self._pulse_generator(self._period) + self._generator = self._pulse_generator() class ColorWheel(Animation): @@ -442,17 +440,18 @@ class ColorWheel(Animation): The classic adafruit colorwheel. :param pixel_object: The initialised LED object. - :param int speed: Animation refresh rate in seconds, e.g. ``0.1``. + :param float speed: Animation refresh rate in seconds, e.g. ``0.1``. :param period: Period to cycle the colorwheel over. Default 5. """ # pylint: disable=too-many-arguments def __init__(self, pixel_object, speed, period=5, name=None): super(ColorWheel, self).__init__(pixel_object, speed, BLACK, name=name) - self._generator = self._wheel_generator(period) + self._period = period + self._generator = self._wheel_generator() - def _wheel_generator(self, period): - period = int(period * NANOS_PER_SECOND) + def _wheel_generator(self): + period = int(self._period * NANOS_PER_SECOND) last_update = monotonic_ns() cycle_position = 0 @@ -478,7 +477,7 @@ def reset(self): """ Resets the animation. """ - self._generator = self._wheel_generator(period) + self._generator = self._wheel_generator() class Chase(Animation): @@ -486,7 +485,7 @@ class Chase(Animation): Chase pixels in one direction in a single color, like a theater marquee sign. :param pixel_object: The initialised LED object. - :param int speed: Animation speed rate in seconds, e.g. ``0.1``. + :param float speed: Animation speed rate in seconds, e.g. ``0.1``. :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. :param size: Number of pixels to turn on in a row. :param spacing: Number of pixels to turn off in a row. @@ -504,12 +503,12 @@ def __init__(self, pixel_object, speed, color, size=2, spacing=3, reverse=False, self._reverse = reverse self._n = 0 - def resetter(): + def _resetter(): self._n = 0 self._reverse = reverse self._direction = 1 if not reverse else -1 - self._reset = resetter + self._reset = _resetter super(Chase, self).__init__(pixel_object, speed, color, name=name) @@ -787,7 +786,7 @@ def resume(self): for item in self._members: item.resume() - def done_handler(self, animation): + def done_handler(self, animation): # pylint: disable=unused-argument """ Called by some animations when they complete a cycle. For an AnimationGroup this is the first member of the group, if any. diff --git a/docs/api.rst b/docs/api.rst index 465084a..dd0a87d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,10 +1,8 @@ -API Reference -************* .. If you created a package, create one automodule per module in the package. .. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py) .. use this format as the module name: "adafruit_foo.foo" -.. automodule:: led_animation +.. automodule:: adafruit_led_animation :members: diff --git a/docs/conf.py b/docs/conf.py index 290aa7e..83d37e2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -20,7 +20,7 @@ # Uncomment the below if you use native CircuitPython modules such as # digitalio, micropython and busio. List the modules you use. Without it, the # autodoc module docs will fail to generate with a warning. -autodoc_mock_imports = ["led_animation"] +# autodoc_mock_imports = ["digitalio", "busio"] intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None),'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)} @@ -34,9 +34,9 @@ master_doc = 'index' # General information about the project. -project = u'LED_Animation Library' -copyright = u'2017 Adam Patt' -author = u'Adam Patt' +project = u'Adafruit_LED_Animation Library' +copyright = u'2020 Roy Hooper' +author = u'Roy Hooper' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -109,7 +109,7 @@ html_favicon = '_static/favicon.ico' # Output file base name for HTML help builder. -htmlhelp_basename = 'Led_animationLibrarydoc' +htmlhelp_basename = 'Adafruit_LED_animation_Librarydoc' # -- Options for LaTeX output --------------------------------------------- @@ -135,7 +135,8 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'LED_AnimationLibrary.tex', u'LED_Animation Library Documentation', + (master_doc, 'Adafruit_LED_Animation_Library.tex', + u'Adafruit_LED_Animation Library Documentation', author, 'manual'), ] @@ -144,7 +145,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'LED_Animationlibrary', u'LED_Animation Library Documentation', + (master_doc, 'Adafruit_LED_Animation_library', u'Adafruit LED_Animation Library Documentation', [author], 1) ] @@ -154,7 +155,7 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'LED_AnimationLibrary', u' LED_Animation Library Documentation', - author, 'LED_AnimationLibrary', 'One line description of project.', + (master_doc, 'Adafruit_LED_Animation_library', u'Adafruit LED_Animation Library Documentation', + author, 'Adafruit_LED_Animation_library', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docs/index.rst b/docs/index.rst index c7c4b7a..426a2fb 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,16 +20,10 @@ Table of Contents api -.. toctree:: - :caption: Tutorials - -.. toctree:: - :caption: Related Products - .. toctree:: :caption: Other Links - Download + Download CircuitPython Reference Documentation CircuitPython Support Forum Discord Chat diff --git a/examples/led_animation_simpletest.py b/examples/led_animation_simpletest.py index f8b3b2e..a70e652 100644 --- a/examples/led_animation_simpletest.py +++ b/examples/led_animation_simpletest.py @@ -1,17 +1,14 @@ -"""Blink LED animation.""" -import board +""" +Example animation sequence. +""" +from adafruit_led_animation.animation import Comet, AnimationSequence, Chase import neopixel -from adafruit_led_animation.animation import Blink -import adafruit_led_animation.color as color - -# Works on Circuit Playground Express and Bluefruit. -# For other boards, change board.NEOPIXEL to match the pin to which the NeoPixels are attached. -pixel_pin = board.NEOPIXEL -# Change to match the number of pixels you have attached to your board. -num_pixels = 10 +import board -pixels = neopixel.NeoPixel(pixel_pin, num_pixels) -blink = Blink(pixels, 0.5, color.PURPLE) +pixels = neopixel.NeoPixel(board.D6, 32, brightness=0.2, auto_write=False) +comet = Comet(pixels, speed=0.01, color=PURPLE, tail_length=10, bounce=True) +chase = Chase(pixels, speed=0.1, size=3, spacing=6, color=WHITE) +animations = AnimationSequence(comet, chase, advance_interval=15) while True: - blink.animate() + animations.animate() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..547436e --- /dev/null +++ b/setup.py @@ -0,0 +1,60 @@ +"""A setuptools based setup module. + +See: +https://packaging.python.org/en/latest/distributing.html +https://github.com/pypa/sampleproject +""" + +from setuptools import setup, find_packages +# To use a consistent encoding +from codecs import open +from os import path + +here = path.abspath(path.dirname(__file__)) + +# Get the long description from the README file +with open(path.join(here, 'README.rst'), encoding='utf-8') as f: + long_description = f.read() + +setup( + name='adafruit-circuitpython-led-animation', + + use_scm_version=True, + setup_requires=['setuptools_scm'], + + description='A library to easily generate LED animations', + long_description=long_description, + long_description_content_type='text/x-rst', + + # The project's main homepage. + url='https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation', + + # Author details + author='Adafruit Industries', + author_email='circuitpython@adafruit.com', + + install_requires=[ + 'Adafruit-Blinka' + ], + + # Choose your license + license='MIT', + + # See https://pypi.python.org/pypi?%3Aaction=list_classifiers + classifiers=[ + 'Development Status :: 3 - Alpha', + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Libraries', + 'Topic :: System :: Hardware', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + ], + + # What does your project relate to? + keywords='adafruit blinka circuitpython micropython led_animation led animation neopixel ' + 'dotstar', + + py_modules=['adafruit_led_animation'], +) From 333a6c7536cd82a83ebbcc59e9427437c85d0e12 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Sat, 4 Jan 2020 00:11:50 -0500 Subject: [PATCH 11/46] lint --- README.rst | 1 + examples/led_animation_simpletest.py | 1 + 2 files changed, 2 insertions(+) diff --git a/README.rst b/README.rst index c8000fa..c3211de 100644 --- a/README.rst +++ b/README.rst @@ -59,6 +59,7 @@ Usage Example .. code-block:: python from adafruit_led_animation.animation import Comet, AnimationSequence, Chase + from adafruit_led_animation.color import PURPLE, WHITE import neopixel import board diff --git a/examples/led_animation_simpletest.py b/examples/led_animation_simpletest.py index a70e652..2880c93 100644 --- a/examples/led_animation_simpletest.py +++ b/examples/led_animation_simpletest.py @@ -2,6 +2,7 @@ Example animation sequence. """ from adafruit_led_animation.animation import Comet, AnimationSequence, Chase +from adafruit_led_animation.color import PURPLE, WHITE import neopixel import board From a3d95a6b87ea34e25739a2c377185b032440129c Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Sat, 4 Jan 2020 00:15:45 -0500 Subject: [PATCH 12/46] lint --- README.rst | 4 ++-- examples/led_animation_simpletest.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index c3211de..8d109c8 100644 --- a/README.rst +++ b/README.rst @@ -58,10 +58,10 @@ Usage Example .. code-block:: python + import board + import neopixel from adafruit_led_animation.animation import Comet, AnimationSequence, Chase from adafruit_led_animation.color import PURPLE, WHITE - import neopixel - import board pixels = neopixel.NeoPixel(board.D6, 32, brightness=0.2, auto_write=False) comet = Comet(pixels, speed=0.01, color=PURPLE, tail_length=10, bounce=True) diff --git a/examples/led_animation_simpletest.py b/examples/led_animation_simpletest.py index 2880c93..8ee3e77 100644 --- a/examples/led_animation_simpletest.py +++ b/examples/led_animation_simpletest.py @@ -1,10 +1,10 @@ """ Example animation sequence. """ +import board +import neopixel from adafruit_led_animation.animation import Comet, AnimationSequence, Chase from adafruit_led_animation.color import PURPLE, WHITE -import neopixel -import board pixels = neopixel.NeoPixel(board.D6, 32, brightness=0.2, auto_write=False) comet = Comet(pixels, speed=0.01, color=PURPLE, tail_length=10, bounce=True) From c3670cb6dccb09cb94058d25c74bd672a71f0e77 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Sat, 4 Jan 2020 10:58:56 -0500 Subject: [PATCH 13/46] make sphinx happy (thanks @kattni!) --- docs/api.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index dd0a87d..b92f82b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -4,5 +4,11 @@ .. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py) .. use this format as the module name: "adafruit_foo.foo" -.. automodule:: adafruit_led_animation +.. automodule:: adafruit_led_animation.animation + :members: + +.. automodule:: adafruit_led_animation.color + :members: + +.. automodule:: adafruit_led_animation.helper :members: From fe005f404ca52b22021e935a1b66244fd9abc235 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Sat, 4 Jan 2020 11:15:20 -0500 Subject: [PATCH 14/46] add docs and examples --- adafruit_led_animation/helper.py | 41 +++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 16756e0..e2a8724 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -7,6 +7,26 @@ class AggregatePixels: """ AggregatePixels lets you treat ranges of pixels as single pixels for animation purposes. + + :param strip: An object that implements the Neopixel or Dotstar protocol. + :param iterable pixel_ranges: Pixel ranges (or individual pixels). + :param bool individual_pixels: Whether pixel_ranges are individual pixels. + + .. code-block:: python + + import board + import neopixel + from adafruit_led_animation.helper import AggregatePixels + pixels = neopixel.NeoPixel(board.D12, 307, auto_write=False) + + tree = AggregatePixels(pixels, [ + (0, 21), (21, 48), (48, 71), (71, 93),(93, 115), (115, 135), (135, 153), + (153, 170), (170, 188), (188, 203), (203, 217), (217, 228), (228, 240), + (240, 247), (247, 253), (253, 256), (256, 260), (260, 307)] + ) + tree[0] = (255, 255, 0) + tree.show() + """ def __init__(self, strip, pixel_ranges, individual_pixels=False): self._pixels = strip @@ -71,6 +91,7 @@ def brightness(self, brightness): def fill(self, color): """ Fill the used pixel ranges with color. + :param color: Color to fill all pixels referenced by this AggregatePixels definition with. """ if self._individual_pixels: for pixels in self._ranges: @@ -100,9 +121,27 @@ def auto_write(self, value): class SubsetPixels: """ - SubsetPixels lets you work with a subset of a pixel object. """ def __init__(self, strip, start, end): + """ + SubsetPixels lets you work with a subset of a pixel object. + + :param strip: An object that implements the Neopixel or Dotstar protocol. + :param int start: Starting pixel number. + :param int end: Ending pixel number. + + .. code-block:: python + + import board + import neopixel + from adafruit_led_animation.helper import SubsetPixels + pixels = neopixel.NeoPixel(board.D12, 307, auto_write=False) + + star_start = 260 + star_arm = SubsetPixels(pixels, star_start + 7, star_start + 15) + star_arm.fill((255, 0, 255)) + pixels.show() + """ self._pixels = strip self._start = start self._end = end From 15c8ae967503654f70245797ac60f8fb72738e43 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Sat, 4 Jan 2020 11:19:06 -0500 Subject: [PATCH 15/46] move docs above init --- adafruit_led_animation/helper.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index e2a8724..82099aa 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -121,27 +121,25 @@ def auto_write(self, value): class SubsetPixels: """ - """ - def __init__(self, strip, start, end): - """ - SubsetPixels lets you work with a subset of a pixel object. + SubsetPixels lets you work with a subset of a pixel object. - :param strip: An object that implements the Neopixel or Dotstar protocol. - :param int start: Starting pixel number. - :param int end: Ending pixel number. + :param strip: An object that implements the Neopixel or Dotstar protocol. + :param int start: Starting pixel number. + :param int end: Ending pixel number. - .. code-block:: python + .. code-block:: python - import board - import neopixel - from adafruit_led_animation.helper import SubsetPixels - pixels = neopixel.NeoPixel(board.D12, 307, auto_write=False) + import board + import neopixel + from adafruit_led_animation.helper import SubsetPixels + pixels = neopixel.NeoPixel(board.D12, 307, auto_write=False) - star_start = 260 - star_arm = SubsetPixels(pixels, star_start + 7, star_start + 15) - star_arm.fill((255, 0, 255)) - pixels.show() - """ + star_start = 260 + star_arm = SubsetPixels(pixels, star_start + 7, star_start + 15) + star_arm.fill((255, 0, 255)) + pixels.show() + """ + def __init__(self, strip, start, end): self._pixels = strip self._start = start self._end = end From e797092669c4770e40a9e4e25fac1d6b98feb032 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Sat, 1 Feb 2020 12:53:52 -0500 Subject: [PATCH 16/46] Move the pulse generator out to a helper so it can be reused --- adafruit_led_animation/__init__.py | 11 ++++++ adafruit_led_animation/animation.py | 53 ++++++----------------------- adafruit_led_animation/helper.py | 37 ++++++++++++++++++++ 3 files changed, 58 insertions(+), 43 deletions(-) diff --git a/adafruit_led_animation/__init__.py b/adafruit_led_animation/__init__.py index d39f136..a885f65 100644 --- a/adafruit_led_animation/__init__.py +++ b/adafruit_led_animation/__init__.py @@ -8,5 +8,16 @@ def const(value): # pylint: disable=missing-docstring return value +try: + from time import monotonic_ns +except ImportError: + import time + + def monotonic_ns(): + """ + Implementation of monotonic_ns for platforms without time.monotonic_ns + """ + return int(time.time() * NANOS_PER_SECOND) + NANOS_PER_SECOND = const(1000000000) NANOS_PER_MS = const(1000000) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 21afd1f..4f21c33 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -46,22 +46,14 @@ import random from math import ceil -from . import NANOS_PER_SECOND +from . import NANOS_PER_SECOND, monotonic_ns from .color import BLACK, RAINBOW, wheel -try: - from time import monotonic_ns -except ImportError: - import time - - def monotonic_ns(): - """ - Implementation of monotonic_ns for platforms without time.monotonic_ns - """ - return int(time.time() * NANOS_PER_SECOND) __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" +from .helper import pulse_generator + class Animation: """ @@ -394,45 +386,20 @@ class Pulse(Animation): def __init__(self, pixel_object, speed, color, period=5, name=None): super(Pulse, self).__init__(pixel_object, speed, color, name=name) self._period = period - self._generator = self._pulse_generator() - - def _pulse_generator(self): - period = int(self._period * NANOS_PER_SECOND) - white = len(self.pixel_object[0]) > 3 and isinstance(self.pixel_object[0][-1], float) - half_period = period // 2 - - last_update = monotonic_ns() - cycle_position = 0 - last_pos = 0 - while True: - fill_color = list(self.color) - now = monotonic_ns() - time_since_last_draw = now - last_update - last_update = now - pos = cycle_position = (cycle_position + time_since_last_draw) % period - if pos < last_pos: - self._cycle_done() - last_pos = pos - if pos > half_period: - pos = period - pos - intensity = (pos / half_period) - if white: - fill_color[3] = int(fill_color[3] * intensity) - fill_color[0] = int(fill_color[0] * intensity) - fill_color[1] = int(fill_color[1] * intensity) - fill_color[2] = int(fill_color[2] * intensity) - self.fill(fill_color) - self.show() - yield + self._generator = None + self.reset() def draw(self): - next(self._generator) + color = next(self._generator) + self.fill(color) + self.show() def reset(self): """ Resets the animation. """ - self._generator = self._pulse_generator() + white = len(self.pixel_object[0]) > 3 and isinstance(self.pixel_object[0][-1], float) + self._generator = pulse_generator(self._period, self, white) class ColorWheel(Animation): diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 82099aa..537a5c1 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -3,6 +3,9 @@ """ import math +from adafruit_led_animation import NANOS_PER_SECOND +from adafruit_led_animation.animation import monotonic_ns + class AggregatePixels: """ @@ -204,3 +207,37 @@ def auto_write(self): @auto_write.setter def auto_write(self, value): self._pixels.auto_write = value + + +def pulse_generator(period: float, animation_object, white=False): + """ + Generates a sequence of colors for a pulse, based on the time period specified. + :param period: Pulse duration in seconds. + :param animation_object: An animation object to interact with. + :param white: Whether the pixel strip has a white pixel. + """ + period = int(period * NANOS_PER_SECOND) + half_period = period // 2 + + last_update = monotonic_ns() + cycle_position = 0 + last_pos = 0 + while True: + fill_color = list(animation_object.color) + now = monotonic_ns() + time_since_last_draw = now - last_update + last_update = now + pos = cycle_position = (cycle_position + time_since_last_draw) % period + if pos < last_pos: + if animation_object.done_cycle_handler: + animation_object.done_cycle_handler(animation_object) + last_pos = pos + if pos > half_period: + pos = period - pos + intensity = (pos / half_period) + if white: + fill_color[3] = int(fill_color[3] * intensity) + fill_color[0] = int(fill_color[0] * intensity) + fill_color[1] = int(fill_color[1] * intensity) + fill_color[2] = int(fill_color[2] * intensity) + yield fill_color From ff5f0cf27e9271948ad21145f6116819fe108e32 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Thu, 7 May 2020 13:29:40 -0400 Subject: [PATCH 17/46] Updating files to match master. --- CODE_OF_CONDUCT.md | 10 ++-- README.rst | 92 +++++++++++++++++++++++++++--------- docs/conf.py | 115 ++++++++++++++++++++++++++------------------- setup.py | 57 ++++++++++------------ 4 files changed, 166 insertions(+), 108 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 7ca3a1d..a62e132 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -34,6 +34,8 @@ Examples of unacceptable behavior by participants include: * Excessive or unwelcome helping; answering outside the scope of the question asked * Trolling, insulting/derogatory comments, and personal or political attacks +* Promoting or spreading disinformation, lies, or conspiracy theories against + a person, group, organisation, project, or community * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission @@ -72,10 +74,10 @@ You may report in the following ways: In any situation, you may send an email to . On the Adafruit Discord, you may send an open message from any channel -to all Community Helpers by tagging @community moderators. You may also send an -open message from any channel, or a direct message to @kattni#1507, -@tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or -@Andon#8175. +to all Community Moderators by tagging @community moderators. You may +also send an open message from any channel, or a direct message to +@kattni#1507, @tannewt#4653, @Dan Halbert#1614, @cater#2442, +@sommersoft#0222, @Mr. Certainly#0472 or @Andon#8175. Email and direct message reports will be kept confidential. diff --git a/README.rst b/README.rst index 8d109c8..cf3f7e4 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,8 @@ Introduction ============ -.. image:: https://readthedocs.org/projects/adafruit-circuitpython-led_animation/badge/?version=latest - :target: https://circuitpython.readthedocs.io/projects/led_animation/en/latest/ +.. image:: https://readthedocs.org/projects/adafruit_circuitpython_led_animation/badge/?version=latest + :target: https://circuitpython.readthedocs.io/projects/led-animation/en/latest/ :alt: Documentation Status .. image:: https://img.shields.io/discord/327254708534116352.svg @@ -13,8 +13,7 @@ Introduction :target: https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation/actions :alt: Build Status -A library to easily generate LED animations - +Perform a variety of LED animation tasks Dependencies ============= @@ -24,25 +23,23 @@ This driver depends on: Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading -`the Adafruit library and driver bundle `_. +`the Adafruit library and driver bundle `_. + Installing from PyPI ===================== -.. note:: This library is not available on PyPI yet. Install documentation is included - as a standard element. Stay tuned for PyPI availability! - On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally `from -PyPI `_. To install for current user: +PyPI `_. To install for current user: .. code-block:: shell - pip3 install adafruit-circuitpython-led-animation + pip3 install adafruit-circuitpython-led animation To install system-wide (this may be required in some cases): .. code-block:: shell - sudo pip3 install adafruit-circuitpython-led-animation + sudo pip3 install adafruit-circuitpython-led animation To install in a virtual environment in your current project: @@ -51,7 +48,7 @@ To install in a virtual environment in your current project: mkdir project-name && cd project-name python3 -m venv .env source .env/bin/activate - pip3 install adafruit-circuitpython-led-animation + pip3 install adafruit-circuitpython-led animation Usage Example ============= @@ -60,25 +57,74 @@ Usage Example import board import neopixel - from adafruit_led_animation.animation import Comet, AnimationSequence, Chase - from adafruit_led_animation.color import PURPLE, WHITE + from adafruit_led_animation.animation import Blink + import adafruit_led_animation.color as color + + # Works on Circuit Playground Express and Bluefruit. + # For other boards, change board.NEOPIXEL to match the pin to which the NeoPixels are attached. + pixel_pin = board.NEOPIXEL + # Change to match the number of pixels you have attached to your board. + num_pixels = 10 - pixels = neopixel.NeoPixel(board.D6, 32, brightness=0.2, auto_write=False) - comet = Comet(pixels, speed=0.01, color=PURPLE, tail_length=10, bounce=True) - chase = Chase(pixels, speed=0.1, size=3, spacing=6, color=WHITE) - animations = AnimationSequence(comet, chase, advance_interval=15) + pixels = neopixel.NeoPixel(pixel_pin, num_pixels) + blink = Blink(pixels, 0.5, color.PURPLE) while True: - animations.animate() + blink.animate() Contributing ============ Contributions are welcome! Please read our `Code of Conduct -`_ +`_ before contributing to help this project stay welcoming. -Documentation -============= +Building locally +================ + +Zip release files +----------------- + +To build this library locally you'll need to install the +`circuitpython-build-tools `_ package. + +.. code-block:: shell + + python3 -m venv .env + source .env/bin/activate + pip install circuitpython-build-tools + +Once installed, make sure you are in the virtual environment: + +.. code-block:: shell + + source .env/bin/activate + +Then run the build: + +.. code-block:: shell + + circuitpython-build-bundles --filename_prefix circuitpython-led_animation --library_location . + +Sphinx documentation +----------------------- + +Sphinx is used to build the documentation based on rST files and comments in the code. First, +install dependencies (feel free to reuse the virtual environment from above): + +.. code-block:: shell + + python3 -m venv .env + source .env/bin/activate + pip install Sphinx sphinx-rtd-theme + +Now, once you have the virtual environment activated: + +.. code-block:: shell + + cd docs + sphinx-build -E -W -b html . _build/html -For information on building library documentation, please check out `this guide `_. +This will output the documentation to ``docs/_build/html``. Open the index.html in your browser to +view them. It will also (due to -W) error out on any warning like Travis will. This is a good way to +locally verify it will pass. diff --git a/docs/conf.py b/docs/conf.py index 83d37e2..ec64647 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,7 +2,8 @@ import os import sys -sys.path.insert(0, os.path.abspath('..')) + +sys.path.insert(0, os.path.abspath("..")) # -- General configuration ------------------------------------------------ @@ -10,42 +11,45 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinx.ext.napoleon', - 'sphinx.ext.todo', + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinx.ext.napoleon", + "sphinx.ext.todo", ] # TODO: Please Read! # Uncomment the below if you use native CircuitPython modules such as # digitalio, micropython and busio. List the modules you use. Without it, the # autodoc module docs will fail to generate with a warning. -# autodoc_mock_imports = ["digitalio", "busio"] +autodoc_mock_imports = ["led_animation"] -intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None),'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)} +intersphinx_mapping = { + "python": ("https://docs.python.org/3.4", None), + "CircuitPython": ("https://circuitpython.readthedocs.io/en/latest/", None), +} # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = u'Adafruit_LED_Animation Library' -copyright = u'2020 Roy Hooper' -author = u'Roy Hooper' +project = "LED_Animation Library" +copyright = "2017 Adam Patt" +author = "Adam Patt" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = u'1.0' +version = "1.0" # The full version, including alpha/beta/rc tags. -release = u'1.0' +release = "1.0" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -57,7 +61,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.env', 'CODE_OF_CONDUCT.md'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".env", "CODE_OF_CONDUCT.md"] # The reST default role (used for this markup: `text`) to use for all # documents. @@ -69,7 +73,7 @@ add_function_parentheses = True # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -84,60 +88,62 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -on_rtd = os.environ.get('READTHEDOCS', None) == 'True' +on_rtd = os.environ.get("READTHEDOCS", None) == "True" if not on_rtd: # only import and set the theme if we're building docs locally try: import sphinx_rtd_theme - html_theme = 'sphinx_rtd_theme' - html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), '.'] + + html_theme = "sphinx_rtd_theme" + html_theme_path = [sphinx_rtd_theme.get_html_theme_path(), "."] except: - html_theme = 'default' - html_theme_path = ['.'] + html_theme = "default" + html_theme_path = ["."] else: - html_theme_path = ['.'] + html_theme_path = ["."] # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # The name of an image file (relative to this directory) to use as a favicon of # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # -html_favicon = '_static/favicon.ico' +html_favicon = "_static/favicon.ico" # Output file base name for HTML help builder. -htmlhelp_basename = 'Adafruit_LED_animation_Librarydoc' +htmlhelp_basename = "Led_animationLibrarydoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'Adafruit_LED_Animation_Library.tex', - u'Adafruit_LED_Animation Library Documentation', - author, 'manual'), + ( + master_doc, + "LED_AnimationLibrary.tex", + "LED_Animation Library Documentation", + author, + "manual", + ), ] # -- Options for manual page output --------------------------------------- @@ -145,8 +151,13 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'Adafruit_LED_Animation_library', u'Adafruit LED_Animation Library Documentation', - [author], 1) + ( + master_doc, + "LED_Animationlibrary", + "LED_Animation Library Documentation", + [author], + 1, + ) ] # -- Options for Texinfo output ------------------------------------------- @@ -155,7 +166,13 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'Adafruit_LED_Animation_library', u'Adafruit LED_Animation Library Documentation', - author, 'Adafruit_LED_Animation_library', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "LED_AnimationLibrary", + " LED_Animation Library Documentation", + author, + "LED_AnimationLibrary", + "One line description of project.", + "Miscellaneous", + ), ] diff --git a/setup.py b/setup.py index 547436e..b95e951 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ """ from setuptools import setup, find_packages + # To use a consistent encoding from codecs import open from os import path @@ -13,48 +14,40 @@ here = path.abspath(path.dirname(__file__)) # Get the long description from the README file -with open(path.join(here, 'README.rst'), encoding='utf-8') as f: +with open(path.join(here, "README.rst"), encoding="utf-8") as f: long_description = f.read() setup( - name='adafruit-circuitpython-led-animation', - + name="adafruit-circuitpython-led animation", use_scm_version=True, - setup_requires=['setuptools_scm'], - - description='A library to easily generate LED animations', + setup_requires=["setuptools_scm"], + description="CircuitPython helper for LED colors and animations.", long_description=long_description, - long_description_content_type='text/x-rst', - + long_description_content_type="text/x-rst", # The project's main homepage. - url='https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation', - + url="https://github.com/adafruit/Adafruit_CircuitPython_LED Animation", # Author details - author='Adafruit Industries', - author_email='circuitpython@adafruit.com', - - install_requires=[ - 'Adafruit-Blinka' - ], - + author="Adafruit Industries", + author_email="circuitpython@adafruit.com", + install_requires=["Adafruit-Blinka",], # Choose your license - license='MIT', - + license="MIT", # See https://pypi.python.org/pypi?%3Aaction=list_classifiers classifiers=[ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'Topic :: Software Development :: Libraries', - 'Topic :: System :: Hardware', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Topic :: Software Development :: Libraries", + "Topic :: System :: Hardware", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", ], - # What does your project relate to? - keywords='adafruit blinka circuitpython micropython led_animation led animation neopixel ' - 'dotstar', - - py_modules=['adafruit_led_animation'], + keywords="adafruit blinka circuitpython micropython led animation led colors animations", + # You can just specify the packages manually here if your project is + # simple. Or you can use find_packages(). + # TODO: IF LIBRARY FILES ARE A PACKAGE FOLDER, + # CHANGE `py_modules=['...']` TO `packages=['...']` + py_modules=["adafruit_led animation"], ) From 043175adc3485987f1c10060af58467fd3734e61 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Thu, 7 May 2020 15:34:34 -0400 Subject: [PATCH 18/46] Bring it into the darkness. --- adafruit_led_animation/__init__.py | 3 ++ adafruit_led_animation/animation.py | 71 +++++++++++++++++++++-------- adafruit_led_animation/helper.py | 10 ++-- 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/adafruit_led_animation/__init__.py b/adafruit_led_animation/__init__.py index a885f65..3041c34 100644 --- a/adafruit_led_animation/__init__.py +++ b/adafruit_led_animation/__init__.py @@ -5,9 +5,11 @@ try: from micropython import const except ImportError: + def const(value): # pylint: disable=missing-docstring return value + try: from time import monotonic_ns except ImportError: @@ -19,5 +21,6 @@ def monotonic_ns(): """ return int(time.time() * NANOS_PER_SECOND) + NANOS_PER_SECOND = const(1000000000) NANOS_PER_MS = const(1000000) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 4f21c33..15ed244 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -59,6 +59,7 @@ class Animation: """ Base class for animations. """ + # pylint: disable=too-many-arguments def __init__(self, pixel_object, speed, color, peers=None, paused=False, name=None): self.pixel_object = pixel_object @@ -148,7 +149,7 @@ def color(self, color): if self._color == color: return if isinstance(color, int): - color = (color >> 16 & 0xff, color >> 8 & 0xff, color & 0xff) + color = (color >> 16 & 0xFF, color >> 8 & 0xFF, color & 0xFF) self._color = color self._recompute_color(color) @@ -192,6 +193,7 @@ class ColorCycle(Animation): :param colors: A list of colors to cycle through in ``(r, g, b)`` tuple, or ``0x000000`` hex format. Defaults to a rainbow color cycle. """ + def __init__(self, pixel_object, speed, colors=RAINBOW, name=None): self.colors = colors super(ColorCycle, self).__init__(pixel_object, speed, colors[0], name=name) @@ -226,6 +228,7 @@ class Blink(ColorCycle): :param float speed: Animation speed in seconds, e.g. ``0.1``. :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. """ + def __init__(self, pixel_object, speed, color, name=None): super(Blink, self).__init__(pixel_object, speed, [color, BLACK], name=name) @@ -240,6 +243,7 @@ class Solid(ColorCycle): :param pixel_object: The initialised LED object. :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. """ + def __init__(self, pixel_object, color, name=None): super(Solid, self).__init__(pixel_object, speed=1, colors=[color], name=name) @@ -263,9 +267,18 @@ class Comet(Animation): :param bool reverse: Animates the comet in the reverse order. Defaults to ``False``. :param bool bounce: Comet will bounce back and forth. Defaults to ``True``. """ + # pylint: disable=too-many-arguments - def __init__(self, pixel_object, speed, color, tail_length=10, reverse=False, bounce=False, - name=None): + def __init__( + self, + pixel_object, + speed, + color, + tail_length=10, + reverse=False, + bounce=False, + name=None, + ): self._tail_length = tail_length + 1 self._color_step = 0.9 / tail_length self._color_offset = 0.1 @@ -282,9 +295,11 @@ def _recompute_color(self, color): def __recompute_color(self, color): self._comet_colors = [BLACK] + [ - [int(color[rgb] * ((n * self._color_step) + self._color_offset)) - for rgb in range(len(color)) - ] for n in range(self._tail_length - 1) + [ + int(color[rgb] * ((n * self._color_step) + self._color_offset)) + for rgb in range(len(color)) + ] + for n in range(self._tail_length - 1) ] self._reverse_comet_colors = list(reversed(self._comet_colors)) self._computed_color = color @@ -309,9 +324,11 @@ def _comet_generator(self): end = num_pixels - start if start <= 0: num_visible = self._tail_length + start - self.pixel_object[0:num_visible] = colors[self._tail_length - num_visible:] + self.pixel_object[0:num_visible] = colors[ + self._tail_length - num_visible : + ] else: - self.pixel_object[start:start + end] = colors[0:end] + self.pixel_object[start : start + end] = colors[0:end] self.show() yield cycle_passes += 1 @@ -340,6 +357,7 @@ class Sparkle(Animation): :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. :param num_sparkles: Number of sparkles to generate per animation cycle. """ + # pylint: disable=too-many-arguments def __init__(self, pixel_object, speed, color, num_sparkles=1, name=None): if len(pixel_object) < 2: @@ -361,8 +379,10 @@ def _recompute_color(self, color): self._dim_color = dim_color def draw(self): - pixels = [random.randint(0, (len(self.pixel_object) - 2)) - for n in range(self._num_sparkles)] + pixels = [ + random.randint(0, (len(self.pixel_object) - 2)) + for n in range(self._num_sparkles) + ] for pixel in pixels: self.pixel_object[pixel] = self._color self.show() @@ -398,7 +418,9 @@ def reset(self): """ Resets the animation. """ - white = len(self.pixel_object[0]) > 3 and isinstance(self.pixel_object[0][-1], float) + white = len(self.pixel_object[0]) > 3 and isinstance( + self.pixel_object[0][-1], float + ) self._generator = pulse_generator(self._period, self, white) @@ -432,8 +454,9 @@ def _wheel_generator(self): self._cycle_done() last_pos = pos wheel_index = int((pos / period) * 256) - self.pixel_object[:] = [wheel((i + wheel_index) % 255) - for i, _ in enumerate(self.pixel_object)] + self.pixel_object[:] = [ + wheel((i + wheel_index) % 255) for i, _ in enumerate(self.pixel_object) + ] self.show() yield @@ -460,7 +483,9 @@ class Chase(Animation): """ # pylint: disable=too-many-arguments - def __init__(self, pixel_object, speed, color, size=2, spacing=3, reverse=False, name=None): + def __init__( + self, pixel_object, speed, color, size=2, spacing=3, reverse=False, name=None + ): self._size = size self._spacing = spacing self._repeat_width = size + spacing @@ -495,8 +520,10 @@ def draw(self): self.pixel_object.fill((0, 0, 0)) for i in range(self._size): n = (self._n + i) % self._repeat_width - num = len(self.pixel_object[n::self._repeat_width]) - self.pixel_object[n::self._repeat_width] = [self.group_color(n) for n in range(num)] + num = len(self.pixel_object[n :: self._repeat_width]) + self.pixel_object[n :: self._repeat_width] = [ + self.group_color(n) for n in range(num) + ] _n = (self._n + self._direction) % self._repeat_width if _n < self._n: self._cycle_done() @@ -546,9 +573,14 @@ class AnimationSequence: while True: animations.animate() """ - def __init__(self, *members, advance_interval=None, auto_clear=False, random_order=False): + + def __init__( + self, *members, advance_interval=None, auto_clear=False, random_order=False + ): self._members = members - self._advance_interval = advance_interval * NANOS_PER_SECOND if advance_interval else None + self._advance_interval = ( + advance_interval * NANOS_PER_SECOND if advance_interval else None + ) self._last_advance = monotonic_ns() self._current = 0 self._auto_clear = auto_clear @@ -605,7 +637,7 @@ def random(self): """ Jump to a random animation. """ - self.activate(random.randint(0, len(self._members)-1)) + self.activate(random.randint(0, len(self._members) - 1)) def animate(self): """ @@ -696,6 +728,7 @@ class AnimationGroup: first member of the group. Defaults to ``False``. """ + def __init__(self, *members, sync=False): self._members = members self._sync = sync diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 537a5c1..8410976 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -31,6 +31,7 @@ class AggregatePixels: tree.show() """ + def __init__(self, strip, pixel_ranges, individual_pixels=False): self._pixels = strip self._ranges = pixel_ranges @@ -142,6 +143,7 @@ class SubsetPixels: star_arm.fill((255, 0, 255)) pixels.show() """ + def __init__(self, strip, start, end): self._pixels = strip self._start = start @@ -154,7 +156,7 @@ def __repr__(self): def __setitem__(self, index, val): if isinstance(index, slice): start, stop, step = index.indices(self.n) - self._pixels[start + self._start:stop + self._start:step] = val + self._pixels[start + self._start : stop + self._start : step] = val else: self._pixels[index + self._start] = val @@ -164,7 +166,7 @@ def __setitem__(self, index, val): def __getitem__(self, index): if isinstance(index, slice): start, stop, step = index.indices(self.n) - return self._pixels[start + self._start:stop + self._start:step] + return self._pixels[start + self._start : stop + self._start : step] if index < 0: index += len(self) if index >= self.n or index < 0: @@ -189,7 +191,7 @@ def fill(self, color): """ Fill the used pixel ranges with color. """ - self._pixels[self._start:self._end] = [color] * (self.n) + self._pixels[self._start : self._end] = [color] * (self.n) def show(self): """ @@ -234,7 +236,7 @@ def pulse_generator(period: float, animation_object, white=False): last_pos = pos if pos > half_period: pos = period - pos - intensity = (pos / half_period) + intensity = pos / half_period if white: fill_color[3] = int(fill_color[3] * intensity) fill_color[0] = int(fill_color[0] * intensity) From ee0ee6e8cb76dee6671e55545fd3d18e9f0d2325 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Thu, 7 May 2020 15:45:38 -0400 Subject: [PATCH 19/46] Further into the darkness. --- adafruit_led_animation/animation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index c54e1a2..e5b4716 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -867,4 +867,3 @@ def reset(self): """ for item in self._members: item.reset() - From 95d273c02620d89c288a4cae531a7ac3c56ab369 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Thu, 7 May 2020 16:32:15 -0400 Subject: [PATCH 20/46] Fix import. --- adafruit_led_animation/animation.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index e5b4716..cde5712 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -45,14 +45,13 @@ import random from math import ceil +import adafruit_led_animation.helper from . import NANOS_PER_SECOND, monotonic_ns from .color import BLACK, RAINBOW, wheel __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" -from .helper import pulse_generator - class Animation: """ @@ -420,7 +419,7 @@ def reset(self): white = len(self.pixel_object[0]) > 3 and isinstance( self.pixel_object[0][-1], float ) - self._generator = pulse_generator(self._period, self, white) + self._generator = adafruit_led_animation.helper.pulse_generator(self._period, self, white) class ColorWheel(Animation): From 8bfa8014dce13d0dca6de4439aea2d4286a05614 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Thu, 7 May 2020 16:47:18 -0400 Subject: [PATCH 21/46] Reblackening. --- adafruit_led_animation/animation.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index cde5712..505ee06 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -419,7 +419,9 @@ def reset(self): white = len(self.pixel_object[0]) > 3 and isinstance( self.pixel_object[0][-1], float ) - self._generator = adafruit_led_animation.helper.pulse_generator(self._period, self, white) + self._generator = adafruit_led_animation.helper.pulse_generator( + self._period, self, white + ) class ColorWheel(Animation): From facd44764520b711d3e461ee86883be864455590 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Fri, 8 May 2020 14:49:45 -0400 Subject: [PATCH 22/46] Fix import, add licenses, docstrings. --- adafruit_led_animation/__init__.py | 26 ++++++++++++++-- adafruit_led_animation/animation.py | 2 +- adafruit_led_animation/color.py | 29 ++++++++++++++--- adafruit_led_animation/helper.py | 48 ++++++++++++++++++++++++++--- 4 files changed, 94 insertions(+), 11 deletions(-) diff --git a/adafruit_led_animation/__init__.py b/adafruit_led_animation/__init__.py index 3041c34..cede779 100644 --- a/adafruit_led_animation/__init__.py +++ b/adafruit_led_animation/__init__.py @@ -1,11 +1,33 @@ +# The MIT License (MIT) +# +# Copyright (c) 2020 Kattni Rembor for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. """ -Adafruit LED Animation library. +Timing for Adafruit LED Animation library. + +Author(s): Roy Hooper """ try: from micropython import const except ImportError: - def const(value): # pylint: disable=missing-docstring return value diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 505ee06..31c5c32 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -47,7 +47,7 @@ from math import ceil import adafruit_led_animation.helper from . import NANOS_PER_SECOND, monotonic_ns -from .color import BLACK, RAINBOW, wheel +from .color import BLACK, RAINBOW, colorwheel __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" diff --git a/adafruit_led_animation/color.py b/adafruit_led_animation/color.py index 116168e..0283906 100644 --- a/adafruit_led_animation/color.py +++ b/adafruit_led_animation/color.py @@ -1,3 +1,24 @@ +# The MIT License (MIT) +# +# Copyright (c) 2019 Kattni Rembor for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. """Color variables made available for import. RAINBOW is a list of colors to use for cycling through. @@ -24,12 +45,12 @@ try: - from _pixelbuf import wheel # pylint: disable=unused-import + from _pixelbuf import colorwheel # pylint: disable=unused-import except ImportError: # Ensure we have a wheel if not built in - def wheel(pos): # pylint: disable=missing-docstring - # Input a value 0 to 255 to get a color value. - # The colours are a transition r - g - b - back to r. + def colorwheel(pos): + """Input a value 0 to 255 to get a color value. + The colours are a transition r - g - b - back to r.""" if pos < 0 or pos > 255: return 0, 0, 0 if pos < 85: diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 8410976..50eb558 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -1,10 +1,50 @@ +# The MIT License (MIT) +# +# Copyright (c) 2019 Kattni Rembor for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. """ -Helper classes for making complex animations. +`adafruit_led_animation.helper` +================================================================================ + +Helper classes for making complex animations using LED Animation library. + + +* Author(s): Roy Hooper, Kattni Rembor + +Implementation Notes +-------------------- + +**Hardware:** + +* `Adafruit NeoPixels `_ +* `Adafruit DotStars `_ + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + """ -import math -from adafruit_led_animation import NANOS_PER_SECOND -from adafruit_led_animation.animation import monotonic_ns +import math +from . import NANOS_PER_SECOND, monotonic_ns class AggregatePixels: From 21ca6b94e65208b7f1fd303c004807002c5d99c5 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Fri, 8 May 2020 14:54:17 -0400 Subject: [PATCH 23/46] Hello darkness my old friend. --- adafruit_led_animation/__init__.py | 1 + adafruit_led_animation/animation.py | 17 +++++++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/adafruit_led_animation/__init__.py b/adafruit_led_animation/__init__.py index cede779..f2f5c45 100644 --- a/adafruit_led_animation/__init__.py +++ b/adafruit_led_animation/__init__.py @@ -28,6 +28,7 @@ try: from micropython import const except ImportError: + def const(value): # pylint: disable=missing-docstring return value diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 31c5c32..deb298e 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -424,22 +424,22 @@ def reset(self): ) -class ColorWheel(Animation): +class Rainbow(Animation): """ - The classic adafruit colorwheel. + The classic rainbow color wheel. :param pixel_object: The initialised LED object. :param float speed: Animation refresh rate in seconds, e.g. ``0.1``. - :param period: Period to cycle the colorwheel over. Default 5. + :param period: Period to cycle the rainbow over. Default 5. """ # pylint: disable=too-many-arguments def __init__(self, pixel_object, speed, period=5, name=None): - super(ColorWheel, self).__init__(pixel_object, speed, BLACK, name=name) + super(Rainbow, self).__init__(pixel_object, speed, BLACK, name=name) self._period = period - self._generator = self._wheel_generator() + self._generator = self._color_wheel_generator() - def _wheel_generator(self): + def _color_wheel_generator(self): period = int(self._period * NANOS_PER_SECOND) last_update = monotonic_ns() @@ -455,7 +455,8 @@ def _wheel_generator(self): last_pos = pos wheel_index = int((pos / period) * 256) self.pixel_object[:] = [ - wheel((i + wheel_index) % 255) for i, _ in enumerate(self.pixel_object) + colorwheel((i + wheel_index) % 255) + for i, _ in enumerate(self.pixel_object) ] self.show() yield @@ -467,7 +468,7 @@ def reset(self): """ Resets the animation. """ - self._generator = self._wheel_generator() + self._generator = self._color_wheel_generator() class SparklePulse(Animation): From 9398874c0a7048a013bd70715cfac841db7ef547 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Mon, 11 May 2020 14:55:42 -0400 Subject: [PATCH 24/46] Make RainbowChase work, and make Chase code easier to work with via generators --- adafruit_led_animation/animation.py | 84 +++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 15 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index deb298e..94aec13 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -72,6 +72,7 @@ def __init__(self, pixel_object, speed, color, peers=None, paused=False, name=No self.color = color # Triggers _recompute_color self.done_cycle_handler = None self.name = name + self.counter = 0 def __str__(self): return "" % (self.__class__.__name__, self.name) @@ -91,6 +92,8 @@ def animate(self): return False self.draw() + self.counter += 1 + if self.peers: for peer in self.peers: peer.draw() @@ -555,10 +558,10 @@ def __init__( self._overflow = len(pixel_object) % self._repeat_width self._direction = 1 if not reverse else -1 self._reverse = reverse - self._n = 0 + self._offset = 0 def _resetter(): - self._n = 0 + self._offset = 0 self._reverse = reverse self._direction = 1 if not reverse else -1 @@ -579,27 +582,47 @@ def reverse(self, value): self._direction = -1 if self._reverse else 1 def draw(self): - self.pixel_object.fill((0, 0, 0)) - for i in range(self._size): - n = (self._n + i) % self._repeat_width - num = len(self.pixel_object[n :: self._repeat_width]) - self.pixel_object[n :: self._repeat_width] = [ - self.group_color(n) for n in range(num) - ] - _n = (self._n + self._direction) % self._repeat_width - if _n < self._n: + + def bar_colors(): + bar_no = 0 + for i in range(self._offset, 0, -1): + if i > self._spacing: + yield self.bar_color(bar_no, i) + else: + yield self.space_color(bar_no, i) + bar_no = 1 + while True: + for bar_pixel in range(self._size): + yield self.bar_color(bar_no, bar_pixel) + for space_pixel in range(self._spacing): + yield self.space_color(bar_no, space_pixel) + bar_no += 1 + + colorgen = bar_colors() + self.pixel_object[:] = [next(colorgen) for _ in self.pixel_object] + + if self._offset == 0: self._cycle_done() - self._n = _n - self.show() + self._offset = (self._offset + self._direction) % self._repeat_width - def group_color(self, n): # pylint: disable=unused-argument + def bar_color(self, n, pixel_no=0): # pylint: disable=unused-argument """ - Generate the color for the n'th group + Generate the color for the n'th bar_color in the Chase :param n: The pixel group to get the color for + :param pixel_no: Which pixel in the group to get the color for """ return self.color + def space_color(self, n, pixel_no=0): + """ + Generate the spacing color for the n'th bar_color in the Chase + + :param n: The pixel group to get the spacing color for + :param pixel_no: Which pixel in the group to get the spacing color for + """ + return 0 + def reset(self): """ Reset the animation. @@ -607,6 +630,37 @@ def reset(self): self._reset() +class RainbowChase(Chase): + """ + Chase pixels in one direction, like a theater marquee but with rainbows! + + :param pixel_object: The initialised LED object. + :param float speed: Animation speed rate in seconds, e.g. ``0.1``. + :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. + :param size: Number of pixels to turn on in a row. + :param spacing: Number of pixels to turn off in a row. + :param reverse: Reverse direction of movement. + :param wheel_step: How many colors to skip in `colorwheel` per bar (default 8) + """ + # pylint: disable=too-many-arguments + def __init__(self, pixel_object, speed, color, size=2, spacing=3, reverse=False, name=None, + wheel_step=8): + self._num_colors = 256 // wheel_step + self._colors = [colorwheel(n % 256) for n in range(0, 512, wheel_step)] + self._color_idx = 0 + super(RainbowChase, self).__init__(pixel_object, speed, color, size, spacing, reverse, name) + + def bar_color(self, n, *_): + return self._colors[self._color_idx - n] + + def _cycle_done(self): + self._color_idx = (self._color_idx + self._direction) % len(self._colors) + super(RainbowChase, self)._cycle_done() + + def show(self): + super(RainbowChase, self).show() + + class AnimationSequence: """ A sequence of Animations to run in sequence, looping forever. From 3277e4d4f6104d8896cc4e250d8c92406c453623 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Mon, 11 May 2020 15:07:22 -0400 Subject: [PATCH 25/46] fix lint --- adafruit_led_animation/animation.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 94aec13..7ee2f2f 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -58,7 +58,7 @@ class Animation: Base class for animations. """ - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments,too-many-instance-attributes def __init__(self, pixel_object, speed, color, peers=None, paused=False, name=None): self.pixel_object = pixel_object self.pixel_object.auto_write = False @@ -614,7 +614,7 @@ def bar_color(self, n, pixel_no=0): # pylint: disable=unused-argument """ return self.color - def space_color(self, n, pixel_no=0): + def space_color(self, n, pixel_no=0): # pylint: disable=unused-argument,no-self-use """ Generate the spacing color for the n'th bar_color in the Chase @@ -650,16 +650,13 @@ def __init__(self, pixel_object, speed, color, size=2, spacing=3, reverse=False, self._color_idx = 0 super(RainbowChase, self).__init__(pixel_object, speed, color, size, spacing, reverse, name) - def bar_color(self, n, *_): + def bar_color(self, n, pixel_no=0): return self._colors[self._color_idx - n] def _cycle_done(self): self._color_idx = (self._color_idx + self._direction) % len(self._colors) super(RainbowChase, self)._cycle_done() - def show(self): - super(RainbowChase, self).show() - class AnimationSequence: """ From cd7783f89b6d00c7211e050ccd9106698f2b3409 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Mon, 11 May 2020 19:50:50 -0400 Subject: [PATCH 26/46] add rainbowcomet --- adafruit_led_animation/animation.py | 51 +++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 7ee2f2f..9a26e23 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -285,6 +285,7 @@ def __init__( self._color_offset = 0.1 self._comet_colors = None self._reverse_comet_colors = None + self._initial_reverse = reverse self.reverse = reverse self.bounce = bounce self._computed_color = color @@ -347,6 +348,47 @@ def reset(self): Resets to the first color. """ self._generator = self._comet_generator() + self.reverse = self._initial_reverse + + +class RainbowComet(Comet): + """ + A comet animation. + + :param pixel_object: The initialised LED object. + :param float speed: Animation speed in seconds, e.g. ``0.1``. + :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. + :param int tail_length: The length of the comet. Defaults to 10. Cannot exceed the number of + pixels present in the pixel object, e.g. if the strip is 30 pixels + long, the ``tail_length`` cannot exceed 30 pixels. + :param bool reverse: Animates the comet in the reverse order. Defaults to ``False``. + :param bool bounce: Comet will bounce back and forth. Defaults to ``True``. + :param int colorwheel_offset: Offset from start of colorwheel (0-255). + """ + + # pylint: disable=too-many-arguments + def __init__(self, pixel_object, speed, color, tail_length=10, reverse=False, bounce=False, + colorwheel_offset=0, name=None): + self._colorwheel_is_tuple = isinstance(colorwheel(0), tuple) + self._colorwheel_offset = colorwheel_offset + + super().__init__(pixel_object, speed, color, tail_length, reverse, bounce, name) + + def _calc_brightness(self, n, color): + brightness = ((n * self._color_step) + self._color_offset) + if not self._colorwheel_is_tuple: + color = (color & 0xff, ((color & 0xff00) >> 8), (color >> 16)) + return [int(i * brightness) for i in color] + + def __recompute_color(self, color): + factor = int(256 / self._tail_length) + self._comet_colors = [BLACK] + [ + self._calc_brightness(n, colorwheel(int( + (n * factor) + self._color_offset + self._colorwheel_offset) % 256)) + for n in range(self._tail_length - 1) + ] + self._reverse_comet_colors = list(reversed(self._comet_colors)) + self._computed_color = color class Sparkle(Animation): @@ -687,9 +729,9 @@ class AnimationSequence: animations.animate() """ - def __init__( - self, *members, advance_interval=None, auto_clear=False, random_order=False - ): + # pylint: disable=too-many-instance-attributes + def __init__(self, *members, advance_interval=None, auto_clear=False, random_order=False, + auto_reset=False): self._members = members self._advance_interval = ( advance_interval * NANOS_PER_SECOND if advance_interval else None @@ -697,6 +739,7 @@ def __init__( self._last_advance = monotonic_ns() self._current = 0 self._auto_clear = auto_clear + self._auto_reset = auto_reset self.clear_color = BLACK self._paused = False self._paused_at = 0 @@ -743,6 +786,8 @@ def next(self): """ current = self._current self.activate((self._current + 1) % len(self._members)) + if self._auto_reset: + self.current_animation.reset() if current > self._current: self._cycle_done() From 6f17a43ad3003fdaaa6086448a4c374308c42cd0 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Tue, 12 May 2020 11:43:56 -0400 Subject: [PATCH 27/46] Update Aggregate and Subset naming. --- adafruit_led_animation/__init__.py | 2 +- adafruit_led_animation/animation.py | 5 +++-- adafruit_led_animation/helper.py | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/adafruit_led_animation/__init__.py b/adafruit_led_animation/__init__.py index f2f5c45..fbe8130 100644 --- a/adafruit_led_animation/__init__.py +++ b/adafruit_led_animation/__init__.py @@ -1,6 +1,6 @@ # The MIT License (MIT) # -# Copyright (c) 2020 Kattni Rembor for Adafruit Industries +# Copyright (c) 2020 Roy Hooper # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 9a26e23..7e897c9 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -1,6 +1,7 @@ # The MIT License (MIT) # -# Copyright (c) 2019 Kattni Rembor for Adafruit Industries +# Copyright (c) 2019-2020 Roy Hooper +# Copyright (c) 2020 Kattni Rembor for Adafruit Industries # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -353,7 +354,7 @@ def reset(self): class RainbowComet(Comet): """ - A comet animation. + A rainbow comet animation. :param pixel_object: The initialised LED object. :param float speed: Animation speed in seconds, e.g. ``0.1``. diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 50eb558..c752540 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -1,6 +1,6 @@ # The MIT License (MIT) # -# Copyright (c) 2019 Kattni Rembor for Adafruit Industries +# Copyright (c) 2019 Roy Hooper # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -47,9 +47,9 @@ from . import NANOS_PER_SECOND, monotonic_ns -class AggregatePixels: +class PixelMap: """ - AggregatePixels lets you treat ranges of pixels as single pixels for animation purposes. + PixelMap lets you treat ranges of pixels as single pixels for animation purposes. :param strip: An object that implements the Neopixel or Dotstar protocol. :param iterable pixel_ranges: Pixel ranges (or individual pixels). @@ -59,10 +59,10 @@ class AggregatePixels: import board import neopixel - from adafruit_led_animation.helper import AggregatePixels + from adafruit_led_animation.helper import PixelMap pixels = neopixel.NeoPixel(board.D12, 307, auto_write=False) - tree = AggregatePixels(pixels, [ + tree = PixelMap(pixels, [ (0, 21), (21, 48), (48, 71), (71, 93),(93, 115), (115, 135), (135, 153), (153, 170), (170, 188), (188, 203), (203, 217), (217, 228), (228, 240), (240, 247), (247, 253), (253, 256), (256, 260), (260, 307)] @@ -135,7 +135,7 @@ def brightness(self, brightness): def fill(self, color): """ Fill the used pixel ranges with color. - :param color: Color to fill all pixels referenced by this AggregatePixels definition with. + :param color: Color to fill all pixels referenced by this PixelMap definition with. """ if self._individual_pixels: for pixels in self._ranges: @@ -163,9 +163,9 @@ def auto_write(self, value): self._pixels.auto_write = value -class SubsetPixels: +class PixelSubset: """ - SubsetPixels lets you work with a subset of a pixel object. + PixelSubset lets you work with a subset of a pixel object. :param strip: An object that implements the Neopixel or Dotstar protocol. :param int start: Starting pixel number. @@ -175,11 +175,11 @@ class SubsetPixels: import board import neopixel - from adafruit_led_animation.helper import SubsetPixels + from adafruit_led_animation.helper import PixelSubset pixels = neopixel.NeoPixel(board.D12, 307, auto_write=False) star_start = 260 - star_arm = SubsetPixels(pixels, star_start + 7, star_start + 15) + star_arm = PixelSubset(pixels, star_start + 7, star_start + 15) star_arm.fill((255, 0, 255)) pixels.show() """ From b8fe94a4d37707aa33f3045941e767ef1334b0f7 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Wed, 13 May 2020 12:57:33 -0400 Subject: [PATCH 28/46] Add NotifiedAnimationSequence, remove color from Rainbow* --- adafruit_led_animation/animation.py | 56 ++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 7e897c9..ac7dd4f 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -48,7 +48,7 @@ from math import ceil import adafruit_led_animation.helper from . import NANOS_PER_SECOND, monotonic_ns -from .color import BLACK, RAINBOW, colorwheel +from .color import BLACK, WHITE, RAINBOW, colorwheel __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" @@ -368,12 +368,12 @@ class RainbowComet(Comet): """ # pylint: disable=too-many-arguments - def __init__(self, pixel_object, speed, color, tail_length=10, reverse=False, bounce=False, + def __init__(self, pixel_object, speed, tail_length=10, reverse=False, bounce=False, colorwheel_offset=0, name=None): self._colorwheel_is_tuple = isinstance(colorwheel(0), tuple) self._colorwheel_offset = colorwheel_offset - super().__init__(pixel_object, speed, color, tail_length, reverse, bounce, name) + super().__init__(pixel_object, speed, 0, tail_length, reverse, bounce, name) def _calc_brightness(self, n, color): brightness = ((n * self._color_step) + self._color_offset) @@ -686,12 +686,12 @@ class RainbowChase(Chase): :param wheel_step: How many colors to skip in `colorwheel` per bar (default 8) """ # pylint: disable=too-many-arguments - def __init__(self, pixel_object, speed, color, size=2, spacing=3, reverse=False, name=None, + def __init__(self, pixel_object, speed, size=2, spacing=3, reverse=False, name=None, wheel_step=8): self._num_colors = 256 // wheel_step self._colors = [colorwheel(n % 256) for n in range(0, 512, wheel_step)] self._color_idx = 0 - super(RainbowChase, self).__init__(pixel_object, speed, color, size, spacing, reverse, name) + super(RainbowChase, self).__init__(pixel_object, speed, 0, size, spacing, reverse, name) def bar_color(self, n, pixel_no=0): return self._colors[self._color_idx - n] @@ -703,20 +703,24 @@ def _cycle_done(self): class AnimationSequence: """ - A sequence of Animations to run in sequence, looping forever. + A sequence of Animations to run in succession, looping forever. Advances manually or at the specified interval. :param members: The animation objects or groups. :param int advance_interval: Time in seconds between animations if cycling automatically. Defaults to ``None``. - :param random_order: Switch to a different animation each advance. + :param bool auto_clear: Clear the pixels between animations. If ``True``, the current animation + will be cleared from the pixels before the next one starts. + Defaults to ``False``. + :param bool random_order: Activate the animations in a random order. Defaults to ``False``. + :param bool auto_reset: .. code-block:: python - from adafruit_led_animation.animation import AnimationSequence, Blink, Comet, Sparkle - import adafruit_led_animation.color as color import board import neopixel + from adafruit_led_animation.animation import AnimationSequence, Blink, Comet, Sparkle + import adafruit_led_animation.color as color strip_pixels = neopixel.NeoPixel(board.A1, 30, brightness=1, auto_write=False) @@ -877,6 +881,40 @@ def reset(self): self.current_animation.reset() +class NotifiedAnimationSequence(AnimationSequence): + """Prints the current animation type (e.g. ``RainbowComet``, ``Chase``) and ``name`` (e.g. the + string from ``name=`` in the animation setup). Use for debugging when running multiple versions + of the same animation, or simply to print the names to the serial console. Used in the same + manner as ``AnimationSequence`` which is a sequence of Animations to run in succession, looping + forever. Advances manually or at the specified interval. + + :param members: The animation objects or groups. + :param int advance_interval: Time in seconds between animations if cycling + automatically. Defaults to ``None``. + :param random_order: Activate the animations in a random order. Defaults to ``False``. + + .. code-block:: python + + import board + import neopixel + from adafruit_led_animation.animation import NotifiedAnimationSequence, Blink, Comet + import adafruit_led_animation.color as color + + strip_pixels = neopixel.NeoPixel(board.A1, 30, brightness=1, auto_write=False) + + blink = Blink(strip_pixels, 0.2, color.RED, name="red-blink") + comet = Comet(strip_pixels, 0.1, color.BLUE, name="blue-comet") + + animations = NotifiedAnimationSequence(blink, comet, advance_interval=1) + + while True: + animations.animate() + """ + def activate(self, index): + super(NotifiedAnimationSequence, self).activate(index) + print("Activating:", self.current_animation) + + class AnimationGroup: """ A group of animations that are active together. An example would be grouping a strip of From d66c9992d02d0f08792af81f85911ef49b6e71ea Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Wed, 13 May 2020 14:04:01 -0400 Subject: [PATCH 29/46] Refactor cycle completion notification system --- adafruit_led_animation/animation.py | 355 +++------------------------- adafruit_led_animation/helper.py | 278 +++++++++++++++++++++- 2 files changed, 307 insertions(+), 326 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index ac7dd4f..032b38c 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -48,22 +48,25 @@ from math import ceil import adafruit_led_animation.helper from . import NANOS_PER_SECOND, monotonic_ns -from .color import BLACK, WHITE, RAINBOW, colorwheel +from .color import BLACK, RAINBOW, colorwheel __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" class Animation: + # pylint: disable=too-many-instance-attributes """ Base class for animations. """ + CYCLE_NOTIFICATIONS_SUPPORTED = False - # pylint: disable=too-many-arguments,too-many-instance-attributes + # pylint: disable=too-many-arguments def __init__(self, pixel_object, speed, color, peers=None, paused=False, name=None): self.pixel_object = pixel_object self.pixel_object.auto_write = False self.peers = peers if peers else [] + """A sequence of animations to trigger .draw() on when this animation draws.""" self._speed_ns = 0 self._color = None self._paused = paused @@ -71,9 +74,11 @@ def __init__(self, pixel_object, speed, color, peers=None, paused=False, name=No self._time_left_at_pause = 0 self.speed = speed # sets _speed_ns self.color = color # Triggers _recompute_color - self.done_cycle_handler = None self.name = name - self.counter = 0 + self.draw_count = 0 + """Number of animation frames drawn.""" + self.cycle_count = 0 + """Number of animation cycles completed.""" def __str__(self): return "" % (self.__class__.__name__, self.name) @@ -93,8 +98,9 @@ def animate(self): return False self.draw() - self.counter += 1 + self.draw_count += 1 + # Draw related animations together if self.peers: for peer in self.peers: peer.draw() @@ -172,13 +178,13 @@ def _recompute_color(self, color): Override as needed. """ - def _cycle_done(self): + def cycle_complete(self): """ Called by some animations when they complete an animation cycle. - Calls done_cycle_handler if one is set. + Animations that support cycle complete notifications will have X property set to False. + Override as needed. """ - if self.done_cycle_handler: - self.done_cycle_handler(self) # pylint: disable=not-callable + self.cycle_count += 1 def reset(self): """ @@ -195,12 +201,13 @@ class ColorCycle(Animation): :param colors: A list of colors to cycle through in ``(r, g, b)`` tuple, or ``0x000000`` hex format. Defaults to a rainbow color cycle. """ - def __init__(self, pixel_object, speed, colors=RAINBOW, name=None): self.colors = colors super(ColorCycle, self).__init__(pixel_object, speed, colors[0], name=name) self._generator = self._color_generator() + cycle_complete_supported = True + def draw(self): next(self._generator) self.pixel_object.fill(self.color) @@ -213,7 +220,7 @@ def _color_generator(self): yield index = (index + 1) % len(self.colors) if index == len(self.colors): - self._cycle_done() + self.cycle_complete() def reset(self): """ @@ -252,9 +259,6 @@ def __init__(self, pixel_object, color, name=None): def _recompute_color(self, color): self.colors = [color] - def _cycle_done(self): - pass - class Comet(Animation): """ @@ -293,6 +297,8 @@ def __init__( self._generator = self._comet_generator() super(Comet, self).__init__(pixel_object, speed, color, name=name) + cycle_complete_supported = True + def _recompute_color(self, color): pass @@ -338,7 +344,7 @@ def _comet_generator(self): if self.bounce: self.reverse = not self.reverse if not self.bounce or cycle_passes == 2: - self._cycle_done() + self.cycle_complete() cycle_passes = 0 def draw(self): @@ -453,6 +459,8 @@ def __init__(self, pixel_object, speed, color, period=5, name=None): self._generator = None self.reset() + cycle_complete_supported = True + def draw(self): color = next(self._generator) self.fill(color) @@ -485,6 +493,8 @@ def __init__(self, pixel_object, speed, period=5, name=None): self._period = period self._generator = self._color_wheel_generator() + cycle_complete_supported = True + def _color_wheel_generator(self): period = int(self._period * NANOS_PER_SECOND) @@ -497,7 +507,7 @@ def _color_wheel_generator(self): last_update = now pos = cycle_position = (cycle_position + time_since_last_draw) % period if pos < last_pos: - self._cycle_done() + self.cycle_complete() last_pos = pos wheel_index = int((pos / period) * 256) self.pixel_object[:] = [ @@ -612,6 +622,8 @@ def _resetter(): super(Chase, self).__init__(pixel_object, speed, color, name=name) + cycle_complete_supported = True + @property def reverse(self): """ @@ -645,7 +657,7 @@ def bar_colors(): self.pixel_object[:] = [next(colorgen) for _ in self.pixel_object] if self._offset == 0: - self._cycle_done() + self.cycle_complete() self._offset = (self._offset + self._direction) % self._repeat_width def bar_color(self, n, pixel_no=0): # pylint: disable=unused-argument @@ -696,311 +708,6 @@ def __init__(self, pixel_object, speed, size=2, spacing=3, reverse=False, name=N def bar_color(self, n, pixel_no=0): return self._colors[self._color_idx - n] - def _cycle_done(self): + def cycle_complete(self): self._color_idx = (self._color_idx + self._direction) % len(self._colors) - super(RainbowChase, self)._cycle_done() - - -class AnimationSequence: - """ - A sequence of Animations to run in succession, looping forever. - Advances manually or at the specified interval. - - :param members: The animation objects or groups. - :param int advance_interval: Time in seconds between animations if cycling - automatically. Defaults to ``None``. - :param bool auto_clear: Clear the pixels between animations. If ``True``, the current animation - will be cleared from the pixels before the next one starts. - Defaults to ``False``. - :param bool random_order: Activate the animations in a random order. Defaults to ``False``. - :param bool auto_reset: - - .. code-block:: python - - import board - import neopixel - from adafruit_led_animation.animation import AnimationSequence, Blink, Comet, Sparkle - import adafruit_led_animation.color as color - - strip_pixels = neopixel.NeoPixel(board.A1, 30, brightness=1, auto_write=False) - - blink = Blink(strip_pixels, 0.2, color.RED) - comet = Comet(strip_pixels, 0.1, color.BLUE) - sparkle = Sparkle(strip_pixels, 0.05, color.GREEN) - - animations = AnimationSequence(blink, comet, sparkle, advance_interval=1) - - while True: - animations.animate() - """ - - # pylint: disable=too-many-instance-attributes - def __init__(self, *members, advance_interval=None, auto_clear=False, random_order=False, - auto_reset=False): - self._members = members - self._advance_interval = ( - advance_interval * NANOS_PER_SECOND if advance_interval else None - ) - self._last_advance = monotonic_ns() - self._current = 0 - self._auto_clear = auto_clear - self._auto_reset = auto_reset - self.clear_color = BLACK - self._paused = False - self._paused_at = 0 - self._random = random_order - if random_order: - self._current = random.randint(0, len(self._members) - 1) - self._color = None - self.done_cycle_handler = None - for item in self._members: - item.done_cycle_handler = self.done_handler - - def done_handler(self, animation): - """ - Called when an animation sequence is done. - """ - - def _auto_advance(self): - if not self._advance_interval: - return - now = monotonic_ns() - if now - self._last_advance > self._advance_interval: - self._last_advance = now - if self._random: - self.random() - else: - self.next() - - def activate(self, index): - """ - Activates a specific animation. - """ - if isinstance(index, str): - self._current = [member.name for member in self._members].index(index) - else: - self._current = index - if self._auto_clear: - self.fill(self.clear_color) - if self._color: - self.current_animation.color = self._color - - def next(self): - """ - Jump to the next animation. - """ - current = self._current - self.activate((self._current + 1) % len(self._members)) - if self._auto_reset: - self.current_animation.reset() - if current > self._current: - self._cycle_done() - - def random(self): - """ - Jump to a random animation. - """ - self.activate(random.randint(0, len(self._members) - 1)) - - def animate(self): - """ - Call animate() from your code's main loop. It will draw the current animation - or go to the next animation based on the advance_interval if set. - - :return: True if the animation draw cycle was triggered, otherwise False. - """ - if not self._paused: - self._auto_advance() - return self.current_animation.animate() - - @property - def current_animation(self): - """ - Returns the current animation in the sequence. - """ - return self._members[self._current] - - @property - def color(self): - """ - Use this property to change the color of all animations in the sequence. - """ - return self._color - - @color.setter - def color(self, color): - self._color = color - self.current_animation.color = color - - def fill(self, color): - """ - Fills the current animation with a color. - """ - self.current_animation.fill(color) - - def freeze(self): - """ - Freeze the current animation in the sequence. - Also stops auto_advance. - """ - if self._paused: - return - self._paused = True - self._paused_at = monotonic_ns() - self.current_animation.freeze() - - def resume(self): - """ - Resume the current animation in the sequence, and resumes auto advance if enabled. - """ - if not self._paused: - return - self._paused = False - now = monotonic_ns() - self._last_advance += now - self._paused_at - self._paused_at = 0 - self.current_animation.resume() - - def animation_done(self, animation): - """ - Called by some animations when they finish a sequence. - """ - - def _cycle_done(self): - """ - Called when the (first) member animation cycles. - Calls done_cycle_handler if one is set. - """ - if self.done_cycle_handler: - self.done_cycle_handler(self) # pylint: disable=not-callable - - def reset(self): - """ - Resets the current animation. - """ - self.current_animation.reset() - - -class NotifiedAnimationSequence(AnimationSequence): - """Prints the current animation type (e.g. ``RainbowComet``, ``Chase``) and ``name`` (e.g. the - string from ``name=`` in the animation setup). Use for debugging when running multiple versions - of the same animation, or simply to print the names to the serial console. Used in the same - manner as ``AnimationSequence`` which is a sequence of Animations to run in succession, looping - forever. Advances manually or at the specified interval. - - :param members: The animation objects or groups. - :param int advance_interval: Time in seconds between animations if cycling - automatically. Defaults to ``None``. - :param random_order: Activate the animations in a random order. Defaults to ``False``. - - .. code-block:: python - - import board - import neopixel - from adafruit_led_animation.animation import NotifiedAnimationSequence, Blink, Comet - import adafruit_led_animation.color as color - - strip_pixels = neopixel.NeoPixel(board.A1, 30, brightness=1, auto_write=False) - - blink = Blink(strip_pixels, 0.2, color.RED, name="red-blink") - comet = Comet(strip_pixels, 0.1, color.BLUE, name="blue-comet") - - animations = NotifiedAnimationSequence(blink, comet, advance_interval=1) - - while True: - animations.animate() - """ - def activate(self, index): - super(NotifiedAnimationSequence, self).activate(index) - print("Activating:", self.current_animation) - - -class AnimationGroup: - """ - A group of animations that are active together. An example would be grouping a strip of - pixels connected to a board and the onboard LED. - - :param members: The animation objects or groups. - :param bool sync: Synchronises the timing of all members of the group to the settings of the - first member of the group. Defaults to ``False``. - - """ - - def __init__(self, *members, sync=False): - self._members = members - self._sync = sync - if sync: - main = members[0] - main.peers = members[1:] - # Register the done handler on the last animation. - self.done_cycle_handler = None - if not self._members: - return - self._members[-1].done_cycle_handler = self.done_handler - - def animate(self): - """ - Call animate() from your code's main loop. It will draw all of the animations - in the group. - - :return: True if any animation draw cycle was triggered, otherwise False. - """ - if self._sync: - return self._members[0].animate() - - return any([item.animate() for item in self._members]) - - @property - def color(self): - """ - Use this property to change the color of all members of the animation group. - """ - return None - - @color.setter - def color(self, color): - for item in self._members: - item.color = color - - def fill(self, color): - """ - Fills all pixel objects in the group with a color. - """ - for item in self._members: - item.fill(color) - - def freeze(self): - """ - Freeze all animations in the group. - """ - for item in self._members: - item.freeze() - - def resume(self): - """ - Resume all animations in the group. - """ - for item in self._members: - item.resume() - - def done_handler(self, animation): # pylint: disable=unused-argument - """ - Called by some animations when they complete a cycle. For an AnimationGroup this is the - first member of the group, if any. - """ - self._cycle_done() - - def _cycle_done(self): - """ - Called when the (first) member animation cycles. - Calls done_cycle_handler if one is set. - """ - if self.done_cycle_handler: - self.done_cycle_handler(self) # pylint: disable=not-callable - - def reset(self): - """ - Resets the animations in the group. - """ - for item in self._members: - item.reset() + super(RainbowChase, self).cycle_complete() diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index c752540..b8d3c5b 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -44,7 +44,9 @@ """ import math +import random from . import NANOS_PER_SECOND, monotonic_ns +from .color import BLACK class PixelMap: @@ -271,8 +273,7 @@ def pulse_generator(period: float, animation_object, white=False): last_update = now pos = cycle_position = (cycle_position + time_since_last_draw) % period if pos < last_pos: - if animation_object.done_cycle_handler: - animation_object.done_cycle_handler(animation_object) + animation_object.cycle_complete() last_pos = pos if pos > half_period: pos = period - pos @@ -283,3 +284,276 @@ def pulse_generator(period: float, animation_object, white=False): fill_color[1] = int(fill_color[1] * intensity) fill_color[2] = int(fill_color[2] * intensity) yield fill_color + + +def _wrap_cycle_complete(obj, animation): + if not animation.cycle_complete_supported: + raise NotImplementedError(animation + " does not support cycle_complete") + method = animation.cycle_complete + + def wrapper(): + method() + obj.cycle_complete() + + return wrapper + + +class AnimationGroup: + """ + A group of animations that are active together. An example would be grouping a strip of + pixels connected to a board and the onboard LED. + + :param members: The animation objects or groups. + :param bool sync: Synchronises the timing of all members of the group to the settings of the + first member of the group. Defaults to ``False``. + + """ + + def __init__(self, *members, sync=False): + if not members: + raise ValueError("At least one member required in an AnimationGroup") + + self._members = members + self._sync = sync + if sync: + main = members[0] + main.peers = members[1:] + + # Catch cycle_complete on the last animation. + self._members[-1].animation_cycle_done = _wrap_cycle_complete(self, self._members[-1]) + self.cycle_complete_supported = self._members[-1].cycle_complete_supported + + def cycle_complete(self): + """ + Called when the animation group is done the last animation in the sequence. + """ + + def animate(self): + """ + Call animate() from your code's main loop. It will draw all of the animations + in the group. + + :return: True if any animation draw cycle was triggered, otherwise False. + """ + if self._sync: + return self._members[0].animate() + + return any([item.animate() for item in self._members]) + + @property + def color(self): + """ + Use this property to change the color of all members of the animation group. + """ + return None + + @color.setter + def color(self, color): + for item in self._members: + item.color = color + + def fill(self, color): + """ + Fills all pixel objects in the group with a color. + """ + for item in self._members: + item.fill(color) + + def freeze(self): + """ + Freeze all animations in the group. + """ + for item in self._members: + item.freeze() + + def resume(self): + """ + Resume all animations in the group. + """ + for item in self._members: + item.resume() + + def reset(self): + """ + Resets the animations in the group. + """ + for item in self._members: + item.reset() + + +class AnimationSequence: + """ + A sequence of Animations to run in succession, looping forever. + Advances manually, or at the specified interval. + + :param members: The animation objects or groups. + :param int advance_interval: Time in seconds between animations if cycling + automatically. Defaults to ``None``. + :param bool auto_clear: Clear the pixels between animations. If ``True``, the current animation + will be cleared from the pixels before the next one starts. + Defaults to ``False``. + :param bool random_order: Activate the animations in a random order. Defaults to ``False``. + :param bool auto_reset: Automatically call reset() on animations when changing animations. + :param bool advance_on_cycle_complete: Automatically advance when `cycle_complete` is triggered + on member animations. All Animations must support + cycle_complete to use this. + .. code-block:: python + + import board + import neopixel + from adafruit_led_animation.animation import AnimationSequence, Blink, Comet, Sparkle + import adafruit_led_animation.color as color + + strip_pixels = neopixel.NeoPixel(board.A1, 30, brightness=1, auto_write=False) + + blink = Blink(strip_pixels, 0.2, color.RED) + comet = Comet(strip_pixels, 0.1, color.BLUE) + sparkle = Sparkle(strip_pixels, 0.05, color.GREEN) + + animations = AnimationSequence(blink, comet, sparkle, advance_interval=1) + + while True: + animations.animate() + """ + + # pylint: disable=too-many-instance-attributes + def __init__(self, *members, advance_interval=None, auto_clear=False, random_order=False, + auto_reset=False, advance_on_cycle_complete=False): + self._members = members + self._advance_interval = ( + advance_interval * NANOS_PER_SECOND if advance_interval else None + ) + self._last_advance = monotonic_ns() + self._current = 0 + self.auto_clear = auto_clear + self.auto_reset = auto_reset + self.advance_on_cycle_complete = advance_on_cycle_complete + self.clear_color = BLACK + self._paused = False + self._paused_at = 0 + self._random = random_order + if random_order: + self._current = random.randint(0, len(self._members) - 1) + self._color = None + + for item in self._members: + item.animation_cycle_done = _wrap_cycle_complete(self, item) + + cycle_complete_supported = True + + def cycle_complete(self): + """ + Called when the animation sequence is done all animations in the sequence. + Advances the animation if advance_on_cycle_complete is True. + """ + if self.advance_on_cycle_complete: + self._advance() + + def _auto_advance(self): + if not self._advance_interval: + return + now = monotonic_ns() + if now - self._last_advance > self._advance_interval: + self._last_advance = now + self._advance() + + def _advance(self): + if self._random: + self.random() + else: + self.next() + + def activate(self, index): + """ + Activates a specific animation. + """ + if isinstance(index, str): + self._current = [member.name for member in self._members].index(index) + else: + self._current = index + if self.auto_clear: + self.fill(self.clear_color) + if self._color: + self.current_animation.color = self._color + + def next(self): + """ + Jump to the next animation. + """ + current = self._current + self.activate((self._current + 1) % len(self._members)) + if self.auto_reset: + self.current_animation.reset() + if current > self._current: + self.cycle_complete() + + def random(self): + """ + Jump to a random animation. + """ + self.activate(random.randint(0, len(self._members) - 1)) + + def animate(self): + """ + Call animate() from your code's main loop. It will draw the current animation + or go to the next animation based on the advance_interval if set. + + :return: True if the animation draw cycle was triggered, otherwise False. + """ + if not self._paused: + self._auto_advance() + return self.current_animation.animate() + + @property + def current_animation(self): + """ + Returns the current animation in the sequence. + """ + return self._members[self._current] + + @property + def color(self): + """ + Use this property to change the color of all animations in the sequence. + """ + return self._color + + @color.setter + def color(self, color): + self._color = color + self.current_animation.color = color + + def fill(self, color): + """ + Fills the current animation with a color. + """ + self.current_animation.fill(color) + + def freeze(self): + """ + Freeze the current animation in the sequence. + Also stops auto_advance. + """ + if self._paused: + return + self._paused = True + self._paused_at = monotonic_ns() + self.current_animation.freeze() + + def resume(self): + """ + Resume the current animation in the sequence, and resumes auto advance if enabled. + """ + if not self._paused: + return + self._paused = False + now = monotonic_ns() + self._last_advance += now - self._paused_at + self._paused_at = 0 + self.current_animation.resume() + + def reset(self): + """ + Resets the current animation. + """ + self.current_animation.reset() From 12ccedae08b2ee542ad9ccf61af1384d37ed599b Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Wed, 13 May 2020 14:45:41 -0400 Subject: [PATCH 30/46] fix auto advance --- adafruit_led_animation/helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index b8d3c5b..48231e0 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -455,7 +455,7 @@ def _auto_advance(self): now = monotonic_ns() if now - self._last_advance > self._advance_interval: self._last_advance = now - self._advance() + self._advance() def _advance(self): if self._random: From 00133ed0fc5685b20f9afe127f7f1f817f64f450 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Wed, 13 May 2020 18:35:01 -0400 Subject: [PATCH 31/46] refactor group and sequence notifications, add the ability to loop multiple times to groups, sequences, and animations --- adafruit_led_animation/animation.py | 38 ++++++++++----- adafruit_led_animation/helper.py | 73 ++++++++++++++++++++--------- 2 files changed, 78 insertions(+), 33 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 032b38c..50a0586 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -72,9 +72,12 @@ def __init__(self, pixel_object, speed, color, peers=None, paused=False, name=No self._paused = paused self._next_update = monotonic_ns() self._time_left_at_pause = 0 + self._also_notify = [] self.speed = speed # sets _speed_ns self.color = color # Triggers _recompute_color self.name = name + self.notify_cycles = 1 + """Number of cycles to trigger additional cycle_done notifications after""" self.draw_count = 0 """Number of animation frames drawn.""" self.cycle_count = 0 @@ -185,6 +188,17 @@ def cycle_complete(self): Override as needed. """ self.cycle_count += 1 + if self.cycle_count % self.notify_cycles == 0: + for callback in self._also_notify: + callback(self) + + def add_cycle_complete_receiver(self, callback): + """ + Adds an additional callback when the cycle completes. + :param callback: Additional callback to trigger when a cycle completes. The callback + is passed the animation object instance. + """ + self._also_notify.append(callback) def reset(self): """ @@ -203,7 +217,7 @@ class ColorCycle(Animation): """ def __init__(self, pixel_object, speed, colors=RAINBOW, name=None): self.colors = colors - super(ColorCycle, self).__init__(pixel_object, speed, colors[0], name=name) + super().__init__(pixel_object, speed, colors[0], name=name) self._generator = self._color_generator() cycle_complete_supported = True @@ -239,7 +253,7 @@ class Blink(ColorCycle): """ def __init__(self, pixel_object, speed, color, name=None): - super(Blink, self).__init__(pixel_object, speed, [color, BLACK], name=name) + super().__init__(pixel_object, speed, [color, BLACK], name=name) def _recompute_color(self, color): self.colors = [color, BLACK] @@ -254,7 +268,7 @@ class Solid(ColorCycle): """ def __init__(self, pixel_object, color, name=None): - super(Solid, self).__init__(pixel_object, speed=1, colors=[color], name=name) + super().__init__(pixel_object, speed=1, colors=[color], name=name) def _recompute_color(self, color): self.colors = [color] @@ -295,7 +309,7 @@ def __init__( self.bounce = bounce self._computed_color = color self._generator = self._comet_generator() - super(Comet, self).__init__(pixel_object, speed, color, name=name) + super().__init__(pixel_object, speed, color, name=name) cycle_complete_supported = True @@ -415,7 +429,7 @@ def __init__(self, pixel_object, speed, color, num_sparkles=1, name=None): self._half_color = None self._dim_color = None self._num_sparkles = num_sparkles - super(Sparkle, self).__init__(pixel_object, speed, color, name=name) + super().__init__(pixel_object, speed, color, name=name) def _recompute_color(self, color): half_color = tuple(color[rgb] // 4 for rgb in range(len(color))) @@ -454,7 +468,7 @@ class Pulse(Animation): # pylint: disable=too-many-arguments def __init__(self, pixel_object, speed, color, period=5, name=None): - super(Pulse, self).__init__(pixel_object, speed, color, name=name) + super().__init__(pixel_object, speed, color, name=name) self._period = period self._generator = None self.reset() @@ -489,7 +503,7 @@ class Rainbow(Animation): # pylint: disable=too-many-arguments def __init__(self, pixel_object, speed, period=5, name=None): - super(Rainbow, self).__init__(pixel_object, speed, BLACK, name=name) + super().__init__(pixel_object, speed, BLACK, name=name) self._period = period self._generator = self._color_wheel_generator() @@ -556,7 +570,7 @@ def __init__( self._cycle_position = 0 self._half_color = None self._dim_color = None - super(SparklePulse, self).__init__(pixel_object, speed, color) + super().__init__(pixel_object, speed, color) def _recompute_color(self, color): half_color = tuple(color[rgb] // 4 for rgb in range(len(color))) @@ -620,7 +634,7 @@ def _resetter(): self._reset = _resetter - super(Chase, self).__init__(pixel_object, speed, color, name=name) + super().__init__(pixel_object, speed, color, name=name) cycle_complete_supported = True @@ -656,7 +670,7 @@ def bar_colors(): colorgen = bar_colors() self.pixel_object[:] = [next(colorgen) for _ in self.pixel_object] - if self._offset == 0: + if self.draw_count % len(self.pixel_object) == 0: self.cycle_complete() self._offset = (self._offset + self._direction) % self._repeat_width @@ -703,11 +717,11 @@ def __init__(self, pixel_object, speed, size=2, spacing=3, reverse=False, name=N self._num_colors = 256 // wheel_step self._colors = [colorwheel(n % 256) for n in range(0, 512, wheel_step)] self._color_idx = 0 - super(RainbowChase, self).__init__(pixel_object, speed, 0, size, spacing, reverse, name) + super().__init__(pixel_object, speed, 0, size, spacing, reverse, name) def bar_color(self, n, pixel_no=0): return self._colors[self._color_idx - n] def cycle_complete(self): self._color_idx = (self._color_idx + self._direction) % len(self._colors) - super(RainbowChase, self).cycle_complete() + super().cycle_complete() diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 48231e0..fdb1412 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -286,18 +286,6 @@ def pulse_generator(period: float, animation_object, white=False): yield fill_color -def _wrap_cycle_complete(obj, animation): - if not animation.cycle_complete_supported: - raise NotImplementedError(animation + " does not support cycle_complete") - method = animation.cycle_complete - - def wrapper(): - method() - obj.cycle_complete() - - return wrapper - - class AnimationGroup: """ A group of animations that are active together. An example would be grouping a strip of @@ -312,21 +300,45 @@ class AnimationGroup: def __init__(self, *members, sync=False): if not members: raise ValueError("At least one member required in an AnimationGroup") - - self._members = members + self.draw_count = 0 + """Number of animation frames drawn.""" + self.cycle_count = 0 + """Number of animation cycles completed.""" + self.notify_cycles = 1 + """Number of cycles to trigger additional cycle_done notifications after""" + self._members = list(members) self._sync = sync + self._also_notify = [] + self.cycle_count = 0 if sync: main = members[0] main.peers = members[1:] # Catch cycle_complete on the last animation. - self._members[-1].animation_cycle_done = _wrap_cycle_complete(self, self._members[-1]) + self._members[-1].add_cycle_complete_receiver(self._group_done) self.cycle_complete_supported = self._members[-1].cycle_complete_supported + def _group_done(self, animation): # pylint: disable=unused-argument + self.cycle_complete() + def cycle_complete(self): """ - Called when the animation group is done the last animation in the sequence. + Called by some animations when they complete an animation cycle. + Animations that support cycle complete notifications will have X property set to False. + Override as needed. """ + self.cycle_count += 1 + if self.cycle_count % self.notify_cycles == 0: + for callback in self._also_notify: + callback(self) + + def add_cycle_complete_receiver(self, callback): + """ + Adds an additional callback when the cycle completes. + :param callback: Additional callback to trigger when a cycle completes. The callback + is passed the animation object instance. + """ + self._also_notify.append(callback) def animate(self): """ @@ -432,23 +444,42 @@ def __init__(self, *members, advance_interval=None, auto_clear=False, random_ord self._paused = False self._paused_at = 0 self._random = random_order + self._also_notify = [] + self.cycle_count = 0 + self.notify_cycles = 1 if random_order: self._current = random.randint(0, len(self._members) - 1) self._color = None - - for item in self._members: - item.animation_cycle_done = _wrap_cycle_complete(self, item) + for member in self._members: + member.add_cycle_complete_receiver(self._sequence_complete) + self.cycle_complete_supported = self._members[-1].cycle_complete_supported cycle_complete_supported = True def cycle_complete(self): """ - Called when the animation sequence is done all animations in the sequence. - Advances the animation if advance_on_cycle_complete is True. + Called by some animations when they complete an animation cycle. + Animations that support cycle complete notifications will have X property set to False. + Override as needed. """ + self.cycle_count += 1 + if self.cycle_count % self.notify_cycles == 0: + for callback in self._also_notify: + callback(self) + + def _sequence_complete(self, animation): # pylint: disable=unused-argument + self.cycle_complete() if self.advance_on_cycle_complete: self._advance() + def add_cycle_complete_receiver(self, callback): + """ + Adds an additional callback when the cycle completes. + :param callback: Additional callback to trigger when a cycle completes. The callback + is passed the animation object instance. + """ + self._also_notify.append(callback) + def _auto_advance(self): if not self._advance_interval: return From 4399fb490e63124af5da1cf0b58c6f4a03df6487 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Wed, 13 May 2020 19:09:25 -0400 Subject: [PATCH 32/46] bugfixes --- adafruit_led_animation/animation.py | 5 +++-- adafruit_led_animation/helper.py | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 50a0586..6026eba 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -219,13 +219,14 @@ def __init__(self, pixel_object, speed, colors=RAINBOW, name=None): self.colors = colors super().__init__(pixel_object, speed, colors[0], name=name) self._generator = self._color_generator() + next(self._generator) cycle_complete_supported = True def draw(self): - next(self._generator) self.pixel_object.fill(self.color) self.show() + next(self._generator) def _color_generator(self): index = 0 @@ -233,7 +234,7 @@ def _color_generator(self): self._color = self.colors[index] yield index = (index + 1) % len(self.colors) - if index == len(self.colors): + if index == 0: self.cycle_complete() def reset(self): diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index fdb1412..6d204d6 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -502,21 +502,21 @@ def activate(self, index): self._current = [member.name for member in self._members].index(index) else: self._current = index - if self.auto_clear: - self.fill(self.clear_color) if self._color: self.current_animation.color = self._color + if self.auto_clear: + self.fill(self.clear_color) def next(self): """ Jump to the next animation. """ current = self._current + if current > self._current: + self.cycle_complete() self.activate((self._current + 1) % len(self._members)) if self.auto_reset: self.current_animation.reset() - if current > self._current: - self.cycle_complete() def random(self): """ From a81ee24dcec381797adcbe244a0e6fb5916d8673 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Wed, 13 May 2020 20:03:53 -0400 Subject: [PATCH 33/46] fix a bug --- adafruit_led_animation/animation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 6026eba..a4c958d 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -486,7 +486,7 @@ def reset(self): Resets the animation. """ white = len(self.pixel_object[0]) > 3 and isinstance( - self.pixel_object[0][-1], float + self.pixel_object[0][-1], int ) self._generator = adafruit_led_animation.helper.pulse_generator( self._period, self, white From ab085fd630d059fb0f67e0c0904fd4c62b7b9436 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Thu, 14 May 2020 14:44:31 -0400 Subject: [PATCH 34/46] fix various bugs, duplicate show calls, and other fixes --- adafruit_led_animation/animation.py | 13 +++++---- adafruit_led_animation/helper.py | 43 +++++++++++++++++++++++------ 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index a4c958d..0f64359 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -84,7 +84,7 @@ def __init__(self, pixel_object, speed, color, peers=None, paused=False, name=No """Number of animation cycles completed.""" def __str__(self): - return "" % (self.__class__.__name__, self.name) + return "<%s: %s>" % (self.__class__.__name__, self.name) def animate(self): """ @@ -107,9 +107,6 @@ def animate(self): if self.peers: for peer in self.peers: peer.draw() - self.show() - for peer in self.peers: - peer.show() self._next_update = now + self._speed_ns return True @@ -117,6 +114,7 @@ def animate(self): def draw(self): """ Animation subclasses must implement draw() to render the animation sequence. + Draw must call show(). """ raise NotImplementedError() @@ -146,7 +144,6 @@ def fill(self, color): Fills the pixel object with a color. """ self.pixel_object.fill(color) - self.show() @property def color(self): @@ -517,12 +514,13 @@ def _color_wheel_generator(self): cycle_position = 0 last_pos = 0 while True: + cycle_completed = False now = monotonic_ns() time_since_last_draw = now - last_update last_update = now pos = cycle_position = (cycle_position + time_since_last_draw) % period if pos < last_pos: - self.cycle_complete() + cycle_completed = True last_pos = pos wheel_index = int((pos / period) * 256) self.pixel_object[:] = [ @@ -530,6 +528,8 @@ def _color_wheel_generator(self): for i, _ in enumerate(self.pixel_object) ] self.show() + if cycle_completed: + self.cycle_complete() yield def draw(self): @@ -670,6 +670,7 @@ def bar_colors(): colorgen = bar_colors() self.pixel_object[:] = [next(colorgen) for _ in self.pixel_object] + self.show() if self.draw_count % len(self.pixel_object) == 0: self.cycle_complete() diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 6d204d6..c6d4e69 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -45,6 +45,7 @@ import math import random + from . import NANOS_PER_SECOND, monotonic_ns from .color import BLACK @@ -104,7 +105,7 @@ def __setitem__(self, index, val): else: self._set_pixels(index, val) - if self._pixels.auto_write: + if not self._pixels.auto_write: self.show() def __getitem__(self, index): @@ -202,7 +203,7 @@ def __setitem__(self, index, val): else: self._pixels[index + self._start] = val - if self._pixels.auto_write: + if not self._pixels.auto_write: self.show() def __getitem__(self, index): @@ -297,7 +298,7 @@ class AnimationGroup: """ - def __init__(self, *members, sync=False): + def __init__(self, *members, sync=False, name=None): if not members: raise ValueError("At least one member required in an AnimationGroup") self.draw_count = 0 @@ -310,6 +311,7 @@ def __init__(self, *members, sync=False): self._sync = sync self._also_notify = [] self.cycle_count = 0 + self.name = name if sync: main = members[0] main.peers = members[1:] @@ -318,6 +320,9 @@ def __init__(self, *members, sync=False): self._members[-1].add_cycle_complete_receiver(self._group_done) self.cycle_complete_supported = self._members[-1].cycle_complete_supported + def __str__(self): + return "" % (self.__class__.__name__, self.name) + def _group_done(self, animation): # pylint: disable=unused-argument self.cycle_complete() @@ -392,6 +397,13 @@ def reset(self): for item in self._members: item.reset() + def show(self): + """ + Draws the current animation group members. + """ + for item in self._members: + item.show() + class AnimationSequence: """ @@ -430,7 +442,9 @@ class AnimationSequence: # pylint: disable=too-many-instance-attributes def __init__(self, *members, advance_interval=None, auto_clear=False, random_order=False, - auto_reset=False, advance_on_cycle_complete=False): + auto_reset=False, advance_on_cycle_complete=False, name=None): + if advance_interval and advance_on_cycle_complete: + raise ValueError("Cannot use both advance_interval and auto_clear") self._members = members self._advance_interval = ( advance_interval * NANOS_PER_SECOND if advance_interval else None @@ -447,6 +461,7 @@ def __init__(self, *members, advance_interval=None, auto_clear=False, random_ord self._also_notify = [] self.cycle_count = 0 self.notify_cycles = 1 + self.name = name if random_order: self._current = random.randint(0, len(self._members) - 1) self._color = None @@ -456,6 +471,9 @@ def __init__(self, *members, advance_interval=None, auto_clear=False, random_ord cycle_complete_supported = True + def __str__(self): + return "<%s: %s>" % (self.__class__.__name__, self.name) + def cycle_complete(self): """ Called by some animations when they complete an animation cycle. @@ -489,6 +507,11 @@ def _auto_advance(self): self._advance() def _advance(self): + if self.auto_reset: + self.current_animation.reset() + if self.auto_clear: + self.current_animation.fill(self.clear_color) + self.current_animation.show() if self._random: self.random() else: @@ -504,8 +527,6 @@ def activate(self, index): self._current = index if self._color: self.current_animation.color = self._color - if self.auto_clear: - self.fill(self.clear_color) def next(self): """ @@ -515,8 +536,6 @@ def next(self): if current > self._current: self.cycle_complete() self.activate((self._current + 1) % len(self._members)) - if self.auto_reset: - self.current_animation.reset() def random(self): """ @@ -531,7 +550,7 @@ def animate(self): :return: True if the animation draw cycle was triggered, otherwise False. """ - if not self._paused: + if not self._paused and self._advance_interval: self._auto_advance() return self.current_animation.animate() @@ -588,3 +607,9 @@ def reset(self): Resets the current animation. """ self.current_animation.reset() + + def show(self): + """ + Draws the current animation group members. + """ + self.current_animation.show() From 5bd6bfe5a9b3c1bfb537dd597ad5cb2d97b88ccd Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Thu, 14 May 2020 17:02:02 -0400 Subject: [PATCH 35/46] Update simpletest to new API. --- examples/led_animation_simpletest.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/examples/led_animation_simpletest.py b/examples/led_animation_simpletest.py index 8ee3e77..2db1da0 100644 --- a/examples/led_animation_simpletest.py +++ b/examples/led_animation_simpletest.py @@ -1,15 +1,26 @@ """ -Example animation sequence. +This example repeatedly displays two animations, Comet and Chase, at a five second interval. + +Designed for NeoPixel FeatherWing. Update pixel_pin and pixel_num to match your wiring if using +a different form of NeoPixels. """ import board import neopixel -from adafruit_led_animation.animation import Comet, AnimationSequence, Chase +from adafruit_led_animation.animation import Comet, Chase +from adafruit_led_animation.helper import AnimationSequence from adafruit_led_animation.color import PURPLE, WHITE -pixels = neopixel.NeoPixel(board.D6, 32, brightness=0.2, auto_write=False) +# Update to match the pin connected to your NeoPixels +pixel_pin = board.D6 +# Update to match the number of NeoPixels you have connected +pixel_num = 32 + +pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.2, auto_write=False) + comet = Comet(pixels, speed=0.01, color=PURPLE, tail_length=10, bounce=True) chase = Chase(pixels, speed=0.1, size=3, spacing=6, color=WHITE) -animations = AnimationSequence(comet, chase, advance_interval=15) + +animations = AnimationSequence(comet, chase, advance_interval=5) while True: animations.animate() From cf4a1fe0dc20cb2e77938811bf86435c4fcfb5c6 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Thu, 14 May 2020 17:46:30 -0400 Subject: [PATCH 36/46] Make generics to help with vertical and horizontal lines. --- adafruit_led_animation/helper.py | 79 ++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index c6d4e69..2cf4c7d 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -165,6 +165,85 @@ def auto_write(self): def auto_write(self, value): self._pixels.auto_write = value + @classmethod + def vertical_lines(cls, pixels, width, height, gridmapper): + """ + Generate a PixelMap of horizontal lines on a strip arranged in a grid. + + :param pixels: pixel object + :param width: width of grid + :param height: height of grid + :param gridmapper: a function to map x and y coordinates to the grid + see vertical_strip_gridmap and horizontal_strip_gridmap + :return: PixelMap + + Example: Vertical lines on a 32x8 grid with the pixel rows oriented vertically, + alternating direction every row. + + .. code-block:: python + PixelMap.vertical_lines(pixels, 32, 8, vertical_strip_gridmap(8)) + + """ + if len(pixels) < width * height: + raise ValueError("number of pixels is less than width x height") + mapping = [] + for x in range(width): + mapping.append([gridmapper(x, y) for y in range(height)]) + return cls(pixels, mapping, individual_pixels=True) + + @classmethod + def horizontal_lines(cls, pixels, width, height, gridmapper): + """ + Generate a PixelMap of horizontal lines on a strip arranged in a grid. + + :param pixels: pixel object + :param width: width of grid + :param height: height of grid + :param gridmapper: a function to map x and y coordinates to the grid + see vertical_strip_gridmap and horizontal_strip_gridmap + :return: PixelMap + + Example: Horizontal lines on a 16x16 grid with the pixel rows oriented vertically, + alternating direction every row. + + .. code-block:: python + PixelMap.horizontal_lines(pixels, 16, 16, vertical_strip_gridmap(16)) + """ + if len(pixels) < width * height: + raise ValueError("number of pixels is less than width x height") + mapping = [] + for y in range(height): + mapping.append([gridmapper(x, y) for x in range(width)]) + return cls(pixels, mapping, individual_pixels=True) + + +def vertical_strip_gridmap(height, alternating=True): + """ + Returns a function that determines the pixel number for a grid with strips arranged vertically. + :param height: strip height in pixels + :param alternating: strips alternate directions in a zigzag + :return: mapper(x, y) + """ + def mapper(x, y): + if alternating and x % 2: + return x * height + (height - 1 - y) + return x * height + y + return mapper + + +def horizontal_strip_gridmap(width, alternating=True): + """ + Determines the pixel number for a grid with strips arranged horizontally. + :param width: strip width in pixels + :param alternating: strips alternate directions in a zigzag + :return: mapper(x, y) + """ + def mapper(x, y): + if alternating and y % 2: + return y * width + (width - 1 - x) + return y * width + x + return mapper + class PixelSubset: """ From 496e46c89b7b0bed82599a19275bb1a97b9c2d08 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Thu, 14 May 2020 17:51:57 -0400 Subject: [PATCH 37/46] reformat code to comply with black --- adafruit_led_animation/animation.py | 40 ++++++++++++++++++++++------- adafruit_led_animation/helper.py | 16 ++++++++++-- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 0f64359..6dbd8de 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -212,6 +212,7 @@ class ColorCycle(Animation): :param colors: A list of colors to cycle through in ``(r, g, b)`` tuple, or ``0x000000`` hex format. Defaults to a rainbow color cycle. """ + def __init__(self, pixel_object, speed, colors=RAINBOW, name=None): self.colors = colors super().__init__(pixel_object, speed, colors[0], name=name) @@ -386,24 +387,37 @@ class RainbowComet(Comet): """ # pylint: disable=too-many-arguments - def __init__(self, pixel_object, speed, tail_length=10, reverse=False, bounce=False, - colorwheel_offset=0, name=None): + def __init__( + self, + pixel_object, + speed, + tail_length=10, + reverse=False, + bounce=False, + colorwheel_offset=0, + name=None, + ): self._colorwheel_is_tuple = isinstance(colorwheel(0), tuple) self._colorwheel_offset = colorwheel_offset super().__init__(pixel_object, speed, 0, tail_length, reverse, bounce, name) def _calc_brightness(self, n, color): - brightness = ((n * self._color_step) + self._color_offset) + brightness = (n * self._color_step) + self._color_offset if not self._colorwheel_is_tuple: - color = (color & 0xff, ((color & 0xff00) >> 8), (color >> 16)) + color = (color & 0xFF, ((color & 0xFF00) >> 8), (color >> 16)) return [int(i * brightness) for i in color] def __recompute_color(self, color): factor = int(256 / self._tail_length) self._comet_colors = [BLACK] + [ - self._calc_brightness(n, colorwheel(int( - (n * factor) + self._color_offset + self._colorwheel_offset) % 256)) + self._calc_brightness( + n, + colorwheel( + int((n * factor) + self._color_offset + self._colorwheel_offset) + % 256 + ), + ) for n in range(self._tail_length - 1) ] self._reverse_comet_colors = list(reversed(self._comet_colors)) @@ -652,7 +666,6 @@ def reverse(self, value): self._direction = -1 if self._reverse else 1 def draw(self): - def bar_colors(): bar_no = 0 for i in range(self._offset, 0, -1): @@ -713,9 +726,18 @@ class RainbowChase(Chase): :param reverse: Reverse direction of movement. :param wheel_step: How many colors to skip in `colorwheel` per bar (default 8) """ + # pylint: disable=too-many-arguments - def __init__(self, pixel_object, speed, size=2, spacing=3, reverse=False, name=None, - wheel_step=8): + def __init__( + self, + pixel_object, + speed, + size=2, + spacing=3, + reverse=False, + name=None, + wheel_step=8, + ): self._num_colors = 256 // wheel_step self._colors = [colorwheel(n % 256) for n in range(0, 512, wheel_step)] self._color_idx = 0 diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 2cf4c7d..5b93292 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -224,10 +224,12 @@ def vertical_strip_gridmap(height, alternating=True): :param alternating: strips alternate directions in a zigzag :return: mapper(x, y) """ + def mapper(x, y): if alternating and x % 2: return x * height + (height - 1 - y) return x * height + y + return mapper @@ -238,10 +240,12 @@ def horizontal_strip_gridmap(width, alternating=True): :param alternating: strips alternate directions in a zigzag :return: mapper(x, y) """ + def mapper(x, y): if alternating and y % 2: return y * width + (width - 1 - x) return y * width + x + return mapper @@ -520,8 +524,16 @@ class AnimationSequence: """ # pylint: disable=too-many-instance-attributes - def __init__(self, *members, advance_interval=None, auto_clear=False, random_order=False, - auto_reset=False, advance_on_cycle_complete=False, name=None): + def __init__( + self, + *members, + advance_interval=None, + auto_clear=False, + random_order=False, + auto_reset=False, + advance_on_cycle_complete=False, + name=None + ): if advance_interval and advance_on_cycle_complete: raise ValueError("Cannot use both advance_interval and auto_clear") self._members = members From 6dd47d552fac00849f2c1c00268169f58a790b47 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Thu, 14 May 2020 18:08:13 -0400 Subject: [PATCH 38/46] Appease the Sphinx. --- adafruit_led_animation/animation.py | 1 + adafruit_led_animation/helper.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 6dbd8de..6628870 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -192,6 +192,7 @@ def cycle_complete(self): def add_cycle_complete_receiver(self, callback): """ Adds an additional callback when the cycle completes. + :param callback: Additional callback to trigger when a cycle completes. The callback is passed the animation object instance. """ diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 5b93292..343211f 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -181,6 +181,7 @@ def vertical_lines(cls, pixels, width, height, gridmapper): alternating direction every row. .. code-block:: python + PixelMap.vertical_lines(pixels, 32, 8, vertical_strip_gridmap(8)) """ @@ -207,6 +208,7 @@ def horizontal_lines(cls, pixels, width, height, gridmapper): alternating direction every row. .. code-block:: python + PixelMap.horizontal_lines(pixels, 16, 16, vertical_strip_gridmap(16)) """ if len(pixels) < width * height: @@ -423,6 +425,7 @@ def cycle_complete(self): def add_cycle_complete_receiver(self, callback): """ Adds an additional callback when the cycle completes. + :param callback: Additional callback to trigger when a cycle completes. The callback is passed the animation object instance. """ @@ -504,6 +507,7 @@ class AnimationSequence: :param bool advance_on_cycle_complete: Automatically advance when `cycle_complete` is triggered on member animations. All Animations must support cycle_complete to use this. + .. code-block:: python import board @@ -584,6 +588,7 @@ def _sequence_complete(self, animation): # pylint: disable=unused-argument def add_cycle_complete_receiver(self, callback): """ Adds an additional callback when the cycle completes. + :param callback: Additional callback to trigger when a cycle completes. The callback is passed the animation object instance. """ From ee35ea8763adda5e1e7ce9e2797e46405744efa3 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Thu, 14 May 2020 19:52:56 -0400 Subject: [PATCH 39/46] fix error related to cycle_complete_supported in some animations --- adafruit_led_animation/animation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 6628870..fc81102 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -59,7 +59,7 @@ class Animation: """ Base class for animations. """ - CYCLE_NOTIFICATIONS_SUPPORTED = False + cycle_complete_supported = False # pylint: disable=too-many-arguments def __init__(self, pixel_object, speed, color, peers=None, paused=False, name=None): From ed4bbf9c53523ca0d67c1b4a080103c553a28370 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Thu, 14 May 2020 20:17:45 -0400 Subject: [PATCH 40/46] Examples and docs. --- adafruit_led_animation/helper.py | 41 +++++++++++++++---- examples/led_animation_all_animations.py | 48 +++++++++++++++++++++++ examples/led_animation_gridmap.py | 50 ++++++++++++++++++++++++ examples/led_animation_simpletest.py | 5 ++- 4 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 examples/led_animation_all_animations.py create mode 100644 examples/led_animation_gridmap.py diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 343211f..0d353ff 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -58,20 +58,42 @@ class PixelMap: :param iterable pixel_ranges: Pixel ranges (or individual pixels). :param bool individual_pixels: Whether pixel_ranges are individual pixels. + To use with ranges of pixels: + .. code-block:: python import board import neopixel from adafruit_led_animation.helper import PixelMap - pixels = neopixel.NeoPixel(board.D12, 307, auto_write=False) + pixels = neopixel.NeoPixel(board.D6, 32, auto_write=False) - tree = PixelMap(pixels, [ - (0, 21), (21, 48), (48, 71), (71, 93),(93, 115), (115, 135), (135, 153), - (153, 170), (170, 188), (188, 203), (203, 217), (217, 228), (228, 240), - (240, 247), (247, 253), (253, 256), (256, 260), (260, 307)] - ) - tree[0] = (255, 255, 0) - tree.show() + pixel_wing_horizontal = PixelMap(pixels, [(0, 8), (8, 16), (16, 24), (24, 32)]) + + pixel_wing_horizontal[0] = (255, 255, 0) + pixel_wing_horizontal.show() + + To use with individual pixels: + + .. code-block:: python + + import board + import neopixel + from adafruit_led_animation.helper import PixelMap + pixels = neopixel.NeoPixel(board.D6, 32, auto_write=False) + + pixel_wing_vertical = PixelMap(pixels, [ + (0, 8, 16, 24), + (1, 9, 17, 25), + (2, 10, 18, 26), + (3, 11, 19, 27), + (4, 12, 20, 28), + (5, 13, 21, 29), + (6, 14, 22, 30), + (7, 15, 23, 31), + ], individual_pixels=True) + + pixel_wing_vertical[0] = (255, 255, 0) + pixel_wing_vertical.show() """ @@ -138,6 +160,7 @@ def brightness(self, brightness): def fill(self, color): """ Fill the used pixel ranges with color. + :param color: Color to fill all pixels referenced by this PixelMap definition with. """ if self._individual_pixels: @@ -222,6 +245,7 @@ def horizontal_lines(cls, pixels, width, height, gridmapper): def vertical_strip_gridmap(height, alternating=True): """ Returns a function that determines the pixel number for a grid with strips arranged vertically. + :param height: strip height in pixels :param alternating: strips alternate directions in a zigzag :return: mapper(x, y) @@ -238,6 +262,7 @@ def mapper(x, y): def horizontal_strip_gridmap(width, alternating=True): """ Determines the pixel number for a grid with strips arranged horizontally. + :param width: strip width in pixels :param alternating: strips alternate directions in a zigzag :return: mapper(x, y) diff --git a/examples/led_animation_all_animations.py b/examples/led_animation_all_animations.py new file mode 100644 index 0000000..e230d55 --- /dev/null +++ b/examples/led_animation_all_animations.py @@ -0,0 +1,48 @@ +""" +This example repeatedly displays all available animations, at a five second interval. + +For NeoPixel FeatherWing. Update pixel_pin and pixel_num to match your wiring if using +a different form of NeoPixels. +""" +import board +import neopixel +from adafruit_led_animation.animation import * +from adafruit_led_animation.helper import AnimationSequence +from adafruit_led_animation.color import PURPLE, WHITE, AMBER, JADE + +# Update to match the pin connected to your NeoPixels +pixel_pin = board.D6 +# Update to match the number of NeoPixels you have connected +pixel_num = 32 + +pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.2, auto_write=False) + +blink = Blink(pixels, speed=0.1, color=JADE) +comet = Comet(pixels, speed=0.01, color=PURPLE, tail_length=10, bounce=True) +chase = Chase(pixels, speed=0.1, size=3, spacing=6, color=WHITE) +pulse = Pulse(pixels, speed=0.1, period=3, color=AMBER) +sparkle = Sparkle(pixels, speed=0.1, color=PURPLE, num_sparkles=10) +solid = Solid(pixels, color=JADE) +rainbow = Rainbow(pixels, speed=0.1, period=2) +sparkle_pulse = SparklePulse(pixels, speed=0.1, period=3, color=JADE) +rainbow_comet = RainbowComet(pixels, speed=0.1, tail_length=7, bounce=True) +rainbow_chase = RainbowChase(pixels, speed=0.1, size=3, spacing=2, wheel_step=8) + + +animations = AnimationSequence( + blink, + comet, + chase, + pulse, + sparkle, + solid, + rainbow, + sparkle_pulse, + rainbow_comet, + rainbow_chase, + advance_interval=5, + auto_clear=True, +) + +while True: + animations.animate() diff --git a/examples/led_animation_gridmap.py b/examples/led_animation_gridmap.py new file mode 100644 index 0000000..0066287 --- /dev/null +++ b/examples/led_animation_gridmap.py @@ -0,0 +1,50 @@ +""" +This example shows usage of the gridmap helper to easily treat a single strip as a horizontal or +vertical grid for animation purposes. + +For NeoPixel FeatherWing. Update pixel_pin and pixel_num to match your wiring if using +a different form of NeoPixels. +""" +import board +import neopixel +from adafruit_led_animation.animation import * +from adafruit_led_animation.helper import * +from adafruit_led_animation.color import PURPLE, JADE, AMBER + + +pixels = neopixel.NeoPixel(board.D6, 32, brightness=0.2, auto_write=False) + +pixel_wing_vertical = PixelMap.vertical_lines( + pixels, 8, 4, horizontal_strip_gridmap(8, alternating=False) +) +pixel_wing_horizontal = PixelMap.horizontal_lines( + pixels, 8, 4, horizontal_strip_gridmap(8, alternating=False) +) + +comet_h = Comet( + pixel_wing_horizontal, speed=0.1, color=PURPLE, tail_length=3, bounce=True +) +comet_v = Comet(pixel_wing_vertical, speed=0.1, color=AMBER, tail_length=6, bounce=True) +chase_h = Chase(pixel_wing_horizontal, speed=0.1, size=3, spacing=6, color=JADE) +rainbow_chase_v = RainbowChase( + pixel_wing_vertical, speed=0.1, size=3, spacing=2, wheel_step=8 +) +rainbow_comet_v = RainbowComet( + pixel_wing_vertical, speed=0.1, tail_length=7, bounce=True +) +rainbow_v = Rainbow(pixel_wing_vertical, speed=0.1, period=2) +rainbow_chase_h = RainbowChase(pixel_wing_horizontal, speed=0.1, size=3, spacing=3) + +animations = AnimationSequence( + rainbow_v, + comet_h, + rainbow_comet_v, + chase_h, + rainbow_chase_v, + comet_v, + rainbow_chase_h, + advance_interval=5, +) + +while True: + animations.animate() diff --git a/examples/led_animation_simpletest.py b/examples/led_animation_simpletest.py index 2db1da0..8ce5ffd 100644 --- a/examples/led_animation_simpletest.py +++ b/examples/led_animation_simpletest.py @@ -1,7 +1,8 @@ """ -This example repeatedly displays two animations, Comet and Chase, at a five second interval. +This simpletest example repeatedly displays two animations, Comet and Chase, at a five second +interval. -Designed for NeoPixel FeatherWing. Update pixel_pin and pixel_num to match your wiring if using +For NeoPixel FeatherWing. Update pixel_pin and pixel_num to match your wiring if using a different form of NeoPixels. """ import board From 98da8abd9130ed93c71029e71af4d8a709c373dc Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Thu, 14 May 2020 20:28:27 -0400 Subject: [PATCH 41/46] Import fix. --- examples/led_animation_all_animations.py | 24 +++++++++-------- examples/led_animation_gridmap.py | 34 ++++++++++++++---------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/examples/led_animation_all_animations.py b/examples/led_animation_all_animations.py index e230d55..a01e309 100644 --- a/examples/led_animation_all_animations.py +++ b/examples/led_animation_all_animations.py @@ -6,7 +6,7 @@ """ import board import neopixel -from adafruit_led_animation.animation import * +from adafruit_led_animation import animation from adafruit_led_animation.helper import AnimationSequence from adafruit_led_animation.color import PURPLE, WHITE, AMBER, JADE @@ -17,16 +17,18 @@ pixels = neopixel.NeoPixel(pixel_pin, pixel_num, brightness=0.2, auto_write=False) -blink = Blink(pixels, speed=0.1, color=JADE) -comet = Comet(pixels, speed=0.01, color=PURPLE, tail_length=10, bounce=True) -chase = Chase(pixels, speed=0.1, size=3, spacing=6, color=WHITE) -pulse = Pulse(pixels, speed=0.1, period=3, color=AMBER) -sparkle = Sparkle(pixels, speed=0.1, color=PURPLE, num_sparkles=10) -solid = Solid(pixels, color=JADE) -rainbow = Rainbow(pixels, speed=0.1, period=2) -sparkle_pulse = SparklePulse(pixels, speed=0.1, period=3, color=JADE) -rainbow_comet = RainbowComet(pixels, speed=0.1, tail_length=7, bounce=True) -rainbow_chase = RainbowChase(pixels, speed=0.1, size=3, spacing=2, wheel_step=8) +blink = animation.Blink(pixels, speed=0.1, color=JADE) +comet = animation.Comet(pixels, speed=0.01, color=PURPLE, tail_length=10, bounce=True) +chase = animation.Chase(pixels, speed=0.1, size=3, spacing=6, color=WHITE) +pulse = animation.Pulse(pixels, speed=0.1, period=3, color=AMBER) +sparkle = animation.Sparkle(pixels, speed=0.1, color=PURPLE, num_sparkles=10) +solid = animation.Solid(pixels, color=JADE) +rainbow = animation.Rainbow(pixels, speed=0.1, period=2) +sparkle_pulse = animation.SparklePulse(pixels, speed=0.1, period=3, color=JADE) +rainbow_comet = animation.RainbowComet(pixels, speed=0.1, tail_length=7, bounce=True) +rainbow_chase = animation.RainbowChase( + pixels, speed=0.1, size=3, spacing=2, wheel_step=8 +) animations = AnimationSequence( diff --git a/examples/led_animation_gridmap.py b/examples/led_animation_gridmap.py index 0066287..0542f0d 100644 --- a/examples/led_animation_gridmap.py +++ b/examples/led_animation_gridmap.py @@ -7,35 +7,41 @@ """ import board import neopixel -from adafruit_led_animation.animation import * -from adafruit_led_animation.helper import * +from adafruit_led_animation import animation +from adafruit_led_animation import helper from adafruit_led_animation.color import PURPLE, JADE, AMBER pixels = neopixel.NeoPixel(board.D6, 32, brightness=0.2, auto_write=False) -pixel_wing_vertical = PixelMap.vertical_lines( - pixels, 8, 4, horizontal_strip_gridmap(8, alternating=False) +pixel_wing_vertical = helper.PixelMap.vertical_lines( + pixels, 8, 4, helper.horizontal_strip_gridmap(8, alternating=False) ) -pixel_wing_horizontal = PixelMap.horizontal_lines( - pixels, 8, 4, horizontal_strip_gridmap(8, alternating=False) +pixel_wing_horizontal = helper.PixelMap.horizontal_lines( + pixels, 8, 4, helper.horizontal_strip_gridmap(8, alternating=False) ) -comet_h = Comet( +comet_h = animation.Comet( pixel_wing_horizontal, speed=0.1, color=PURPLE, tail_length=3, bounce=True ) -comet_v = Comet(pixel_wing_vertical, speed=0.1, color=AMBER, tail_length=6, bounce=True) -chase_h = Chase(pixel_wing_horizontal, speed=0.1, size=3, spacing=6, color=JADE) -rainbow_chase_v = RainbowChase( +comet_v = animation.Comet( + pixel_wing_vertical, speed=0.1, color=AMBER, tail_length=6, bounce=True +) +chase_h = animation.Chase( + pixel_wing_horizontal, speed=0.1, size=3, spacing=6, color=JADE +) +rainbow_chase_v = animation.RainbowChase( pixel_wing_vertical, speed=0.1, size=3, spacing=2, wheel_step=8 ) -rainbow_comet_v = RainbowComet( +rainbow_comet_v = animation.RainbowComet( pixel_wing_vertical, speed=0.1, tail_length=7, bounce=True ) -rainbow_v = Rainbow(pixel_wing_vertical, speed=0.1, period=2) -rainbow_chase_h = RainbowChase(pixel_wing_horizontal, speed=0.1, size=3, spacing=3) +rainbow_v = animation.Rainbow(pixel_wing_vertical, speed=0.1, period=2) +rainbow_chase_h = animation.RainbowChase( + pixel_wing_horizontal, speed=0.1, size=3, spacing=3 +) -animations = AnimationSequence( +animations = helper.AnimationSequence( rainbow_v, comet_h, rainbow_comet_v, From 4c442768de862006ef6a9470238451472e36e876 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Thu, 14 May 2020 21:20:40 -0400 Subject: [PATCH 42/46] split up into more files to make it possible to use some animations on M0 --- adafruit_led_animation/animation.py | 255 +---------------- adafruit_led_animation/group.py | 117 ++++++++ adafruit_led_animation/helper.py | 345 +---------------------- adafruit_led_animation/rainbow.py | 147 ++++++++++ adafruit_led_animation/sequence.py | 225 +++++++++++++++ adafruit_led_animation/sparkle.py | 110 ++++++++ examples/led_animation_all_animations.py | 17 +- examples/led_animation_gridmap.py | 13 +- examples/led_animation_simpletest.py | 4 +- 9 files changed, 628 insertions(+), 605 deletions(-) create mode 100644 adafruit_led_animation/group.py create mode 100644 adafruit_led_animation/rainbow.py create mode 100644 adafruit_led_animation/sequence.py create mode 100644 adafruit_led_animation/sparkle.py diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index fc81102..24d288d 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -44,11 +44,9 @@ """ -import random from math import ceil -import adafruit_led_animation.helper from . import NANOS_PER_SECOND, monotonic_ns -from .color import BLACK, RAINBOW, colorwheel +from .color import BLACK, RAINBOW __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" @@ -372,103 +370,6 @@ def reset(self): self.reverse = self._initial_reverse -class RainbowComet(Comet): - """ - A rainbow comet animation. - - :param pixel_object: The initialised LED object. - :param float speed: Animation speed in seconds, e.g. ``0.1``. - :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. - :param int tail_length: The length of the comet. Defaults to 10. Cannot exceed the number of - pixels present in the pixel object, e.g. if the strip is 30 pixels - long, the ``tail_length`` cannot exceed 30 pixels. - :param bool reverse: Animates the comet in the reverse order. Defaults to ``False``. - :param bool bounce: Comet will bounce back and forth. Defaults to ``True``. - :param int colorwheel_offset: Offset from start of colorwheel (0-255). - """ - - # pylint: disable=too-many-arguments - def __init__( - self, - pixel_object, - speed, - tail_length=10, - reverse=False, - bounce=False, - colorwheel_offset=0, - name=None, - ): - self._colorwheel_is_tuple = isinstance(colorwheel(0), tuple) - self._colorwheel_offset = colorwheel_offset - - super().__init__(pixel_object, speed, 0, tail_length, reverse, bounce, name) - - def _calc_brightness(self, n, color): - brightness = (n * self._color_step) + self._color_offset - if not self._colorwheel_is_tuple: - color = (color & 0xFF, ((color & 0xFF00) >> 8), (color >> 16)) - return [int(i * brightness) for i in color] - - def __recompute_color(self, color): - factor = int(256 / self._tail_length) - self._comet_colors = [BLACK] + [ - self._calc_brightness( - n, - colorwheel( - int((n * factor) + self._color_offset + self._colorwheel_offset) - % 256 - ), - ) - for n in range(self._tail_length - 1) - ] - self._reverse_comet_colors = list(reversed(self._comet_colors)) - self._computed_color = color - - -class Sparkle(Animation): - """ - Sparkle animation of a single color. - - :param pixel_object: The initialised LED object. - :param float speed: Animation speed in seconds, e.g. ``0.1``. - :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. - :param num_sparkles: Number of sparkles to generate per animation cycle. - """ - - # pylint: disable=too-many-arguments - def __init__(self, pixel_object, speed, color, num_sparkles=1, name=None): - if len(pixel_object) < 2: - raise ValueError("Sparkle needs at least 2 pixels") - self._half_color = None - self._dim_color = None - self._num_sparkles = num_sparkles - super().__init__(pixel_object, speed, color, name=name) - - def _recompute_color(self, color): - half_color = tuple(color[rgb] // 4 for rgb in range(len(color))) - dim_color = tuple(color[rgb] // 10 for rgb in range(len(color))) - for pixel in range(len(self.pixel_object)): - if self.pixel_object[pixel] == self._half_color: - self.pixel_object[pixel] = half_color - elif self.pixel_object[pixel] == self._dim_color: - self.pixel_object[pixel] = dim_color - self._half_color = half_color - self._dim_color = dim_color - - def draw(self): - pixels = [ - random.randint(0, (len(self.pixel_object) - 2)) - for n in range(self._num_sparkles) - ] - for pixel in pixels: - self.pixel_object[pixel] = self._color - self.show() - for pixel in pixels: - self.pixel_object[pixel] = self._half_color - self.pixel_object[pixel + 1] = self._dim_color - self.show() - - class Pulse(Animation): """ Pulse all pixels a single color. @@ -500,122 +401,9 @@ def reset(self): white = len(self.pixel_object[0]) > 3 and isinstance( self.pixel_object[0][-1], int ) - self._generator = adafruit_led_animation.helper.pulse_generator( - self._period, self, white - ) + from adafruit_led_animation.helper import pulse_generator # pylint: disable=import-outside-toplevel - -class Rainbow(Animation): - """ - The classic rainbow color wheel. - - :param pixel_object: The initialised LED object. - :param float speed: Animation refresh rate in seconds, e.g. ``0.1``. - :param period: Period to cycle the rainbow over. Default 5. - """ - - # pylint: disable=too-many-arguments - def __init__(self, pixel_object, speed, period=5, name=None): - super().__init__(pixel_object, speed, BLACK, name=name) - self._period = period - self._generator = self._color_wheel_generator() - - cycle_complete_supported = True - - def _color_wheel_generator(self): - period = int(self._period * NANOS_PER_SECOND) - - last_update = monotonic_ns() - cycle_position = 0 - last_pos = 0 - while True: - cycle_completed = False - now = monotonic_ns() - time_since_last_draw = now - last_update - last_update = now - pos = cycle_position = (cycle_position + time_since_last_draw) % period - if pos < last_pos: - cycle_completed = True - last_pos = pos - wheel_index = int((pos / period) * 256) - self.pixel_object[:] = [ - colorwheel((i + wheel_index) % 255) - for i, _ in enumerate(self.pixel_object) - ] - self.show() - if cycle_completed: - self.cycle_complete() - yield - - def draw(self): - next(self._generator) - - def reset(self): - """ - Resets the animation. - """ - self._generator = self._color_wheel_generator() - - -class SparklePulse(Animation): - """ - Combination of the Spark and Pulse animations. - - :param pixel_object: The initialised LED object. - :param int speed: Animation refresh rate in seconds, e.g. ``0.1``. - :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. - :param period: Period to pulse the LEDs over. Default 5. - :param max_intensity: The maximum intensity to pulse, between 0 and 1.0. Default 1. - :param min_intensity: The minimum intensity to pulse, between 0 and 1.0. Default 0. - """ - - # pylint: disable=too-many-arguments - def __init__( - self, pixel_object, speed, color, period=5, max_intensity=1, min_intensity=0 - ): - if len(pixel_object) < 2: - raise ValueError("Sparkle needs at least 2 pixels") - self.max_intensity = max_intensity - self.min_intensity = min_intensity - self._period = period - self._intensity_delta = max_intensity - min_intensity - self._half_period = period / 2 - self._position_factor = 1 / self._half_period - self._bpp = len(pixel_object[0]) - self._last_update = monotonic_ns() - self._cycle_position = 0 - self._half_color = None - self._dim_color = None - super().__init__(pixel_object, speed, color) - - def _recompute_color(self, color): - half_color = tuple(color[rgb] // 4 for rgb in range(len(color))) - dim_color = tuple(color[rgb] // 10 for rgb in range(len(color))) - for pixel in range(len(self.pixel_object)): - if self.pixel_object[pixel] == self._half_color: - self.pixel_object[pixel] = half_color - elif self.pixel_object[pixel] == self._dim_color: - self.pixel_object[pixel] = dim_color - self._half_color = half_color - self._dim_color = dim_color - - def draw(self): - pixel = random.randint(0, (len(self.pixel_object) - 2)) - - now = monotonic_ns() - time_since_last_draw = (now - self._last_update) / NANOS_PER_SECOND - self._last_update = now - pos = self._cycle_position = ( - self._cycle_position + time_since_last_draw - ) % self._period - if pos > self._half_period: - pos = self._period - pos - intensity = self.min_intensity + ( - pos * self._intensity_delta * self._position_factor - ) - color = [int(self.color[n] * intensity) for n in range(self._bpp)] - self.pixel_object[pixel] = color - self.show() + self._generator = pulse_generator(self._period, self, white) class Chase(Animation): @@ -713,40 +501,3 @@ def reset(self): Reset the animation. """ self._reset() - - -class RainbowChase(Chase): - """ - Chase pixels in one direction, like a theater marquee but with rainbows! - - :param pixel_object: The initialised LED object. - :param float speed: Animation speed rate in seconds, e.g. ``0.1``. - :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. - :param size: Number of pixels to turn on in a row. - :param spacing: Number of pixels to turn off in a row. - :param reverse: Reverse direction of movement. - :param wheel_step: How many colors to skip in `colorwheel` per bar (default 8) - """ - - # pylint: disable=too-many-arguments - def __init__( - self, - pixel_object, - speed, - size=2, - spacing=3, - reverse=False, - name=None, - wheel_step=8, - ): - self._num_colors = 256 // wheel_step - self._colors = [colorwheel(n % 256) for n in range(0, 512, wheel_step)] - self._color_idx = 0 - super().__init__(pixel_object, speed, 0, size, spacing, reverse, name) - - def bar_color(self, n, pixel_no=0): - return self._colors[self._color_idx - n] - - def cycle_complete(self): - self._color_idx = (self._color_idx + self._direction) % len(self._colors) - super().cycle_complete() diff --git a/adafruit_led_animation/group.py b/adafruit_led_animation/group.py new file mode 100644 index 0000000..093e244 --- /dev/null +++ b/adafruit_led_animation/group.py @@ -0,0 +1,117 @@ +class AnimationGroup: + """ + A group of animations that are active together. An example would be grouping a strip of + pixels connected to a board and the onboard LED. + + :param members: The animation objects or groups. + :param bool sync: Synchronises the timing of all members of the group to the settings of the + first member of the group. Defaults to ``False``. + + """ + + def __init__(self, *members, sync=False, name=None): + if not members: + raise ValueError("At least one member required in an AnimationGroup") + self.draw_count = 0 + """Number of animation frames drawn.""" + self.cycle_count = 0 + """Number of animation cycles completed.""" + self.notify_cycles = 1 + """Number of cycles to trigger additional cycle_done notifications after""" + self._members = list(members) + self._sync = sync + self._also_notify = [] + self.cycle_count = 0 + self.name = name + if sync: + main = members[0] + main.peers = members[1:] + + # Catch cycle_complete on the last animation. + self._members[-1].add_cycle_complete_receiver(self._group_done) + self.cycle_complete_supported = self._members[-1].cycle_complete_supported + + def __str__(self): + return "" % (self.__class__.__name__, self.name) + + def _group_done(self, animation): # pylint: disable=unused-argument + self.cycle_complete() + + def cycle_complete(self): + """ + Called by some animations when they complete an animation cycle. + Animations that support cycle complete notifications will have X property set to False. + Override as needed. + """ + self.cycle_count += 1 + if self.cycle_count % self.notify_cycles == 0: + for callback in self._also_notify: + callback(self) + + def add_cycle_complete_receiver(self, callback): + """ + Adds an additional callback when the cycle completes. + + :param callback: Additional callback to trigger when a cycle completes. The callback + is passed the animation object instance. + """ + self._also_notify.append(callback) + + def animate(self): + """ + Call animate() from your code's main loop. It will draw all of the animations + in the group. + + :return: True if any animation draw cycle was triggered, otherwise False. + """ + if self._sync: + return self._members[0].animate() + + return any([item.animate() for item in self._members]) + + @property + def color(self): + """ + Use this property to change the color of all members of the animation group. + """ + return None + + @color.setter + def color(self, color): + for item in self._members: + item.color = color + + def fill(self, color): + """ + Fills all pixel objects in the group with a color. + """ + for item in self._members: + item.fill(color) + + def freeze(self): + """ + Freeze all animations in the group. + """ + for item in self._members: + item.freeze() + + def resume(self): + """ + Resume all animations in the group. + """ + for item in self._members: + item.resume() + + def reset(self): + """ + Resets the animations in the group. + """ + for item in self._members: + item.reset() + + def show(self): + """ + Draws the current animation group members. + """ + for item in self._members: + item.show() diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 0d353ff..68092ed 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -44,10 +44,8 @@ """ import math -import random from . import NANOS_PER_SECOND, monotonic_ns -from .color import BLACK class PixelMap: @@ -145,6 +143,10 @@ def __getitem__(self, index): def __len__(self): return len(self._ranges) + def __add__(self, other): + if not isinstance(other, PixelMap): + raise TypeError("Cannot add PixelMap and %s" % type(other).__name__) + @property def brightness(self): """ @@ -395,342 +397,3 @@ def pulse_generator(period: float, animation_object, white=False): fill_color[1] = int(fill_color[1] * intensity) fill_color[2] = int(fill_color[2] * intensity) yield fill_color - - -class AnimationGroup: - """ - A group of animations that are active together. An example would be grouping a strip of - pixels connected to a board and the onboard LED. - - :param members: The animation objects or groups. - :param bool sync: Synchronises the timing of all members of the group to the settings of the - first member of the group. Defaults to ``False``. - - """ - - def __init__(self, *members, sync=False, name=None): - if not members: - raise ValueError("At least one member required in an AnimationGroup") - self.draw_count = 0 - """Number of animation frames drawn.""" - self.cycle_count = 0 - """Number of animation cycles completed.""" - self.notify_cycles = 1 - """Number of cycles to trigger additional cycle_done notifications after""" - self._members = list(members) - self._sync = sync - self._also_notify = [] - self.cycle_count = 0 - self.name = name - if sync: - main = members[0] - main.peers = members[1:] - - # Catch cycle_complete on the last animation. - self._members[-1].add_cycle_complete_receiver(self._group_done) - self.cycle_complete_supported = self._members[-1].cycle_complete_supported - - def __str__(self): - return "" % (self.__class__.__name__, self.name) - - def _group_done(self, animation): # pylint: disable=unused-argument - self.cycle_complete() - - def cycle_complete(self): - """ - Called by some animations when they complete an animation cycle. - Animations that support cycle complete notifications will have X property set to False. - Override as needed. - """ - self.cycle_count += 1 - if self.cycle_count % self.notify_cycles == 0: - for callback in self._also_notify: - callback(self) - - def add_cycle_complete_receiver(self, callback): - """ - Adds an additional callback when the cycle completes. - - :param callback: Additional callback to trigger when a cycle completes. The callback - is passed the animation object instance. - """ - self._also_notify.append(callback) - - def animate(self): - """ - Call animate() from your code's main loop. It will draw all of the animations - in the group. - - :return: True if any animation draw cycle was triggered, otherwise False. - """ - if self._sync: - return self._members[0].animate() - - return any([item.animate() for item in self._members]) - - @property - def color(self): - """ - Use this property to change the color of all members of the animation group. - """ - return None - - @color.setter - def color(self, color): - for item in self._members: - item.color = color - - def fill(self, color): - """ - Fills all pixel objects in the group with a color. - """ - for item in self._members: - item.fill(color) - - def freeze(self): - """ - Freeze all animations in the group. - """ - for item in self._members: - item.freeze() - - def resume(self): - """ - Resume all animations in the group. - """ - for item in self._members: - item.resume() - - def reset(self): - """ - Resets the animations in the group. - """ - for item in self._members: - item.reset() - - def show(self): - """ - Draws the current animation group members. - """ - for item in self._members: - item.show() - - -class AnimationSequence: - """ - A sequence of Animations to run in succession, looping forever. - Advances manually, or at the specified interval. - - :param members: The animation objects or groups. - :param int advance_interval: Time in seconds between animations if cycling - automatically. Defaults to ``None``. - :param bool auto_clear: Clear the pixels between animations. If ``True``, the current animation - will be cleared from the pixels before the next one starts. - Defaults to ``False``. - :param bool random_order: Activate the animations in a random order. Defaults to ``False``. - :param bool auto_reset: Automatically call reset() on animations when changing animations. - :param bool advance_on_cycle_complete: Automatically advance when `cycle_complete` is triggered - on member animations. All Animations must support - cycle_complete to use this. - - .. code-block:: python - - import board - import neopixel - from adafruit_led_animation.animation import AnimationSequence, Blink, Comet, Sparkle - import adafruit_led_animation.color as color - - strip_pixels = neopixel.NeoPixel(board.A1, 30, brightness=1, auto_write=False) - - blink = Blink(strip_pixels, 0.2, color.RED) - comet = Comet(strip_pixels, 0.1, color.BLUE) - sparkle = Sparkle(strip_pixels, 0.05, color.GREEN) - - animations = AnimationSequence(blink, comet, sparkle, advance_interval=1) - - while True: - animations.animate() - """ - - # pylint: disable=too-many-instance-attributes - def __init__( - self, - *members, - advance_interval=None, - auto_clear=False, - random_order=False, - auto_reset=False, - advance_on_cycle_complete=False, - name=None - ): - if advance_interval and advance_on_cycle_complete: - raise ValueError("Cannot use both advance_interval and auto_clear") - self._members = members - self._advance_interval = ( - advance_interval * NANOS_PER_SECOND if advance_interval else None - ) - self._last_advance = monotonic_ns() - self._current = 0 - self.auto_clear = auto_clear - self.auto_reset = auto_reset - self.advance_on_cycle_complete = advance_on_cycle_complete - self.clear_color = BLACK - self._paused = False - self._paused_at = 0 - self._random = random_order - self._also_notify = [] - self.cycle_count = 0 - self.notify_cycles = 1 - self.name = name - if random_order: - self._current = random.randint(0, len(self._members) - 1) - self._color = None - for member in self._members: - member.add_cycle_complete_receiver(self._sequence_complete) - self.cycle_complete_supported = self._members[-1].cycle_complete_supported - - cycle_complete_supported = True - - def __str__(self): - return "<%s: %s>" % (self.__class__.__name__, self.name) - - def cycle_complete(self): - """ - Called by some animations when they complete an animation cycle. - Animations that support cycle complete notifications will have X property set to False. - Override as needed. - """ - self.cycle_count += 1 - if self.cycle_count % self.notify_cycles == 0: - for callback in self._also_notify: - callback(self) - - def _sequence_complete(self, animation): # pylint: disable=unused-argument - self.cycle_complete() - if self.advance_on_cycle_complete: - self._advance() - - def add_cycle_complete_receiver(self, callback): - """ - Adds an additional callback when the cycle completes. - - :param callback: Additional callback to trigger when a cycle completes. The callback - is passed the animation object instance. - """ - self._also_notify.append(callback) - - def _auto_advance(self): - if not self._advance_interval: - return - now = monotonic_ns() - if now - self._last_advance > self._advance_interval: - self._last_advance = now - self._advance() - - def _advance(self): - if self.auto_reset: - self.current_animation.reset() - if self.auto_clear: - self.current_animation.fill(self.clear_color) - self.current_animation.show() - if self._random: - self.random() - else: - self.next() - - def activate(self, index): - """ - Activates a specific animation. - """ - if isinstance(index, str): - self._current = [member.name for member in self._members].index(index) - else: - self._current = index - if self._color: - self.current_animation.color = self._color - - def next(self): - """ - Jump to the next animation. - """ - current = self._current - if current > self._current: - self.cycle_complete() - self.activate((self._current + 1) % len(self._members)) - - def random(self): - """ - Jump to a random animation. - """ - self.activate(random.randint(0, len(self._members) - 1)) - - def animate(self): - """ - Call animate() from your code's main loop. It will draw the current animation - or go to the next animation based on the advance_interval if set. - - :return: True if the animation draw cycle was triggered, otherwise False. - """ - if not self._paused and self._advance_interval: - self._auto_advance() - return self.current_animation.animate() - - @property - def current_animation(self): - """ - Returns the current animation in the sequence. - """ - return self._members[self._current] - - @property - def color(self): - """ - Use this property to change the color of all animations in the sequence. - """ - return self._color - - @color.setter - def color(self, color): - self._color = color - self.current_animation.color = color - - def fill(self, color): - """ - Fills the current animation with a color. - """ - self.current_animation.fill(color) - - def freeze(self): - """ - Freeze the current animation in the sequence. - Also stops auto_advance. - """ - if self._paused: - return - self._paused = True - self._paused_at = monotonic_ns() - self.current_animation.freeze() - - def resume(self): - """ - Resume the current animation in the sequence, and resumes auto advance if enabled. - """ - if not self._paused: - return - self._paused = False - now = monotonic_ns() - self._last_advance += now - self._paused_at - self._paused_at = 0 - self.current_animation.resume() - - def reset(self): - """ - Resets the current animation. - """ - self.current_animation.reset() - - def show(self): - """ - Draws the current animation group members. - """ - self.current_animation.show() diff --git a/adafruit_led_animation/rainbow.py b/adafruit_led_animation/rainbow.py new file mode 100644 index 0000000..80f65fd --- /dev/null +++ b/adafruit_led_animation/rainbow.py @@ -0,0 +1,147 @@ +from time import monotonic_ns + +from adafruit_led_animation import NANOS_PER_SECOND +from adafruit_led_animation.animation import Animation, Chase, Comet +from adafruit_led_animation.color import BLACK, colorwheel + + +class Rainbow(Animation): + """ + The classic rainbow color wheel. + + :param pixel_object: The initialised LED object. + :param float speed: Animation refresh rate in seconds, e.g. ``0.1``. + :param period: Period to cycle the rainbow over. Default 5. + """ + + # pylint: disable=too-many-arguments + def __init__(self, pixel_object, speed, period=5, name=None): + super().__init__(pixel_object, speed, BLACK, name=name) + self._period = period + self._generator = self._color_wheel_generator() + + cycle_complete_supported = True + + def _color_wheel_generator(self): + period = int(self._period * NANOS_PER_SECOND) + + last_update = monotonic_ns() + cycle_position = 0 + last_pos = 0 + while True: + cycle_completed = False + now = monotonic_ns() + time_since_last_draw = now - last_update + last_update = now + pos = cycle_position = (cycle_position + time_since_last_draw) % period + if pos < last_pos: + cycle_completed = True + last_pos = pos + wheel_index = int((pos / period) * 256) + self.pixel_object[:] = [ + colorwheel((i + wheel_index) % 255) + for i, _ in enumerate(self.pixel_object) + ] + self.show() + if cycle_completed: + self.cycle_complete() + yield + + def draw(self): + next(self._generator) + + def reset(self): + """ + Resets the animation. + """ + self._generator = self._color_wheel_generator() + + +class RainbowChase(Chase): + """ + Chase pixels in one direction, like a theater marquee but with rainbows! + + :param pixel_object: The initialised LED object. + :param float speed: Animation speed rate in seconds, e.g. ``0.1``. + :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. + :param size: Number of pixels to turn on in a row. + :param spacing: Number of pixels to turn off in a row. + :param reverse: Reverse direction of movement. + :param wheel_step: How many colors to skip in `colorwheel` per bar (default 8) + """ + + # pylint: disable=too-many-arguments + def __init__( + self, + pixel_object, + speed, + size=2, + spacing=3, + reverse=False, + name=None, + wheel_step=8, + ): + self._num_colors = 256 // wheel_step + self._colors = [colorwheel(n % 256) for n in range(0, 512, wheel_step)] + self._color_idx = 0 + super().__init__(pixel_object, speed, 0, size, spacing, reverse, name) + + def bar_color(self, n, pixel_no=0): + return self._colors[self._color_idx - n] + + def cycle_complete(self): + self._color_idx = (self._color_idx + self._direction) % len(self._colors) + super().cycle_complete() + + +class RainbowComet(Comet): + """ + A rainbow comet animation. + + :param pixel_object: The initialised LED object. + :param float speed: Animation speed in seconds, e.g. ``0.1``. + :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. + :param int tail_length: The length of the comet. Defaults to 10. Cannot exceed the number of + pixels present in the pixel object, e.g. if the strip is 30 pixels + long, the ``tail_length`` cannot exceed 30 pixels. + :param bool reverse: Animates the comet in the reverse order. Defaults to ``False``. + :param bool bounce: Comet will bounce back and forth. Defaults to ``True``. + :param int colorwheel_offset: Offset from start of colorwheel (0-255). + """ + + # pylint: disable=too-many-arguments + def __init__( + self, + pixel_object, + speed, + tail_length=10, + reverse=False, + bounce=False, + colorwheel_offset=0, + name=None, + ): + self._colorwheel_is_tuple = isinstance(colorwheel(0), tuple) + self._colorwheel_offset = colorwheel_offset + + super().__init__(pixel_object, speed, 0, tail_length, reverse, bounce, name) + + def _calc_brightness(self, n, color): + brightness = (n * self._color_step) + self._color_offset + if not self._colorwheel_is_tuple: + color = (color & 0xFF, ((color & 0xFF00) >> 8), (color >> 16)) + return [int(i * brightness) for i in color] + + def __recompute_color(self, color): + factor = int(256 / self._tail_length) + self._comet_colors = [BLACK] + [ + self._calc_brightness( + n, + colorwheel( + int((n * factor) + self._color_offset + self._colorwheel_offset) + % 256 + ), + ) + for n in range(self._tail_length - 1) + ] + self._reverse_comet_colors = list(reversed(self._comet_colors)) + self._computed_color = color diff --git a/adafruit_led_animation/sequence.py b/adafruit_led_animation/sequence.py new file mode 100644 index 0000000..6971eab --- /dev/null +++ b/adafruit_led_animation/sequence.py @@ -0,0 +1,225 @@ +import random +from time import monotonic_ns + +from adafruit_led_animation import NANOS_PER_SECOND +from adafruit_led_animation.color import BLACK + + +class AnimationSequence: + """ + A sequence of Animations to run in succession, looping forever. + Advances manually, or at the specified interval. + + :param members: The animation objects or groups. + :param int advance_interval: Time in seconds between animations if cycling + automatically. Defaults to ``None``. + :param bool auto_clear: Clear the pixels between animations. If ``True``, the current animation + will be cleared from the pixels before the next one starts. + Defaults to ``False``. + :param bool random_order: Activate the animations in a random order. Defaults to ``False``. + :param bool auto_reset: Automatically call reset() on animations when changing animations. + :param bool advance_on_cycle_complete: Automatically advance when `cycle_complete` is triggered + on member animations. All Animations must support + cycle_complete to use this. + + .. code-block:: python + + import board + import neopixel + from adafruit_led_animation.animation import AnimationSequence, Blink, Comet, Sparkle + import adafruit_led_animation.color as color + + strip_pixels = neopixel.NeoPixel(board.A1, 30, brightness=1, auto_write=False) + + blink = Blink(strip_pixels, 0.2, color.RED) + comet = Comet(strip_pixels, 0.1, color.BLUE) + sparkle = Sparkle(strip_pixels, 0.05, color.GREEN) + + animations = AnimationSequence(blink, comet, sparkle, advance_interval=1) + + while True: + animations.animate() + """ + + # pylint: disable=too-many-instance-attributes + def __init__( + self, + *members, + advance_interval=None, + auto_clear=False, + random_order=False, + auto_reset=False, + advance_on_cycle_complete=False, + name=None + ): + if advance_interval and advance_on_cycle_complete: + raise ValueError("Cannot use both advance_interval and auto_clear") + self._members = members + self._advance_interval = ( + advance_interval * NANOS_PER_SECOND if advance_interval else None + ) + self._last_advance = monotonic_ns() + self._current = 0 + self.auto_clear = auto_clear + self.auto_reset = auto_reset + self.advance_on_cycle_complete = advance_on_cycle_complete + self.clear_color = BLACK + self._paused = False + self._paused_at = 0 + self._random = random_order + self._also_notify = [] + self.cycle_count = 0 + self.notify_cycles = 1 + self.name = name + if random_order: + self._current = random.randint(0, len(self._members) - 1) + self._color = None + for member in self._members: + member.add_cycle_complete_receiver(self._sequence_complete) + self.cycle_complete_supported = self._members[-1].cycle_complete_supported + + cycle_complete_supported = True + + def __str__(self): + return "<%s: %s>" % (self.__class__.__name__, self.name) + + def cycle_complete(self): + """ + Called by some animations when they complete an animation cycle. + Animations that support cycle complete notifications will have X property set to False. + Override as needed. + """ + self.cycle_count += 1 + if self.cycle_count % self.notify_cycles == 0: + for callback in self._also_notify: + callback(self) + + def _sequence_complete(self, animation): # pylint: disable=unused-argument + self.cycle_complete() + if self.advance_on_cycle_complete: + self._advance() + + def add_cycle_complete_receiver(self, callback): + """ + Adds an additional callback when the cycle completes. + + :param callback: Additional callback to trigger when a cycle completes. The callback + is passed the animation object instance. + """ + self._also_notify.append(callback) + + def _auto_advance(self): + if not self._advance_interval: + return + now = monotonic_ns() + if now - self._last_advance > self._advance_interval: + self._last_advance = now + self._advance() + + def _advance(self): + if self.auto_reset: + self.current_animation.reset() + if self.auto_clear: + self.current_animation.fill(self.clear_color) + self.current_animation.show() + if self._random: + self.random() + else: + self.next() + + def activate(self, index): + """ + Activates a specific animation. + """ + if isinstance(index, str): + self._current = [member.name for member in self._members].index(index) + else: + self._current = index + if self._color: + self.current_animation.color = self._color + + def next(self): + """ + Jump to the next animation. + """ + current = self._current + if current > self._current: + self.cycle_complete() + self.activate((self._current + 1) % len(self._members)) + + def random(self): + """ + Jump to a random animation. + """ + self.activate(random.randint(0, len(self._members) - 1)) + + def animate(self): + """ + Call animate() from your code's main loop. It will draw the current animation + or go to the next animation based on the advance_interval if set. + + :return: True if the animation draw cycle was triggered, otherwise False. + """ + if not self._paused and self._advance_interval: + self._auto_advance() + return self.current_animation.animate() + + @property + def current_animation(self): + """ + Returns the current animation in the sequence. + """ + return self._members[self._current] + + @property + def color(self): + """ + Use this property to change the color of all animations in the sequence. + """ + return self._color + + @color.setter + def color(self, color): + self._color = color + self.current_animation.color = color + + def fill(self, color): + """ + Fills the current animation with a color. + """ + self.current_animation.fill(color) + + def freeze(self): + """ + Freeze the current animation in the sequence. + Also stops auto_advance. + """ + if self._paused: + return + self._paused = True + self._paused_at = monotonic_ns() + self.current_animation.freeze() + + def resume(self): + """ + Resume the current animation in the sequence, and resumes auto advance if enabled. + """ + if not self._paused: + return + self._paused = False + now = monotonic_ns() + self._last_advance += now - self._paused_at + self._paused_at = 0 + self.current_animation.resume() + + def reset(self): + """ + Resets the current animation. + """ + self.current_animation.reset() + + def show(self): + """ + Draws the current animation group members. + """ + self.current_animation.show() diff --git a/adafruit_led_animation/sparkle.py b/adafruit_led_animation/sparkle.py new file mode 100644 index 0000000..d3b8be1 --- /dev/null +++ b/adafruit_led_animation/sparkle.py @@ -0,0 +1,110 @@ +import random +from time import monotonic_ns + +from adafruit_led_animation import NANOS_PER_SECOND +from adafruit_led_animation.animation import Animation + + +class Sparkle(Animation): + """ + Sparkle animation of a single color. + + :param pixel_object: The initialised LED object. + :param float speed: Animation speed in seconds, e.g. ``0.1``. + :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. + :param num_sparkles: Number of sparkles to generate per animation cycle. + """ + + # pylint: disable=too-many-arguments + def __init__(self, pixel_object, speed, color, num_sparkles=1, name=None): + if len(pixel_object) < 2: + raise ValueError("Sparkle needs at least 2 pixels") + self._half_color = None + self._dim_color = None + self._num_sparkles = num_sparkles + super().__init__(pixel_object, speed, color, name=name) + + def _recompute_color(self, color): + half_color = tuple(color[rgb] // 4 for rgb in range(len(color))) + dim_color = tuple(color[rgb] // 10 for rgb in range(len(color))) + for pixel in range(len(self.pixel_object)): + if self.pixel_object[pixel] == self._half_color: + self.pixel_object[pixel] = half_color + elif self.pixel_object[pixel] == self._dim_color: + self.pixel_object[pixel] = dim_color + self._half_color = half_color + self._dim_color = dim_color + + def draw(self): + pixels = [ + random.randint(0, (len(self.pixel_object) - 2)) + for n in range(self._num_sparkles) + ] + for pixel in pixels: + self.pixel_object[pixel] = self._color + self.show() + for pixel in pixels: + self.pixel_object[pixel] = self._half_color + self.pixel_object[pixel + 1] = self._dim_color + self.show() + + +class SparklePulse(Animation): + """ + Combination of the Spark and Pulse animations. + + :param pixel_object: The initialised LED object. + :param int speed: Animation refresh rate in seconds, e.g. ``0.1``. + :param color: Animation color in ``(r, g, b)`` tuple, or ``0x000000`` hex format. + :param period: Period to pulse the LEDs over. Default 5. + :param max_intensity: The maximum intensity to pulse, between 0 and 1.0. Default 1. + :param min_intensity: The minimum intensity to pulse, between 0 and 1.0. Default 0. + """ + + # pylint: disable=too-many-arguments + def __init__( + self, pixel_object, speed, color, period=5, max_intensity=1, min_intensity=0 + ): + if len(pixel_object) < 2: + raise ValueError("Sparkle needs at least 2 pixels") + self.max_intensity = max_intensity + self.min_intensity = min_intensity + self._period = period + self._intensity_delta = max_intensity - min_intensity + self._half_period = period / 2 + self._position_factor = 1 / self._half_period + self._bpp = len(pixel_object[0]) + self._last_update = monotonic_ns() + self._cycle_position = 0 + self._half_color = None + self._dim_color = None + super().__init__(pixel_object, speed, color) + + def _recompute_color(self, color): + half_color = tuple(color[rgb] // 4 for rgb in range(len(color))) + dim_color = tuple(color[rgb] // 10 for rgb in range(len(color))) + for pixel in range(len(self.pixel_object)): + if self.pixel_object[pixel] == self._half_color: + self.pixel_object[pixel] = half_color + elif self.pixel_object[pixel] == self._dim_color: + self.pixel_object[pixel] = dim_color + self._half_color = half_color + self._dim_color = dim_color + + def draw(self): + pixel = random.randint(0, (len(self.pixel_object) - 2)) + + now = monotonic_ns() + time_since_last_draw = (now - self._last_update) / NANOS_PER_SECOND + self._last_update = now + pos = self._cycle_position = ( + self._cycle_position + time_since_last_draw + ) % self._period + if pos > self._half_period: + pos = self._period - pos + intensity = self.min_intensity + ( + pos * self._intensity_delta * self._position_factor + ) + color = [int(self.color[n] * intensity) for n in range(self._bpp)] + self.pixel_object[pixel] = color + self.show() diff --git a/examples/led_animation_all_animations.py b/examples/led_animation_all_animations.py index a01e309..793993a 100644 --- a/examples/led_animation_all_animations.py +++ b/examples/led_animation_all_animations.py @@ -3,11 +3,16 @@ For NeoPixel FeatherWing. Update pixel_pin and pixel_num to match your wiring if using a different form of NeoPixels. + +This example does not work on SAMD21 (M0) boards. """ import board import neopixel + +import adafruit_led_animation.rainbow +import adafruit_led_animation.sparkle from adafruit_led_animation import animation -from adafruit_led_animation.helper import AnimationSequence +from adafruit_led_animation.sequence import AnimationSequence from adafruit_led_animation.color import PURPLE, WHITE, AMBER, JADE # Update to match the pin connected to your NeoPixels @@ -21,12 +26,12 @@ comet = animation.Comet(pixels, speed=0.01, color=PURPLE, tail_length=10, bounce=True) chase = animation.Chase(pixels, speed=0.1, size=3, spacing=6, color=WHITE) pulse = animation.Pulse(pixels, speed=0.1, period=3, color=AMBER) -sparkle = animation.Sparkle(pixels, speed=0.1, color=PURPLE, num_sparkles=10) +sparkle = adafruit_led_animation.sparkle.Sparkle(pixels, speed=0.1, color=PURPLE, num_sparkles=10) solid = animation.Solid(pixels, color=JADE) -rainbow = animation.Rainbow(pixels, speed=0.1, period=2) -sparkle_pulse = animation.SparklePulse(pixels, speed=0.1, period=3, color=JADE) -rainbow_comet = animation.RainbowComet(pixels, speed=0.1, tail_length=7, bounce=True) -rainbow_chase = animation.RainbowChase( +rainbow = adafruit_led_animation.rainbow.Rainbow(pixels, speed=0.1, period=2) +sparkle_pulse = adafruit_led_animation.sparkle.SparklePulse(pixels, speed=0.1, period=3, color=JADE) +rainbow_comet = adafruit_led_animation.rainbow.RainbowComet(pixels, speed=0.1, tail_length=7, bounce=True) +rainbow_chase = adafruit_led_animation.rainbow.RainbowChase( pixels, speed=0.1, size=3, spacing=2, wheel_step=8 ) diff --git a/examples/led_animation_gridmap.py b/examples/led_animation_gridmap.py index 0542f0d..bf949d6 100644 --- a/examples/led_animation_gridmap.py +++ b/examples/led_animation_gridmap.py @@ -7,6 +7,9 @@ """ import board import neopixel + +import adafruit_led_animation.rainbow +import adafruit_led_animation.sequence from adafruit_led_animation import animation from adafruit_led_animation import helper from adafruit_led_animation.color import PURPLE, JADE, AMBER @@ -30,18 +33,18 @@ chase_h = animation.Chase( pixel_wing_horizontal, speed=0.1, size=3, spacing=6, color=JADE ) -rainbow_chase_v = animation.RainbowChase( +rainbow_chase_v = adafruit_led_animation.rainbow.RainbowChase( pixel_wing_vertical, speed=0.1, size=3, spacing=2, wheel_step=8 ) -rainbow_comet_v = animation.RainbowComet( +rainbow_comet_v = adafruit_led_animation.rainbow.RainbowComet( pixel_wing_vertical, speed=0.1, tail_length=7, bounce=True ) -rainbow_v = animation.Rainbow(pixel_wing_vertical, speed=0.1, period=2) -rainbow_chase_h = animation.RainbowChase( +rainbow_v = adafruit_led_animation.rainbow.Rainbow(pixel_wing_vertical, speed=0.1, period=2) +rainbow_chase_h = adafruit_led_animation.rainbow.RainbowChase( pixel_wing_horizontal, speed=0.1, size=3, spacing=3 ) -animations = helper.AnimationSequence( +animations = adafruit_led_animation.sequence.AnimationSequence( rainbow_v, comet_h, rainbow_comet_v, diff --git a/examples/led_animation_simpletest.py b/examples/led_animation_simpletest.py index 8ce5ffd..bb53d95 100644 --- a/examples/led_animation_simpletest.py +++ b/examples/led_animation_simpletest.py @@ -4,11 +4,13 @@ For NeoPixel FeatherWing. Update pixel_pin and pixel_num to match your wiring if using a different form of NeoPixels. + +This example does not work on SAMD21 (M0) boards. """ import board import neopixel from adafruit_led_animation.animation import Comet, Chase -from adafruit_led_animation.helper import AnimationSequence +from adafruit_led_animation.sequence import AnimationSequence from adafruit_led_animation.color import PURPLE, WHITE # Update to match the pin connected to your NeoPixels From 0dc75995a8592e8ef5e33eac9b21b40ffa4d9be6 Mon Sep 17 00:00:00 2001 From: Roy Hooper Date: Thu, 14 May 2020 21:24:25 -0400 Subject: [PATCH 43/46] remove pixelmap add --- adafruit_led_animation/helper.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 68092ed..652f18f 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -143,10 +143,6 @@ def __getitem__(self, index): def __len__(self): return len(self._ranges) - def __add__(self, other): - if not isinstance(other, PixelMap): - raise TypeError("Cannot add PixelMap and %s" % type(other).__name__) - @property def brightness(self): """ From b0206e99dd6678d73a9bf3d1c6f07cb4be50de9b Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Thu, 14 May 2020 22:08:11 -0400 Subject: [PATCH 44/46] Still needs docstrings in new files. --- adafruit_led_animation/animation.py | 4 +++- adafruit_led_animation/rainbow.py | 4 +--- adafruit_led_animation/sequence.py | 4 +--- adafruit_led_animation/sparkle.py | 4 +--- examples/led_animation_all_animations.py | 12 +++++++++--- examples/led_animation_gridmap.py | 4 +++- 6 files changed, 18 insertions(+), 14 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 24d288d..21ad0e4 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -401,7 +401,9 @@ def reset(self): white = len(self.pixel_object[0]) > 3 and isinstance( self.pixel_object[0][-1], int ) - from adafruit_led_animation.helper import pulse_generator # pylint: disable=import-outside-toplevel + from adafruit_led_animation.helper import ( # pylint: disable=import-outside-toplevel + pulse_generator, + ) self._generator = pulse_generator(self._period, self, white) diff --git a/adafruit_led_animation/rainbow.py b/adafruit_led_animation/rainbow.py index 80f65fd..fa460fc 100644 --- a/adafruit_led_animation/rainbow.py +++ b/adafruit_led_animation/rainbow.py @@ -1,8 +1,6 @@ -from time import monotonic_ns - -from adafruit_led_animation import NANOS_PER_SECOND from adafruit_led_animation.animation import Animation, Chase, Comet from adafruit_led_animation.color import BLACK, colorwheel +from . import NANOS_PER_SECOND, monotonic_ns class Rainbow(Animation): diff --git a/adafruit_led_animation/sequence.py b/adafruit_led_animation/sequence.py index 6971eab..3d6ff5f 100644 --- a/adafruit_led_animation/sequence.py +++ b/adafruit_led_animation/sequence.py @@ -1,8 +1,6 @@ import random -from time import monotonic_ns - -from adafruit_led_animation import NANOS_PER_SECOND from adafruit_led_animation.color import BLACK +from . import NANOS_PER_SECOND, monotonic_ns class AnimationSequence: diff --git a/adafruit_led_animation/sparkle.py b/adafruit_led_animation/sparkle.py index d3b8be1..2fe3df5 100644 --- a/adafruit_led_animation/sparkle.py +++ b/adafruit_led_animation/sparkle.py @@ -1,8 +1,6 @@ import random -from time import monotonic_ns - -from adafruit_led_animation import NANOS_PER_SECOND from adafruit_led_animation.animation import Animation +from . import NANOS_PER_SECOND, monotonic_ns class Sparkle(Animation): diff --git a/examples/led_animation_all_animations.py b/examples/led_animation_all_animations.py index 793993a..1d9504b 100644 --- a/examples/led_animation_all_animations.py +++ b/examples/led_animation_all_animations.py @@ -26,11 +26,17 @@ comet = animation.Comet(pixels, speed=0.01, color=PURPLE, tail_length=10, bounce=True) chase = animation.Chase(pixels, speed=0.1, size=3, spacing=6, color=WHITE) pulse = animation.Pulse(pixels, speed=0.1, period=3, color=AMBER) -sparkle = adafruit_led_animation.sparkle.Sparkle(pixels, speed=0.1, color=PURPLE, num_sparkles=10) +sparkle = adafruit_led_animation.sparkle.Sparkle( + pixels, speed=0.1, color=PURPLE, num_sparkles=10 +) solid = animation.Solid(pixels, color=JADE) rainbow = adafruit_led_animation.rainbow.Rainbow(pixels, speed=0.1, period=2) -sparkle_pulse = adafruit_led_animation.sparkle.SparklePulse(pixels, speed=0.1, period=3, color=JADE) -rainbow_comet = adafruit_led_animation.rainbow.RainbowComet(pixels, speed=0.1, tail_length=7, bounce=True) +sparkle_pulse = adafruit_led_animation.sparkle.SparklePulse( + pixels, speed=0.1, period=3, color=JADE +) +rainbow_comet = adafruit_led_animation.rainbow.RainbowComet( + pixels, speed=0.1, tail_length=7, bounce=True +) rainbow_chase = adafruit_led_animation.rainbow.RainbowChase( pixels, speed=0.1, size=3, spacing=2, wheel_step=8 ) diff --git a/examples/led_animation_gridmap.py b/examples/led_animation_gridmap.py index bf949d6..0408aa6 100644 --- a/examples/led_animation_gridmap.py +++ b/examples/led_animation_gridmap.py @@ -39,7 +39,9 @@ rainbow_comet_v = adafruit_led_animation.rainbow.RainbowComet( pixel_wing_vertical, speed=0.1, tail_length=7, bounce=True ) -rainbow_v = adafruit_led_animation.rainbow.Rainbow(pixel_wing_vertical, speed=0.1, period=2) +rainbow_v = adafruit_led_animation.rainbow.Rainbow( + pixel_wing_vertical, speed=0.1, period=2 +) rainbow_chase_h = adafruit_led_animation.rainbow.RainbowChase( pixel_wing_horizontal, speed=0.1, size=3, spacing=3 ) From f29856cfd3318bfe26529f2fc41bcb6862b27b7c Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Fri, 15 May 2020 12:11:09 -0400 Subject: [PATCH 45/46] Update docstrings and docs. --- adafruit_led_animation/animation.py | 3 +- adafruit_led_animation/color.py | 2 +- adafruit_led_animation/group.py | 49 +++++++++++++++++++++++++++++ adafruit_led_animation/helper.py | 3 +- adafruit_led_animation/rainbow.py | 47 +++++++++++++++++++++++++++ adafruit_led_animation/sequence.py | 48 ++++++++++++++++++++++++++++ adafruit_led_animation/sparkle.py | 48 ++++++++++++++++++++++++++++ docs/api.rst | 12 +++++++ examples/led_animation_gridmap.py | 2 ++ 9 files changed, 209 insertions(+), 5 deletions(-) diff --git a/adafruit_led_animation/animation.py b/adafruit_led_animation/animation.py index 21ad0e4..51a7e9d 100644 --- a/adafruit_led_animation/animation.py +++ b/adafruit_led_animation/animation.py @@ -24,8 +24,7 @@ `adafruit_led_animation.animation` ================================================================================ -CircuitPython helper library for LED animations. - +Animation base class, and basic animations for CircuitPython helper library for LED animations. * Author(s): Roy Hooper, Kattni Rembor diff --git a/adafruit_led_animation/color.py b/adafruit_led_animation/color.py index 0283906..0cdb0d2 100644 --- a/adafruit_led_animation/color.py +++ b/adafruit_led_animation/color.py @@ -19,7 +19,7 @@ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -"""Color variables made available for import. +"""Color variables made available for import for CircuitPython LED animations library. RAINBOW is a list of colors to use for cycling through. """ diff --git a/adafruit_led_animation/group.py b/adafruit_led_animation/group.py index 093e244..db98a43 100644 --- a/adafruit_led_animation/group.py +++ b/adafruit_led_animation/group.py @@ -1,3 +1,52 @@ +# The MIT License (MIT) +# +# Copyright (c) 2019-2020 Roy Hooper +# Copyright (c) 2020 Kattni Rembor for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +`adafruit_led_animation.group` +================================================================================ + +Animation group helper for CircuitPython helper library for LED animations.. + + +* Author(s): Roy Hooper, Kattni Rembor + +Implementation Notes +-------------------- + +**Hardware:** + +* `Adafruit NeoPixels `_ +* `Adafruit DotStars `_ + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +""" + +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" + class AnimationGroup: """ A group of animations that are active together. An example would be grouping a strip of diff --git a/adafruit_led_animation/helper.py b/adafruit_led_animation/helper.py index 652f18f..f38367f 100644 --- a/adafruit_led_animation/helper.py +++ b/adafruit_led_animation/helper.py @@ -23,8 +23,7 @@ `adafruit_led_animation.helper` ================================================================================ -Helper classes for making complex animations using LED Animation library. - +Helper classes for making complex animations using CircuitPython LED animations library. * Author(s): Roy Hooper, Kattni Rembor diff --git a/adafruit_led_animation/rainbow.py b/adafruit_led_animation/rainbow.py index fa460fc..9fe8abf 100644 --- a/adafruit_led_animation/rainbow.py +++ b/adafruit_led_animation/rainbow.py @@ -1,7 +1,54 @@ +# The MIT License (MIT) +# +# Copyright (c) 2019-2020 Roy Hooper +# Copyright (c) 2020 Kattni Rembor for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +`adafruit_led_animation.rainbow` +================================================================================ + +Rainbow animations for CircuitPython helper library for LED animations. + +* Author(s): Roy Hooper, Kattni Rembor + +Implementation Notes +-------------------- + +**Hardware:** + +* `Adafruit NeoPixels `_ +* `Adafruit DotStars `_ + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +""" + from adafruit_led_animation.animation import Animation, Chase, Comet from adafruit_led_animation.color import BLACK, colorwheel from . import NANOS_PER_SECOND, monotonic_ns +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" class Rainbow(Animation): """ diff --git a/adafruit_led_animation/sequence.py b/adafruit_led_animation/sequence.py index 3d6ff5f..d06af02 100644 --- a/adafruit_led_animation/sequence.py +++ b/adafruit_led_animation/sequence.py @@ -1,7 +1,55 @@ +# The MIT License (MIT) +# +# Copyright (c) 2019-2020 Roy Hooper +# Copyright (c) 2020 Kattni Rembor for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +`adafruit_led_animation.sequence` +================================================================================ + +Animation sequence helper for CircuitPython helper library for LED animations. + + +* Author(s): Roy Hooper, Kattni Rembor + +Implementation Notes +-------------------- + +**Hardware:** + +* `Adafruit NeoPixels `_ +* `Adafruit DotStars `_ + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +""" + import random from adafruit_led_animation.color import BLACK from . import NANOS_PER_SECOND, monotonic_ns +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" class AnimationSequence: """ diff --git a/adafruit_led_animation/sparkle.py b/adafruit_led_animation/sparkle.py index 2fe3df5..8dbaf46 100644 --- a/adafruit_led_animation/sparkle.py +++ b/adafruit_led_animation/sparkle.py @@ -1,7 +1,55 @@ +# The MIT License (MIT) +# +# Copyright (c) 2019-2020 Roy Hooper +# Copyright (c) 2020 Kattni Rembor for Adafruit Industries +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +""" +`adafruit_led_animation.sparkle` +================================================================================ + +Sparkle animations for CircuitPython helper library for LED animations. + + +* Author(s): Roy Hooper, Kattni Rembor + +Implementation Notes +-------------------- + +**Hardware:** + +* `Adafruit NeoPixels `_ +* `Adafruit DotStars `_ + +**Software and Dependencies:** + +* Adafruit CircuitPython firmware for the supported boards: + https://circuitpython.org/downloads + +""" + import random from adafruit_led_animation.animation import Animation from . import NANOS_PER_SECOND, monotonic_ns +__version__ = "0.0.0-auto.0" +__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" class Sparkle(Animation): """ diff --git a/docs/api.rst b/docs/api.rst index b92f82b..61f7163 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -12,3 +12,15 @@ .. automodule:: adafruit_led_animation.helper :members: + +.. automodule:: adafruit_led_animation.group + :members: + +.. automodule:: adafruit_led_animation.sequence + :members: + +.. automodule:: adafruit_led_animation.rainbow + :members: + +.. automodule:: adafruit_led_animation.sparkle + :members: diff --git a/examples/led_animation_gridmap.py b/examples/led_animation_gridmap.py index 0408aa6..2931aab 100644 --- a/examples/led_animation_gridmap.py +++ b/examples/led_animation_gridmap.py @@ -4,6 +4,8 @@ For NeoPixel FeatherWing. Update pixel_pin and pixel_num to match your wiring if using a different form of NeoPixels. + +This example does not work on SAMD21 (M0) boards. """ import board import neopixel From fa69348345b5a2840d21b63bb443cfcb9bc45d84 Mon Sep 17 00:00:00 2001 From: Kattni Rembor Date: Fri, 15 May 2020 12:12:08 -0400 Subject: [PATCH 46/46] Black again. --- adafruit_led_animation/group.py | 1 + adafruit_led_animation/rainbow.py | 1 + adafruit_led_animation/sequence.py | 1 + adafruit_led_animation/sparkle.py | 1 + 4 files changed, 4 insertions(+) diff --git a/adafruit_led_animation/group.py b/adafruit_led_animation/group.py index db98a43..90cee46 100644 --- a/adafruit_led_animation/group.py +++ b/adafruit_led_animation/group.py @@ -47,6 +47,7 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" + class AnimationGroup: """ A group of animations that are active together. An example would be grouping a strip of diff --git a/adafruit_led_animation/rainbow.py b/adafruit_led_animation/rainbow.py index 9fe8abf..78500b1 100644 --- a/adafruit_led_animation/rainbow.py +++ b/adafruit_led_animation/rainbow.py @@ -50,6 +50,7 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" + class Rainbow(Animation): """ The classic rainbow color wheel. diff --git a/adafruit_led_animation/sequence.py b/adafruit_led_animation/sequence.py index d06af02..79e7161 100644 --- a/adafruit_led_animation/sequence.py +++ b/adafruit_led_animation/sequence.py @@ -51,6 +51,7 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" + class AnimationSequence: """ A sequence of Animations to run in succession, looping forever. diff --git a/adafruit_led_animation/sparkle.py b/adafruit_led_animation/sparkle.py index 8dbaf46..8ac5b36 100644 --- a/adafruit_led_animation/sparkle.py +++ b/adafruit_led_animation/sparkle.py @@ -51,6 +51,7 @@ __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_LED_Animation.git" + class Sparkle(Animation): """ Sparkle animation of a single color.