Skip to content
/ sled Public

Spatial LED: a library to help you get the coolest effects possible out of your bedroom LED strips.

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT
Notifications You must be signed in to change notification settings

DavJCosby/sled

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build and Run Tests

Spatial LED (SLED)

SLED is a rust library for creating spatial lighting effects for individually addressable LED strips. API ergonomics and performance are top priorities for this project. That said, SLED is still in its early stages of development which means there is plenty of room for improvement in both categories.

What SLED does do:

  • It exposes an API that lets you:
    • Modify virtual LED colors depending on each LED's position, distance, direction, line segment, etc;
    • Output that color data in a simple, contiguous data structure for your own usage;
    • Filter LEDs by their spatial properties to pre-compute important sets;
  • Additionally, some tools are provided to help you build functional apps faster (you may opt-out with compiler features):
    • Driver - Pack draw/compute logic into a Driver to simplify to the process of swapping between effects, or changing effect settings at runtime.
    • Scheduler - Lightweight tool to schedule redraws at a fixed rate, powered by spin_sleep.

What SLED does not do:

  • It does not interface directly with your GPIO pins to control your LED hardware. Each project will be different, so it's up to you to bring your own glue. Check out my personal raspberry pi implementation to get an idea of what that might look like.
  • It does not allow you to represent your LEDs in 3D space. Could be a fun idea in the future, but it's just not planned for the time being.

The Basics

In absence of an official guide, this will serve as a basic introduction to Sled. From here, you can use the documentation comments to learn what else Sled offers.

Setup

To create a Sled struct, you need to create a configuration file and provide its path to the constructor:

type Rgb8 = Rgb<_, u8>;

use sled::Sled;
fn main() -> Result<(), sled::SledError> {
    let mut sled = Sled::new::<Rgb8>("/path/to/config.toml")?;
    Ok(())
}

A configuration file explains the layout of your LED strips in 2D space. This is used to pre-calculate some important information, speeding up complex draw calls.

Example .toml file:

center_point = [0.0, 0.5]
density = 30.0

[[line_segment]]
start = [-2.0, 0.0]
end = [0.5, -1.0]

[[line_segment]]
start = [0.5, -1.0]
end = [3.5, 0.0]

[[line_segment]]
start = [3.5, 0.0]
end = [2, 2]

[[line_segment]]
start = [2.0, 2]
end = [-2.0, 2]
[[line_segment]]
start = [-2.0, 2]
end = [-2.0, 0.0]
  • center_point is a static reference point you can use to speed up draw calls. At initialization, directions, distances, etc relative to this point are pre-calculated for each Led.
  • density represents how many LED's per unit we can expect for the line segments below. If one or more LED strip has a different density for whatever reason, you can override this default for each [[line_segment]].
  • Add as many [[line_segment]] tags as you need to represent your scene.

Drawing

Once you have your Sled struct, you can start drawing to it right away! Here’s a taste of some of the things SLED lets you do:

Set all vertices to white:

sled.set_vertices(Rgb::new(1.0, 1.0, 1.0));

Set all Vertices

Note that this is a custom terminal UI visualization that is not packaged as part of the sled crate. It is ultimately up to you to decide how to visualize your LEDs, Sled just handles the computation.

Set all LEDs 2 units away from the center_point to red:

sled.set_at_dist(2.0, Rgb::new(1.0, 0.0, 0.0))?;

Set at Distance

Set each LED using a function of its direction from point (2, 1):

 sled.map_by_dir_from(Vec2::new(2.0, 1.0), |dir| {
     let red = (dir.x + 1.0) * 0.5;
     let green = (dir.y + 1.0) * 0.5;
     Rgb::new(red, green, 0.5)
 });

Map by Direction

Dim one of the walls by 75%:

sled.modulate_segment(3, |led| led.color * 0.25)?;

Modulate Segment

Set all LEDs within the overlapping areas of two different circles to blue:

let circle_1: Filter = sled.get_within_dist_from(
        2.0,
        Vec2::new(1.0, 0.5)
    );
    
let circle_2: Filter = sled.get_within_dist_from(
	2.5,
	Vec2::new(-1.0, 1.5)
);

let overlap = circle_1.and(&circle_2);
sled.set_filter(&overlap, Rgb::new(0.0, 0.0, 1.0));

Set Overlapping Areas For more examples, see the documentation comments on the Sled struct.

Output

Once you’re ready to display these colors, you’ll probably want them packed in a nice contiguous array of RGB values. There are a few methods available to pack the information you need.

// collect an ordered vector of Rgbs, 32-bits/channel
let colors_f32: Vec<Rgb> = sled.read_colors();
// collect an ordered vector of Rgbs, 8-bits/channel (overhead for conversion)
let colors_u8: Vec<Rgb<_, u8>> = sled.read_colors();

let positions: Vec<Vec2> = sled.read_positions();

let colors_and_positions: Vec<(Rgb, Vec2)> =
    sled.read_colors_and_positions();

Advanced Features

For basic applications, the Sled struct gives you plenty of power. Odds are though, you'll want to create more advanced effects that might be time or user-input driven. A few optional (enabled by default, opt-out by disabling their compiler features) tools are provided to streamline that process.

Drivers

Drivers are useful for encapsulating everything you need to drive a lighting effect all in one place. Here's an example of what a simple one might look like:

let mut driver = Driver::new();

driver.set_startup_commands(|_sled, buffers, _filters| {
    let colors = buffers.create_buffer::<Rgb>("colors");
    colors.push(Rgb::new(1.0, 0.0, 0.0));
    colors.push(Rgb::new(0.0, 1.0, 0.0));
    colors.push(Rgb::new(0.0, 0.0, 1.0));
    Ok(())
});

driver.set_draw_commands(|sled, buffers, _filters, time_info| {
    sled.set_all(Rgb::new(0.0, 0.0, 0.0));

    let elapsed = time_info.elapsed.as_secs_f32();
    let colors = buffers.get_buffer::<Rgb>("colors")?;
    let num_colors = colors.len();

    for i in 0..num_colors {
        let alpha = i as f32 / num_colors as f32;
        let angle = elapsed + (TAU * alpha);
        sled.set_at_angle(angle, colors[i])?;
    }

    Ok(())
});

To start using the Driver, give it ownership over a Sled using .mount() and use .step() to manually refresh it.

let sled = Sled::new("path/to/config.toml")?;
driver.mount(sled); // sled gets moved into driver here.

loop {
    driver.step();
}

Basic Time-Driven Effect Using Buffers

.set_startup_commands() - Define a function or closure to run when driver.mount() is called. Grants mutable control over Sled, BufferContainer, and Filters.

set_draw_commands() - Define a function or closure to run every time driver.step() is called. Grants mutable control over Sled, and immutable access to BufferContainer, Filters, and TimeInfo.

set_compute_commands() - Define a function or closure to run every time driver.step() is called, scheduled right before draw commands. Grants immutable access to Sled, mutable control over BufferContainer and Filters and immutable access to TimeInfo.

Buffers

A driver exposes a data structure called BufferContainer. A BufferContainer essentially acts as a HashMap of &str keys to Vectors of any type you choose to instantiate. This is particularly useful for passing important data and settings in to the effect.

It's best practice to create buffers with startup commands, and then modify them either through compute commands or from outside the driver depending on your needs.

fn startup(sled: &mut Sled, buffers: &mut BufferContainer, _filters: &mut Filters) -> Result<(), SledError> {
    let wall_toggles: &mut Vec<bool> = buffer.create_buffer("wall_toggles");
    let wall_colors: &mut Vec<Rgb> = buffer.create_buffer("wall_colors");
    let some_important_data = buffer.create_buffer::<MY_CUSTOM_TYPE>("important_data");
    Ok(())
}

driver.set_startup_commands(startup);

To maniplate buffers from outside driver, just do:

let buffers: &BufferContainer = driver.buffers();
// or
let buffers: &mut BufferContainer = driver.buffers_mut();

Using a BufferContainer is relatively straightforward.

let draw_commands = |sled, buffers, _, _| {
    let wall_toggles = buffers.get_buffer::<bool>("wall_toggles")?;
    let wall_colors = buffers.get_buffer::<Rgb>("wall_colors")?;
    let important_data = buffers.get_buffer::<MY_CUSTOM_TYPE>("important_data")?;

    for i in 0..wall_toggles.len() {
        if wall_toggles[i] == true {
            sled.set_segment(i, wall_colors[i])?;
        } else {
            sled.set_segment(i, Rgb::new(0.0, 0.0, 0.0))?;
        }
    }
    
    Ok(())
}

If you need to mutate buffer values:

// Mutable reference to the whole buffer
let buffer_mut = buffers.get_buffer_mut::<bool>("wall_toggles")?;

// Modify just one item
buffers.set_buffer_item("wall_toggles", 1, false);

// Mutable reference to just one item
let color: &mut Rgb = buffers.get_buffer_item_mut("wall_colors", 2)?;
*color /= 2.0;

TODO, but will cover:

  • Drivers
    • Draw, Compute, and Startup commands
    • Buffers
    • The Filter Container
  • Scheduler
  • Named Color consts? (opt-in feature)

About

Spatial LED: a library to help you get the coolest effects possible out of your bedroom LED strips.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

No packages published