Crate smart_leds_animations

Source
Expand description

This crate complements the smart_leds collection of crates for interacting with individually addressable LEDs using Rust. It endeavors to provide a declarative interface to a library of ready-made animations usable in low-memory, no_std environments, as well as a framework for creating custom animations.

This crate has been tested only with a WS2812B strip driven by an Arduino Uno R3 but should work with any hardware supported by smart_leds.

A Note About API Stability: Expect some volatility across versions.

This crate was developed by a newcomer to both Rust and embedded programming as a learning project. I learned a lot in getting it to this point, and I think it provides value as-is, but I’m sure there’s a lot of room for improvement. If folks open issues to suggest better implementations, request missing features, etc.—and I hope they do so I can keep learning!—I suspect the public interfaces of the library to change to accommodate the same.

§Features

  • Makes zero heap allocations—no need for an allocator crate.
  • Wraps and re-exports smart_leds so you don’t have to explicitly include it in your dependencies. Abstracts smart_leds’s Gamma and Brightness iterators—for those smart LED chips which support them—into Driver configurations, ensuring correct usage.
  • Provides several ready-made animations to use in your projects as well as a framework for creating custom ones.
  • Supports running different animations on different sections of the LED strip as well as composing individual animations into a compound animation.

§Example

Example implementation of the library; marquee reads: Beetleguese Beetlegeuse

Pictured here is the the Halloween project that led to the creation of this library. A single, unbroken LED strip borders the marquee, so in many cases the visual effects are realized across noncontiguous pixels. The two main animations are implemented as follows:

  • Broken arrow: The main building block of this animation is the Snake; there are four of them in play. Each side of the arrow above the lettering is a Snake. These are grouped together in a Parallel animation because they need to be treated as a single unit in the Series animation that represents the broken arrow as a whole. The other component in the Series is an Arrow, which is little more than two converging Snakes with a little extra logic to handle some edge cases.
  • Glitch: I have taken to calling the rectangle of pixels around the lettering a Glitch effect. Because I thought it too bespoke for a general-purpose library, it is implemented as a custom animation in the aforementioned project. Hopefully it’s a useful example of how downstream users might leverage the AnimateFrames trait.

§Getting Started

Add this crate to your project:

cargo add smart_leds_animations

…then:

// This is simplified code from a project which uses an Arduino Uno R3 to drive WS2812B strip.
// Much has been omitted to highlight use of this library. A complete example is available
// at https://github.com/universalhandle/beetlejuice_marquee.

#![no_std]
#![no_main]

#[arduino_hal::entry]
fn main() -> ! {
  use smart_leds_animations::{
    animations::Snake,
    harness::*,
    smart_leds::RGB8,
  };
  use ws2812_spi::Ws2812;

  // `Ws2812` is part of the `smart_leds` family of crates. Detail about setting up the device
  // is intentionally omitted here; you can see the `smart_leds` documentation or the
  // aforementioned example project for more about that. The important thing is that the
  // returned value implements the `SmartLedsWrite` trait.
  let writer = Ws2812::new(spi);

  // First, set up a Driver. It will be responsible for communicating with the LED strip.
  // DriverBuilder helps construct a Driver, which is a little bit complicated, since
  // `smart_leds` supports different filters for different chips.
  let driver = DriverBuilder::new(writer)
    // For instance, only RGB8 pixels (as opposed to four-channel RGBA pixels) can be passed
    // through the gamma filter. Since the Ws2812 chip works with RGB8, this method can be
    // called here. IDEs won't supply the code hint (and rustc won't compile this code) for
    // other types of smart pixels. The authors of `smart_leds` recommend using this filter to
    // "make orange look orange," and, indeed, colors appear washed out when the filter is
    // disabled. Despite the recommendation, that crate requires end-user action to enable
    // the filter, so this crate follows suit.
    .enable_gamma_correction(true)
    // …and we're done!
    .build();

  // This library is built around a metaphor of animating cartoons, so you'll encounter a
  // `Director`, frames (as in a filmstrip, not computer memory), and other such terminology
  // along the way. The Director is responsible for orchestrating the light show;
  // even the execution loop is managed by the Director. The metaphors get a little mixed
  // here—would a Director ever call "action" except in a room full of flesh-and-blood
  // actors?—but don't get distracted; you're almost there!
  let mut director = DirectorBuilder::new(driver)
    // Optionally specify a function to be called at the end of each execution loop. Useful
    // for controlling the speed of animations, which depends on several factors, including
    // the number of pixels on the strip and the hardware used to drive it. Sleeping here can
    // be a good way to control the "frame rate" of the overall show. Because every hardware
    // abstraction layer will implement sleep differently, this library purposefully does not
    // attempt to implement a generic sleep function but rather provides a place for end-users
    // to hook in their own.
    .set_post_loop_fn(&|| {
      arduino_hal::delay_ms(10);
    })
    .build();

  // Define a strip of 100 pixels with the LEDs initialized in the "off" setting.
  let mut pixels = [RGB8::default(); 100];

  // Initialize a Snake animation.
  let mut snake = Snake::new(
    // the color of the snake
    RGB8 { r: 255, g: 180, b: 47},
    // if true the snake chases its tail; otherwise the snake will completely disappear
    // from the strip before the animation loops
    true,
    // every pixel on the strip is being used for this animation (these are indexes of `pixels`)
    0..=99,
    // the snake moves away from the arduino rather than toward it
    true,
    // the snake will have a 24-pixel-long tail, plus one pixel (always) for the head,
    // making for a 25-pixel-long snake
    24,
  );

  // Lights, camera…
  director.action(
  // Note that all arguments are passed by reference. Because of the decision not to
  // require an allocator, the library depends on end-users to satisfy the compiler's
  // need for the sizes of all values to be known at compile time. Thus, `pixels` is a
  // reference to a fixed-length array, and…
    &mut pixels,
    // …animations (the second argument) is a reference to a fixed-length array of
    // references to Animate trait objects.
    &mut [
      &mut snake,
      // Other animations could have been passed here.
    ],
  );
}

Re-exports§

pub use smart_leds;

Modules§

animate
Tools for developing animations, both ready-made and custom.
animations
Ready-made animations for end-users.
composition
Tools for grouping together individual animations and treating them as one.
harness
Tools for orchestrating animations and communicating with the LED strip.