Initial Commit

This commit is contained in:
Andre Henriques 2023-03-13 20:59:50 +00:00
commit 40a109637f
8 changed files with 2899 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
/target
# Added by cargo
#
# already existing elements were commented out
#/target

1724
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

17
Cargo.toml Normal file
View File

@ -0,0 +1,17 @@
[package]
name = "neolights"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
smart-leds = "0.3.0"
ws281x-rpi = "0.0.1"
rocket = { version="0.5.0-rc.2", features = ["json"] }
# rusqlite = { version = "0.28.0", features = ["bundled"] }
r2d2 = "0.8.10"
r2d2_sqlite = { version = "0.21.0", features = ["bundled"] }
tokio = { version = "1.21.1", features = ["full"] }
serde = "1.0.145"

393
src/animations.rs Normal file
View File

@ -0,0 +1,393 @@
use std::{u8, error::Error};
use rocket::{serde::{json::{Json, serde_json::json}, Deserialize, Serialize}, State};
use crate::{utils::ApiResponse, DBConn, ASender, Action};
use crate::DBPool;
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(crate = "rocket::serde")]
#[serde(rename_all = "camelCase")]
pub struct LigthSetting {
start: Option<u32>,
end: Option<u32>,
tags: Option<String>,
r: u8,
b: u8,
g: u8,
}
impl LigthSetting {
pub fn get_start(&self) -> Option<u32>{
self.start
}
pub fn get_end(&self) -> Option<u32>{
self.end
}
pub fn get_tags(&self) -> Option<String>{
if self.tags.as_ref().is_some() {
return Some(self.tags.as_ref().unwrap().to_string());
}
None
}
pub fn get_r(&self) -> u8 {
self.r
}
pub fn get_b(&self) -> u8 {
self.b
}
pub fn get_g(&self) -> u8 {
self.g
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(crate = "rocket::serde")]
#[serde(rename_all = "camelCase")]
pub struct KeyFrame {
duration: u32,
settings: Vec<LigthSetting>
}
impl KeyFrame {
pub fn get_settings(&self) -> Vec<LigthSetting> {
self.settings.clone()
}
pub fn get_duration(&self) -> u32 {
self.duration
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(crate = "rocket::serde")]
#[serde(rename_all = "camelCase")]
pub struct Animation {
key_frames: Vec<KeyFrame>,
priority: Option<u32>,
name: String,
repeat: bool,
}
impl Animation {
pub fn get_frames(&self) -> Vec<KeyFrame> {
self.key_frames.clone()
}
pub fn get_name(&self) -> String {
self.name.to_string()
}
pub fn get_priority(&self) -> u32 {
if self.priority.is_none() { 0 } else {self.priority.unwrap()}
}
pub fn get_repeat(&self) -> bool {
self.repeat
}
}
#[post("/animation", data= "<data>")]
pub async fn animation(data: Json<Animation>, db: &State<DBPool>) -> String {
if data.key_frames.is_empty() {
return json!(ApiResponse {code: 400, message: "KeyFrame are nessesary".to_string()}).to_string();
}
if data.name.is_empty() {
return json!(ApiResponse {code: 400, message: "Name must not be empty".to_string()}).to_string();
}
for key_frame in data.key_frames.iter() {
if key_frame.settings.is_empty() {
return json!(ApiResponse {code: 400, message: "keyFrames.settings can not be empty".to_string()}).to_string();
}
if key_frame.duration == 0 {
return json!(ApiResponse {code: 400, message: "keyFrames.duration can not be 0".to_string()}).to_string();
}
for setting in key_frame.settings.iter() {
if setting.tags.is_none() && (setting.start.is_none() || setting.end.is_none()) {
return json!(ApiResponse {code: 400, message: "Either tags or start end pair is needed".to_string()}).to_string();
}
if !(setting.start.is_none() && setting.end.is_none()) && (setting.start.is_none() || setting.end.is_none()) {
return json!(ApiResponse {code: 400, message: "Both start and end need to be filled".to_string()}).to_string();
}
}
}
let db_status = add_animation_to_db(data, db);
if db_status.is_err() || db_status.as_ref().ok().is_none() {
return json!(ApiResponse {code: 500, message: format!("Error setting the animation. Err: {:?}", db_status.err()).to_string()}).to_string();
}
let db_status = db_status.ok().unwrap();
if !db_status.is_empty() {
return db_status;
}
json!(ApiResponse {code: 200, message: "Configuration was successful".to_string()}).to_string()
}
fn get_animation_from_name(conn: &DBConn, name: String) -> Result<Option<u32>, Box<dyn Error>> {
let mut stmt = conn.prepare("SELECT id FROM animation WHERE name=?1")?;
let next = stmt.query_map([name], |row| {
row.get(0)
})?.next();
if next.is_none() {
return Ok(None);
}
Ok(next.unwrap()?)
}
fn get_keyframe_from_index(conn: &DBConn, animation: u32, index: u32) -> Result<Option<u32>, Box<dyn Error>> {
let mut stmt = conn.prepare("SELECT id FROM keyframe WHERE animation=?1 AND i=?2")?;
let next = stmt.query_map([animation, index], |row| {
row.get(0)
})?.next();
if next.is_none() {
return Ok(None);
}
Ok(next.unwrap()?)
}
fn add_animation_to_db(data: Json<Animation>, pool: &State<DBPool>) -> Result<String, Box<dyn Error>>{
let priority = if data.priority.is_some() { data.priority.unwrap() } else { 0 };
let conn = pool.get()?;
println!("g: {}", data.name);
let name = get_animation_from_name(&conn, data.name.to_string())?;
println!("t: {:?}", name);
if name.is_some() {
return Ok(json!(ApiResponse {code: 500, message: "Animation with that name is already exists.".to_string()}).to_string());
}
conn.execute("INSERT INTO animation (priority, name, repeat) VALUES (?1, ?2, ?3);", (priority, data.name.to_string(), data.repeat ))?;
let name = get_animation_from_name(&conn, data.name.to_string())?;
if name.is_none() {
return Ok(json!(ApiResponse {code: 500, message: "Failed to create animation.".to_string()}).to_string());
}
let animation_id = name.unwrap();
let mut i = 0;
for key_frame in data.key_frames.clone() {
conn.execute("INSERT INTO keyframe (duration, animation, i) VALUES (?1, ?2, ?3);", (key_frame.duration, animation_id, i))?;
let keyframe = get_keyframe_from_index(&conn, animation_id, i)?;
if keyframe.is_none() {
return Ok(json!(ApiResponse {code: 500, message: "Failed to create animation.".to_string()}).to_string());
}
let keyframe = keyframe.unwrap();
for setting in key_frame.settings {
conn.execute("INSERT INTO lightsetting (start, end, tags, r, b, g, keyframe) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7);", (setting.start, setting.end, setting.tags, setting.r, setting.b, setting.g, keyframe))?;
}
i += 1;
}
Ok("".to_string())
}
/**
*
* Stop animations
*
*/
#[get("/stop/<name>")]
pub async fn stop_animation(name: &str, stop_sender: &State<ASender<String>>) -> String {
let r = stop_sender.lock().unwrap().send(name.to_string());
if r.is_err() || r.ok().is_none() {
return json!(ApiResponse {code: 500, message: "Something went wrong".to_string()}).to_string()
}
json!(ApiResponse {code: 200, message: "Configuration was successful".to_string()}).to_string()
}
/**
*
* Start animations
*
*/
#[get("/start/<name>")]
pub async fn start_animation(name: &str, start_sender: &State<ASender<Animation>>, db: &State<DBPool>) -> String {
let animation = get_animation(name, db);
if animation.is_err() || animation.as_ref().ok().is_none() {
return json!(ApiResponse {code: 500, message: format!("Probelms with the db: {:?}", animation.err()).to_string()}).to_string()
}
let animation = animation.ok().unwrap();
if animation.is_none() {
return json!(ApiResponse {code: 404, message: "Animation not found".to_string()}).to_string()
}
let animation = animation.unwrap();
let r = start_sender.lock().unwrap().send(animation);
if r.is_err() || r.as_ref().ok().is_none() {
return json!(ApiResponse {code: 500, message: "Probelms with the db".to_string()}).to_string()
}
json!(ApiResponse {code: 200, message: "Started animation".to_string()}).to_string()
}
/**
*
* Clear animations
*
*/
#[get("/clear/<name>")]
pub async fn clear(name: &str, start_sender: &State<ASender<Animation>>, db: &State<DBPool>, action: &State<ASender<Action>>) -> String {
let r = action.lock().unwrap().send(Action::Clear);
if r.is_err() || r.ok().is_none() {
return json!(ApiResponse {code: 500, message: "Something went wrong".to_string()}).to_string()
}
let animation = get_animation(name, db);
if animation.is_err() || animation.as_ref().ok().is_none() {
return json!(ApiResponse {code: 500, message: format!("Probelms with the db: {:?}", animation.err()).to_string()}).to_string()
}
let animation = animation.ok().unwrap();
if animation.is_none() {
return json!(ApiResponse {code: 404, message: "Animation not found".to_string()}).to_string()
}
let animation = animation.unwrap();
let r = start_sender.lock().unwrap().send(animation);
if r.is_err() || r.as_ref().ok().is_none() {
return json!(ApiResponse {code: 500, message: "Probelms with the db".to_string()}).to_string()
}
json!(ApiResponse {code: 200, message: "Started animation".to_string()}).to_string()
}
pub fn get_animation(name: &str, db: &State<DBPool>) -> Result<Option<Animation>, Box<dyn Error>> {
let conn = db.get()?;
let mut stmt = conn.prepare("SELECT id, priority, name, repeat FROM animation WHERE name=?1;")?;
struct AnimationId {
animation: Animation,
id: u8
};
let next = stmt.query_map([name], |row| {
Ok(AnimationId {
animation: Animation {
key_frames: Vec::new(),
priority: Some(row.get(1)?),
name: row.get(2)?,
repeat: row.get(3)?
},
id: row.get(0)?
})
})?.next();
if next.is_none() {
return Ok(None);
}
let mut animation = next.unwrap()?;
struct KeyFrameId {
keyframe: KeyFrame,
id: u8,
}
let mut stmt = conn.prepare("SELECT id, duration from keyframe where animation=?1 order by i asc")?;
let mut map = stmt.query_map([animation.id], |row| {
Ok(KeyFrameId {
id: row.get(0)?,
keyframe: KeyFrame {
duration: row.get(1)?,
settings: Vec::new()
}
})
})?;
while let Some(key_frame_id) = map.next() {
let mut key_frame_id = key_frame_id?;
let mut stmt = conn.prepare("SELECT start, end, tags, r, b, g from lightsetting where keyframe=?1")?;
let mut light_setting_map = stmt.query_map([key_frame_id.id], |row| {
Ok(LigthSetting {
start: row.get(0)?,
end: row.get(1)?,
tags: row.get(2)?,
r: row.get(3)?,
b: row.get(4)?,
g: row.get(5)?
})
})?;
while let Some(light_setting) = light_setting_map.next() {
key_frame_id.keyframe.settings.push(light_setting?);
}
animation.animation.key_frames.push(key_frame_id.keyframe);
}
Ok(Some(animation.animation))
}
/**
*
* Delete animations
*
*/
#[get("/delete/<name>")]
pub async fn delete(name: &str, start_sender: &State<ASender<Animation>>, db: &State<DBPool>) -> String {
let animation = remove_animation(name, db);
if animation.is_err() || animation.as_ref().ok().is_none() {
return json!(ApiResponse {code: 500, message: format!("Probelms with the db: {:?}", animation.err()).to_string()}).to_string()
}
json!(ApiResponse {code: 200, message: "Deleted animation".to_string()}).to_string()
}
pub fn remove_animation(name: &str, db: &State<DBPool>) -> Result<(), Box<dyn Error>> {
let conn = db.get()?;
conn.execute("delete from lightsetting where id in (select l.id from lightsetting as l left join keyframe as k on l.keyframe = k.id left join animation as a on k.animation = a.id where a.name=?1);", [name])?;
conn.execute("delete from keyframe where id in (select k.id from keyframe as k left join animation as a on k.animation = a.id where a.name=?1);", [name])?;
conn.execute("delete from animation where name=?1;", [name])?;
Ok(())
}

102
src/configure.rs Normal file
View File

@ -0,0 +1,102 @@
use rocket::{serde::{json::{Json, serde_json::json}, Deserialize, Serialize}, State};
use crate::{utils::ApiResponse, Action, ASender};
use crate::DBPool;
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(crate = "rocket::serde")]
#[serde(rename_all = "camelCase")]
pub struct LightConfiguration {
number: u32,
direction_x: u32,
direction_y: u32,
direction_z: u32,
tags: Option<String>
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(crate = "rocket::serde")]
#[serde(rename_all = "camelCase")]
pub struct LightConfigurationRequest {
lights: Vec<LightConfiguration>
}
#[post("/configure", data= "<data>")]
pub async fn configure(data: Json<LightConfigurationRequest>, db: &State<DBPool>, action: &State<ASender<Action>>) -> String {
let conn = db.get();
if conn.is_err() || conn.as_ref().ok().is_none() {
return json!(ApiResponse {code: 500, message: "Clould not configure. Err: 1".to_string()}).to_string();
}
let conn = conn.ok().unwrap();
let stmt = conn.prepare("SELECT configured from meta");
if stmt.is_err() || stmt.as_ref().ok().is_none() {
return json!(ApiResponse {code: 500, message: format!("Clould not configure. Err: 2, {:?}", stmt)}).to_string();
}
let mut stmt = stmt.ok().unwrap();
struct Data {
configured: bool,
}
let version_iter = stmt.query_map([], |row| {
Ok(Data {
configured: row.get(0)?
})
});
if version_iter.is_err() || version_iter.as_ref().ok().is_none() {
return json!(ApiResponse {code: 500, message: "Clould not configure. Err: 3".to_string()}).to_string();
}
let version_iter = version_iter.ok().unwrap();
let mut recreate = false;
for data in version_iter {
if data.unwrap().configured {
println!("Already configured deleting current");
recreate = true;
break;
}
}
drop(stmt);
if recreate {
let stmt = conn.execute("DELETE FROM configuration;", ());
if stmt.is_err() || stmt.as_ref().ok().is_none() {
return json!(ApiResponse {code: 500, message: format!("Clould not configure. Err: 4, {:?}", stmt)}).to_string();
}
}
for light in data.lights.iter() {
let mut tags: String = "".to_string();
if light.tags.is_some() {
tags = light.tags.as_ref().unwrap().to_string()
}
let stmt = conn.execute(
"INSERT INTO configuration (ledcount, directionX, directionY, directionZ, tags) VALUES (?1, ?2, ?3, ?4, ?5);",
(light.number, light.direction_x, light.direction_y, light.direction_z, tags));
if stmt.is_err() || stmt.as_ref().ok().is_none() {
return json!(ApiResponse {code: 500, message: format!("Clould not configure. Err: 5, {:?}", stmt)}).to_string();
}
}
let send = action.lock().unwrap().send(Action::Reload);
if send.is_err() || send.ok().is_none() {
return json!(ApiResponse {code: 500, message: "Failed to reload".to_string()}).to_string()
}
json!(ApiResponse {code: 200, message: "Configuration was successful".to_string()}).to_string()
}

354
src/main.rs Normal file
View File

@ -0,0 +1,354 @@
#[macro_use]
extern crate rocket;
//use std::{thread, time::Duration, sync::Arc, io};
use ::std::sync::Arc;
mod animations;
mod configure;
mod render;
mod utils;
use animations::Animation;
use r2d2::PooledConnection;
use utils::ApiResponse;
use rocket::{State, serde::json::serde_json::json};
use r2d2_sqlite::SqliteConnectionManager;
//use std::sync::atomic::AtomicBool;
use std::{
error::Error,
sync::{
mpsc::{self, Receiver, Sender},
Mutex,
}
};
pub type DBPool = Arc<r2d2::Pool<r2d2_sqlite::SqliteConnectionManager>>;
pub type DBConn = PooledConnection<r2d2_sqlite::SqliteConnectionManager>;
pub type GResult = Result<(), Box<dyn Error>>;
pub type ASender<T> = Arc<Mutex<Sender<T>>>;
#[get("/")]
fn index() -> &'static str {
"Hello World"
}
pub enum Action {
Reload,
Stop,
Clear
}
#[get("/quit")]
async fn quit(shutdown: rocket::Shutdown, action: &State<ASender<Action>>) -> String {
println!("Got Shutdown");
let send = action.lock().unwrap().send(Action::Stop);
if send.is_err() || send.ok().is_none() {
return json!(ApiResponse {code: 500, message: "Failed to stop".to_string()}).to_string()
}
shutdown.notify();
json!(ApiResponse {code: 200, message: "Stoped".to_string()}).to_string()
}
#[get("/reload")]
async fn reload(action: &State<ASender<Action>>) -> String {
println!("Got Shutdown");
let send = action.lock().unwrap().send(Action::Reload);
if send.is_err() || send.ok().is_none() {
return json!(ApiResponse {code: 500, message: "Failed to reload".to_string()}).to_string()
}
json!(ApiResponse {code: 200, message: "Reloaded".to_string()}).to_string()
}
fn ligth_controll(
pool: DBPool,
action: Receiver<Action>,
start_ani: Receiver<Animation>,
stop_ani: Receiver<String>,
) {
println!("Data loop started");
let render = render::Render::new(pool.clone());
if render.is_err() || render.as_ref().ok().is_none() {
println!("something every wrong {:?}", render.err());
return;
}
let mut render = render.ok().unwrap();
'mainloop: loop {
let action = action.try_recv();
if action.is_ok() {
match action.ok().unwrap() {
Action::Reload => {
let result = render.load_config();
if result.is_err() || result.as_ref().ok().is_none() {
println!("Ehh failed to load config i guess: {:?}", result.err());
}
},
Action::Clear => {
render.blank();
},
Action::Stop => {
render.blank();
render.render();
break 'mainloop;
},
}
}
let ani = start_ani.try_recv();
if ani.is_ok() {
println!("Added animation");
//TODO
render.add_animation(ani.ok().unwrap());
//ani.ok().unwrap().
}
let ani = stop_ani.try_recv();
if ani.is_ok() {
render.remove_animation(ani.ok().unwrap());
}
render.render();
}
println!("stoped main loop");
}
const DATAVERSION: &str = "0.0.10";
fn setup_database(pool: DBPool) -> GResult {
let conn = pool.get()?;
println!("Trying to get meta data");
let stmt = conn.prepare("SELECT version from meta");
if stmt.is_err() || stmt.as_ref().ok().is_none() {
return create_database(pool);
}
let mut stmt = stmt?;
struct Data {
version: String,
}
let version_iter = stmt.query_map([], |row| {
Ok(Data {
version: row.get(0)?,
})
});
println!("Process meta");
if version_iter.is_err() || version_iter.as_ref().ok().is_none() {
create_database(pool)?;
} else {
let version_iter = version_iter.ok().unwrap();
let mut recreate = true;
for data in version_iter {
let version = data.unwrap().version;
println!("Found version {}", version);
if !DATAVERSION.to_string().eq(&version) {
println!("Mismatched versions recreating database");
recreate = true;
break;
}
recreate = false;
}
if recreate {
create_database(pool)?;
}
}
Ok(())
}
fn create_database(pool: DBPool) -> GResult {
let conn = pool.get()?;
println!("Createing the database");
println!("Createing new meta table. With version {}", DATAVERSION);
println!("Drop old meta table");
conn.execute("DROP TABLE IF EXISTS meta;", ())?;
// Create the database
println!("create new meta table");
conn.execute(
"CREATE TABLE meta (
id INTEGER PRIMARY KEY,
version TEXT,
configured Boolean
);",
(),
)?;
println!("insert data");
conn.execute(
"INSERT INTO meta (version, configured) VALUES (?1, ?2);",
(&DATAVERSION, false),
)?;
println!(
"Createing new configuration table. With version {}",
DATAVERSION
);
println!("Drop configuration table");
conn.execute("DROP TABLE IF EXISTS configuration", ())?;
println!("Create new configuration table");
conn.execute(
"CREATE TABLE configuration (
id INTEGER PRIMARY KEY,
ledcount INTEGER,
directionX INTEGER,
directionY INTEGER,
directionZ INTEGER,
tags VARCHAR(255)
);",
(),
)?;
println!(
"Createing new Animation table. With version {}",
DATAVERSION
);
println!("Drop Animation table and references");
conn.execute("DROP TABLE IF EXISTS lightsetting", ())?;
conn.execute("DROP TABLE IF EXISTS keyframe", ())?;
conn.execute("DROP TABLE IF EXISTS animation", ())?;
println!("Create new animation table");
conn.execute(
"CREATE TABLE animation (
id INTEGER PRIMARY KEY,
priority INTEGER,
name VARCHAR(255),
repeat BOOLEAN
);",
(),
)?;
println!(
"Createing new key_frame table. With version {}",
DATAVERSION
);
println!("Drop key_frame table");
println!("Create new key_frame table");
conn.execute(
"CREATE TABLE keyframe (
id INTEGER PRIMARY KEY,
i INTEGER,
duration INTEGER,
animation INTEGER,
FOREIGN KEY(animation) REFERENCES animation(id) ON DELETE CASCADE
);",
(),
)?;
println!(
"Createing new LigthSetting table. With version {}",
DATAVERSION
);
println!("Drop LigthSetting table");
println!("Create new light_setting table");
conn.execute(
"CREATE TABLE lightsetting (
id INTEGER PRIMARY KEY,
start INTEGER,
end INTEGER,
tags VARCHAR(255),
r INTEGER,
b INTEGER,
g INTEGER,
keyframe INTEGER,
FOREIGN KEY(keyframe) REFERENCES keyframe(id) ON DELETE CASCADE
);",
(),
)?;
Ok(())
}
#[rocket::main]
async fn main() -> GResult {
println!("Program start");
println!("Try to connect db");
let sqlite_conn_manager = SqliteConnectionManager::file("ligths.db");
let conn_pool = r2d2::Pool::new(sqlite_conn_manager).expect("Failed to create pool");
let conn_pool_arc = Arc::new(conn_pool);
{
let pool = conn_pool_arc.clone();
let dd = setup_database(pool);
println!("{:?}", dd);
dd.ok().unwrap();
}
let (action_sender, action_receiver): (Sender<Action>, Receiver<Action>) = mpsc::channel();
let (start_animation_sender, start_animation_receiver): (
Sender<Animation>,
Receiver<Animation>,
) = mpsc::channel();
let (stop_animation_sender, stop_animation_receiver): (Sender<String>, Receiver<String>) =
mpsc::channel();
let pool_clone = conn_pool_arc.clone();
let ligths_controll = std::thread::spawn(move || {
ligth_controll(
pool_clone,
action_receiver,
start_animation_receiver,
stop_animation_receiver,
);
});
let cfg = rocket::Config::figment()
.merge(("port", 3000))
.merge(("address", "0.0.0.0"));
let _rocket = rocket::custom(cfg)
.mount(
"/",
routes![
index,
quit,
reload,
configure::configure,
animations::animation,
animations::start_animation,
animations::stop_animation,
animations::clear,
animations::delete
],
)
.manage(conn_pool_arc.clone())
.manage(Arc::new(Mutex::new(action_sender)))
.manage(Arc::new(Mutex::new(start_animation_sender)))
.manage(Arc::new(Mutex::new(stop_animation_sender)))
.launch()
.await?;
ligths_controll.join().unwrap();
Ok(())
}

290
src/render.rs Normal file
View File

@ -0,0 +1,290 @@
use ws281x_rpi::Ws2812Rpi;
use crate::{DBPool, animations::Animation};
use std::error::Error;
use smart_leds::{RGB8, SmartLedsWrite};
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,
ws: Option<Ws2812Rpi>,
last: Vec<RGB8>,
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,
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<RGB8>, settings: &Vec<LigthSetting>) {
fn load_led_into_light (data: &mut Vec<RGB8>, color: RGB8, start: usize, end: usize) {
for i in start..=end {
data[i] = color;
}
}
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![RGB8::default(); self.num_leds.try_into().unwrap()];
let time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs();
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()) {
let err = self.ws.as_mut().unwrap().write(data.clone().into_iter());
if err.is_err() || err.ok().is_none() {
println!("Failed to write data");
}
self.last = data;
}
}
}

11
src/utils.rs Normal file
View File

@ -0,0 +1,11 @@
use rocket::{serde::{Deserialize, Serialize}, Responder, http::{ContentType, Header}};
#[derive(Deserialize, Serialize)]
#[serde(crate="rocket::serde")]
pub struct ApiResponse {
pub code: u32,
pub message: String
}
#[derive(Responder)]
pub struct ApiResponder(String);