350 lines
9.9 KiB
Rust
350 lines
9.9 KiB
Rust
use ws281x_rpi::Ws2812Rpi;
|
|
|
|
use crate::{DBPool, animations::Animation};
|
|
|
|
use std::error::Error;
|
|
use smart_leds::{RGB8, SmartLedsWrite, RGB};
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
#[derive(Clone, Debug)]
|
|
struct LocalLedConfiguration {
|
|
tag: Option<String>,
|
|
start: usize,
|
|
end: usize
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct LigthSetting {
|
|
start: Option<usize>,
|
|
end: Option<usize>,
|
|
tags: Option<String>,
|
|
color: RGB8,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct KeyFrame {
|
|
duration: u64,
|
|
settings: Vec<LigthSetting>
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct RenderAnimation {
|
|
key_frames: Vec<KeyFrame>,
|
|
priority: u32,
|
|
name: String,
|
|
|
|
current_frame: usize,
|
|
start_time: u64,
|
|
reset: bool,
|
|
}
|
|
|
|
pub struct Render {
|
|
conn: DBPool,
|
|
num_leds: u32,
|
|
|
|
last_render: u64,
|
|
|
|
ws: Option<Ws2812Rpi>,
|
|
last: Vec<RGB<f64>>,
|
|
|
|
animations: Vec<RenderAnimation>,
|
|
|
|
local_led_config: Vec<LocalLedConfiguration>,
|
|
}
|
|
|
|
fn try_into_usize (input: Option<u32>) -> Option<usize> {
|
|
if input.is_none() {return None;}
|
|
|
|
let t: Result<usize, _> = input.unwrap().try_into();
|
|
|
|
if t.is_err() {return None;}
|
|
|
|
return t.ok();
|
|
}
|
|
|
|
impl Render {
|
|
pub fn new(conn: DBPool) -> Result<Render, Box<dyn Error>> {
|
|
let mut render = Render {
|
|
conn,
|
|
num_leds: 0,
|
|
last_render: 0,
|
|
ws: None,
|
|
animations: vec![],
|
|
local_led_config: vec![],
|
|
last: vec![],
|
|
};
|
|
|
|
render.load_config()?;
|
|
|
|
return Ok(render);
|
|
}
|
|
|
|
pub fn load_config(&mut self) -> Result<(), Box<dyn Error>> {
|
|
println!("Load config");
|
|
// GPIO Pin 10 is SPI
|
|
// Other modes and PINs are available depending on the Raspberry Pi revision
|
|
// Additional OS configuration might be needed for any mode.
|
|
// Check https://github.com/jgarff/rpi_ws281x for more information.
|
|
const PIN: i32 = 18;
|
|
|
|
let conn = self.conn.get()?;
|
|
|
|
let stmt = conn.prepare("SELECT ledcount, tags from configuration;");
|
|
|
|
if stmt.is_err() || stmt.as_ref().ok().is_none() {
|
|
Err(format!("Something went wrong while reading the database {:?}", stmt.as_ref().err()))?;
|
|
}
|
|
|
|
let mut stmt = stmt?;
|
|
|
|
struct Data {
|
|
ledcount: u32,
|
|
tag: String
|
|
}
|
|
|
|
let version_iter = stmt.query_map([], |row| {
|
|
Ok(Data {
|
|
ledcount: row.get(0)?,
|
|
tag: row.get(1)?
|
|
})
|
|
});
|
|
|
|
if version_iter.is_err() || version_iter.as_ref().ok().is_none() {
|
|
Err(format!("Something went wrong while reading the database {:?}", version_iter.as_ref().err()))?;
|
|
}
|
|
|
|
let version_iter = version_iter.ok().unwrap();
|
|
|
|
let mut current_pos = 0;
|
|
|
|
let mut local_led_config = Vec::new();
|
|
|
|
for data in version_iter {
|
|
|
|
let data = data?;
|
|
|
|
let tag = if data.tag.is_empty() { None } else { Some(data.tag) };
|
|
|
|
let start = current_pos;
|
|
let end = current_pos + data.ledcount;
|
|
current_pos = end;
|
|
|
|
let start: Result<usize, _> = start.try_into();
|
|
let end: Result<usize, _> = end.try_into();
|
|
|
|
if start.is_err() || start.ok().is_none() || end.is_err() || end.ok().is_none() {
|
|
println!("Skiping loading configuration");
|
|
continue;
|
|
}
|
|
|
|
let start = start.ok().unwrap();
|
|
let end = end.ok().unwrap();
|
|
|
|
local_led_config.push(LocalLedConfiguration {
|
|
tag,
|
|
start,
|
|
end
|
|
});
|
|
}
|
|
|
|
self.num_leds = current_pos;
|
|
println!("Loaded: {} leds, with config: {:?}", self.num_leds, local_led_config);
|
|
self.local_led_config = local_led_config;
|
|
|
|
self.ws = Some(Ws2812Rpi::new(current_pos as i32, PIN).unwrap());
|
|
|
|
println!("Here");
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn add_animation(&mut self, animation: Animation) {
|
|
|
|
println!("Added animation");
|
|
|
|
let mut key_frames = Vec::new();
|
|
|
|
for frame in animation.get_frames().iter() {
|
|
let mut light_settings = Vec::new();
|
|
|
|
for setting in frame.get_settings() {
|
|
light_settings.push(LigthSetting {
|
|
tags: setting.get_tags(),
|
|
start: try_into_usize(setting.get_start()),
|
|
end: try_into_usize(setting.get_end()),
|
|
color: RGB8::new(setting.get_r(), setting.get_g(), setting.get_b()),
|
|
});
|
|
}
|
|
|
|
key_frames.push(KeyFrame { duration: frame.get_duration().into(), settings: light_settings })
|
|
}
|
|
|
|
let new_animation = RenderAnimation {
|
|
key_frames,
|
|
priority: animation.get_priority(),
|
|
name: animation.get_name(),
|
|
current_frame: 0,
|
|
start_time: 0,
|
|
reset: animation.get_repeat(),
|
|
};
|
|
|
|
self.animations.push(new_animation);
|
|
|
|
self.animations.sort_by(|a,b| a.priority.partial_cmp(&b.priority).unwrap())
|
|
}
|
|
|
|
pub fn blank(&mut self) {
|
|
self.animations = Vec::new();
|
|
}
|
|
|
|
pub fn remove_animation(&mut self, animation_name: String) {
|
|
self.animations = self.animations.clone().into_iter().filter(|ani| !ani.name.eq(&animation_name)).collect::<Vec<RenderAnimation>>();
|
|
}
|
|
|
|
pub fn proccess_settings (&mut self, data: &mut Vec<RGB<f64>>, settings: &Vec<LigthSetting>) {
|
|
fn load_led_into_light (data: &mut Vec<RGB<f64>>, color: RGB8, start: usize, end: usize) {
|
|
for i in start..=end {
|
|
data[i] = RGB::new(
|
|
color.r as f64,
|
|
color.g as f64,
|
|
color.b as f64
|
|
);
|
|
}
|
|
}
|
|
|
|
for setting in settings {
|
|
if setting.tags.is_some() {
|
|
let tags = setting.tags.as_ref().unwrap().to_string();
|
|
let tags = tags.split(",");
|
|
let tags_fn = |t: String| tags.clone().into_iter().any(|tag| tag.eq(&t));
|
|
for config in self.local_led_config.clone() {
|
|
if config.tag.is_some() && tags_fn(config.tag.unwrap()) {
|
|
load_led_into_light(data, setting.color, config.start, config.end);
|
|
}
|
|
}
|
|
}
|
|
|
|
if setting.start.is_some() && setting.end.is_some() {
|
|
load_led_into_light(data, setting.color, setting.start.unwrap(), setting.end.unwrap());
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn render(&mut self) {
|
|
|
|
if self.ws.is_none() { return; }
|
|
|
|
let mut data: Vec<RGB<f64>> = vec![RGB::default(); self.num_leds.try_into().unwrap()];
|
|
|
|
let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs();
|
|
|
|
if self.last_render == 0 {
|
|
self.last_render = time;
|
|
}
|
|
|
|
let delta = time - self.last_render;
|
|
|
|
self.last_render = time;
|
|
|
|
let mut to_clean: Vec<String> = Vec::new();
|
|
|
|
let animations = self.animations.clone().iter_mut().map(|animation| {
|
|
let mut animation = animation.to_owned();
|
|
|
|
let mut start_time = animation.start_time;
|
|
|
|
if start_time == 0 {
|
|
animation.start_time = time;
|
|
animation.current_frame = 0;
|
|
|
|
self.proccess_settings(&mut data, &animation.key_frames[0].settings);
|
|
return animation;
|
|
}
|
|
|
|
while start_time + animation.key_frames[animation.current_frame].duration < time {
|
|
start_time += animation.key_frames[animation.current_frame].duration;
|
|
animation.start_time = start_time;
|
|
|
|
if animation.current_frame + 1 >= animation.key_frames.len() {
|
|
if animation.reset {
|
|
animation.start_time = time;
|
|
animation.current_frame = 0;
|
|
self.proccess_settings(&mut data, &animation.key_frames[0].settings);
|
|
} else {
|
|
to_clean.push(animation.name.to_string());
|
|
}
|
|
return animation;
|
|
}
|
|
|
|
animation.current_frame += 1;
|
|
}
|
|
|
|
let cf = animation.current_frame;
|
|
|
|
self.proccess_settings(&mut data, &animation.key_frames[cf].settings);
|
|
|
|
return animation;
|
|
}).collect();
|
|
|
|
self.animations = animations;
|
|
|
|
to_clean.iter().for_each(|i| self.remove_animation(i.to_string()));
|
|
|
|
if self.last.is_empty() || !self.last.clone().iter().eq(data.clone().iter()) {
|
|
|
|
if self.last.len() == 0 {
|
|
self.last = data.clone();
|
|
}
|
|
|
|
|
|
let lerp_data: Vec<RGB<f64>> = data.iter().zip(&self.last).map(|(d, l)| l.lerp(d, delta as f64)).collect();
|
|
|
|
//println!("d:{:?}\n l:{:?}", data, lerp_data);
|
|
|
|
let to_set_data: Vec<RGB<u8>> = rgbs_f64_to_u8(&lerp_data);
|
|
|
|
let err = self.ws.as_mut().unwrap().write(to_set_data.into_iter());
|
|
|
|
if err.is_err() || err.ok().is_none() {
|
|
println!("Failed to write data");
|
|
}
|
|
|
|
self.last = lerp_data;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
pub fn rgbs_f64_to_u8(data: &Vec<RGB<f64>>) -> Vec<RGB<u8>>{
|
|
data.clone().iter().map(|a| {
|
|
RGB::new(
|
|
a.r as u8,
|
|
a.g as u8,
|
|
a.b as u8
|
|
)
|
|
}).collect()
|
|
}
|
|
|
|
pub trait Lerp {
|
|
fn lerp(self, other: &Self, diff: f64) -> Self;
|
|
}
|
|
|
|
impl Lerp for f64 {
|
|
fn lerp(self, other: &Self, diff: f64) -> Self {
|
|
let delta = diff / 1_000.0;
|
|
let lerped = self + ((*other - self) * 0.5 * delta);
|
|
|
|
if *other != self {
|
|
println!("c: {} target:{} lerp: {}", self, other, lerped);
|
|
}
|
|
|
|
lerped
|
|
}
|
|
}
|
|
|
|
impl Lerp for RGB<f64> {
|
|
fn lerp(self, other: &Self, diff: f64) -> Self {
|
|
RGB::new(self.r.lerp(&other.r, diff), self.g.lerp(&other.g, diff), self.b.lerp(&other.b, diff))
|
|
}
|
|
}
|