From 4bc1ef4ba242382f8bbf5d1d45c99ffc6e9fa4d2 Mon Sep 17 00:00:00 2001 From: Austen Adler Date: Thu, 12 Aug 2021 22:40:20 -0400 Subject: [PATCH] Many fixes related to error message passing --- src/color.rs | 7 ++-- src/errors.rs | 13 +++++++ src/main.rs | 72 ++++++++++++++++++++++++----------- src/pattern/moving_rainbow.rs | 7 ++-- src/pattern/orb.rs | 6 +-- src/strip.rs | 30 +++++++++++---- src/ui.rs | 57 ++++++++++++++++----------- src/webui.rs | 23 ++++++----- web/src/Form.svelte | 10 ++--- 9 files changed, 149 insertions(+), 76 deletions(-) diff --git a/src/color.rs b/src/color.rs index c79e6ce..b41c857 100644 --- a/src/color.rs +++ b/src/color.rs @@ -1,4 +1,3 @@ -use hex; use serde::Deserialize; use std::num::ParseIntError; use std::str::FromStr; @@ -87,9 +86,9 @@ pub fn merge_colors(from_color: Rgb, to_color: Rgb, factor: f32) -> Rgb { let (to_r, to_g, to_b) = to_color.to_float_tuple(); // TODO: Do not use as u8 - let r = ((to_r - from_r) * factor + from_r) as u8; - let g = ((to_g - from_g) * factor + from_g) as u8; - let b = ((to_b - from_b) * factor + from_b) as u8; + let r = (to_r - from_r).mul_add(factor, from_r) as u8; + let g = (to_g - from_g).mul_add(factor, from_g) as u8; + let b = (to_b - from_b).mul_add(factor, from_b) as u8; Rgb(r, g, b) } diff --git a/src/errors.rs b/src/errors.rs index c5b2915..c5834c8 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,11 +1,23 @@ use core::any::Any; use std::fmt; +use std::io; + +pub type ProgramResult = Result; + +#[derive(Debug)] +pub enum Message { + Error(ProgramError), + Terminated, + String(String), + InputPrompt(String), +} #[derive(Debug)] pub enum ProgramError { General(String), UiError(String), Boxed(Box), + IoError(io::Error), } impl From for ProgramError { @@ -32,6 +44,7 @@ impl fmt::Display for ProgramError { Self::General(s) => write!(f, "{}", s), Self::UiError(s) => write!(f, "Critial UI error: {}", s), Self::Boxed(s) => write!(f, "{:?}", s), + Self::IoError(e) => write!(f, "{:?}", e), } } } diff --git a/src/main.rs b/src/main.rs index c176338..5bd4dd5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,16 +32,22 @@ mod pattern; mod strip; mod ui; mod webui; -use errors::ProgramError; +use errors::{ProgramError, ProgramResult}; use std::io; -use std::sync::mpsc::channel; +use std::io::Write; +use std::sync::mpsc::{channel, Sender}; use std::thread; -use strip::{LedStrip, Message}; +use strip::LedStrip; use ui::console_ui_loop; -fn main() -> Result<(), ProgramError> { - let (tx, rx) = channel::(); - let strip_handle = thread::spawn(move || -> Result<(), ProgramError> { +fn main() -> ProgramResult<()> { + // Strip control transmitter and receiver + let (strip_tx, strip_rx) = channel::(); + let (console_strip_tx, webui_strip_tx) = (strip_tx.clone(), strip_tx); + + let (message_tx, message_rx) = channel::(); + + make_child(message_tx.clone(), move |message_tx| -> ProgramResult<()> { let mut strip = LedStrip::new(strip::Config { // I have 89 right now, but start off with 20 num_lights: 89, @@ -51,25 +57,47 @@ fn main() -> Result<(), ProgramError> { global_brightness_max: 255, tick_time_ms: strip::DEFAULT_TICK_TIME_MS, })?; - strip.strip_loop(&rx) + strip.strip_loop(message_tx, &strip_rx) }); - // I do not care if the console ui crashes - let (g, h) = (tx.clone(), tx.clone()); - let _console_ui_handle = thread::spawn(move || -> Result<(), ProgramError> { - let ret = console_ui_loop(&g); - println!("Console ui dead: {:?}", ret); - ret + make_child(message_tx.clone(), move |message_tx| -> ProgramResult<()> { + console_ui_loop(message_tx, &console_strip_tx) }); - // I do not care if the web ui crashes - let _web_ui_handle = thread::spawn(move || -> Result<(), io::Error> { - let ret = webui::start(h); - println!("Webui dead: {:?}", ret); - ret - // TODO: Do not join -- this is just because we are running on a computer with no spi env - }) - .join()?; + make_child(message_tx, move |message_tx| -> ProgramResult<()> { + webui::start(message_tx.clone(), webui_strip_tx).map_err(ProgramError::IoError) + }); - strip_handle.join()? + let mut input_prompt: Option = None; + loop { + match message_rx.recv() { + Ok(errors::Message::String(s)) => println!("\r{}", s), + Ok(errors::Message::Error(e)) => println!("\rError!! {:?}", e), + Ok(errors::Message::Terminated) => { + panic!("A thread terminated") + } + Ok(errors::Message::InputPrompt(i)) => input_prompt = Some(i), + Err(e) => { + return Err(ProgramError::General(format!( + "All transmitters hung up! {:?}", + e + ))) + } + } + if let Some(ref s) = input_prompt { + print!("{}: ", s); + // We do not care if we can't flush + let _ = io::stdout().flush(); + } + } +} + +fn make_child(message_tx: Sender, f: F) +where + F: FnOnce(&Sender) -> ProgramResult<()> + std::marker::Send, +{ + thread::spawn(move || match f(&message_tx) { + Ok(()) => message_tx.send(errors::Message::Terminated), + Err(e) => message_tx.send(errors::Message::Error(e)), + }); } diff --git a/src/pattern/moving_rainbow.rs b/src/pattern/moving_rainbow.rs index adfb023..f9b584b 100644 --- a/src/pattern/moving_rainbow.rs +++ b/src/pattern/moving_rainbow.rs @@ -30,7 +30,7 @@ impl Pattern for MovingRainbow { Ok(true) } fn init(&mut self, num_lights: u16) -> Result<(), ()> { - if num_lights < 1 || num_lights > 255 { + if !(1..=255).contains(&num_lights) { return Err(()); } if self.width < 1 { @@ -39,14 +39,15 @@ impl Pattern for MovingRainbow { // The length of the buffer // Always a factor of RAINBOW.len() * width - let length_factor = u16::try_from(RAINBOW.len()).or(Err(()))? * u16::from(self.width); + let length_factor = u16::try_from(RAINBOW.len()).or(Err(()))?.saturating_mul(u16::from(self.width)); let buf_length = num_lights .checked_sub(1) .ok_or(())? .div_euclid(length_factor) .checked_add(1) .ok_or(())? - * length_factor; + .saturating_mul(length_factor) + ; self.lights_buf = RAINBOW .iter() diff --git a/src/pattern/orb.rs b/src/pattern/orb.rs index 9bfc0e8..13304a3 100644 --- a/src/pattern/orb.rs +++ b/src/pattern/orb.rs @@ -32,7 +32,7 @@ impl Orb { color, center_width, backoff_width, - total_width: center_width + backoff_width * 2, + total_width: center_width.saturating_add(backoff_width.saturating_mul(2)), bounces: false, step: 0, step_max: 0, @@ -75,7 +75,7 @@ impl Pattern for Orb { self.lights_buf = iter::empty::() .chain(ramp.clone().into_iter()) .chain(iter::repeat(self.color).take(self.center_width.into())) - .chain(ramp.clone().into_iter().rev()) + .chain(ramp.into_iter().rev()) .chain( iter::repeat(other_color) .take(num_lights.saturating_sub(self.total_width.into()).into()), @@ -86,7 +86,7 @@ impl Pattern for Orb { .lights_buf .len() .checked_sub(self.total_width.into()) - .unwrap_or(self.lights_buf.len()); + .unwrap_or_else(|| self.lights_buf.len()); Ok(()) } diff --git a/src/strip.rs b/src/strip.rs index 6bcb505..f7a5b91 100644 --- a/src/strip.rs +++ b/src/strip.rs @@ -1,10 +1,11 @@ use crate::color; +use crate::errors; use crate::errors::ProgramError; use crate::pattern::{self, Pattern}; use std::cmp; use std::ops::Add; use std::process; -use std::sync::mpsc::Receiver; +use std::sync::mpsc::{Receiver, Sender}; use std::time::{Duration, Instant}; use ws2818_rgb_led_spi_driver::adapter_gen::Ws28xxAdapter; use ws2818_rgb_led_spi_driver::adapter_spi::Ws28xxSpiAdapter; @@ -99,7 +100,11 @@ impl LedStrip { } } - pub fn strip_loop(&mut self, rx: &Receiver) -> Result<(), ProgramError> { + pub fn strip_loop( + &mut self, + message_tx: &Sender, + rx: &Receiver, + ) -> Result<(), ProgramError> { let mut exit = false; loop { let target_time = Instant::now().add(Duration::from_millis(self.config.tick_time_ms)); @@ -111,7 +116,10 @@ impl LedStrip { if pat.init(self.config.num_lights).is_ok() { self.pattern = pat; } else { - println!("Clearing light strip: {:?}", pat); + let _ = message_tx.send(errors::Message::String(format!( + "Clearing light strip: {:?}", + pat + ))); } } Message::ChangePattern(pat) => { @@ -119,7 +127,10 @@ impl LedStrip { if pat.init(self.config.num_lights).is_ok() { self.pattern = pat; } else { - println!("Error with pattern: {:?}", pat); + let _ = message_tx.send(errors::Message::String(format!( + "Error with pattern: {:?}", + pat + ))); } } Message::SetNumLights(num_lights) => { @@ -127,7 +138,10 @@ impl LedStrip { } Message::SetTickTime(tick_time_ms) => { if tick_time_ms < MIN_TICK_TIME { - println!("Error with tick time: {}", tick_time_ms); + let _ = message_tx.send(errors::Message::String(format!( + "Error with tick time: {}", + tick_time_ms + ))); } self.config.tick_time_ms = tick_time_ms; } @@ -137,7 +151,9 @@ impl LedStrip { if pat.init(self.config.num_lights).is_ok() { self.pattern = Box::new(pat); } else { - println!("Could not construct clear pattern"); + let _ = message_tx.send(errors::Message::String( + String::from("Could not construct clear pattern") + )); } } } @@ -152,7 +168,7 @@ impl LedStrip { } if exit { - println!("Exiting as requested"); + let _ = message_tx.send(errors::Message::String(String::from("Exiting as requested"))); process::exit(0); } diff --git a/src/ui.rs b/src/ui.rs index 78e8375..76d07e4 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -1,21 +1,24 @@ use crate::color::Rgb; -use crate::errors::ProgramError; +use crate::errors::{self, ProgramError, ProgramResult}; use crate::pattern::{self, Pattern}; use crate::strip; use std::io; use std::io::Write; use std::sync::mpsc::Sender; -pub fn console_ui_loop(tx: &Sender) -> Result<(), ProgramError> { +pub fn console_ui_loop( + message_tx: &Sender, + strip_tx: &Sender, +) -> ProgramResult<()> { loop { - let line = get_line("Command (cfqs)")?; - if let Err(msg) = parse_cmd(tx, &line) { + let line = get_line(message_tx, "Command (cfqs)")?; + if let Err(msg) = parse_cmd(strip_tx, &line) { println!("Command error: {}", msg); } } } -fn parse_cmd(tx: &Sender, s: &str) -> Result<(), String> { +fn parse_cmd(strip_tx: &Sender, s: &str) -> Result<(), String> { match s .split(char::is_whitespace) .collect::>() @@ -30,14 +33,14 @@ fn parse_cmd(tx: &Sender, s: &str) -> Result<(), String> { b.parse::() .map_err(|_| String::from("Blue could not be parsed"))?, ); - change_pattern(tx, Box::new(pattern::Fade::new(color))) + change_pattern(strip_tx, Box::new(pattern::Fade::new(color))) } ["f", c] => { let color_value = c .parse::() .map_err(|_| String::from("Could not parse color"))?; let color = Rgb(color_value, color_value, color_value); - change_pattern(tx, Box::new(pattern::Fade::new(color))) + change_pattern(strip_tx, Box::new(pattern::Fade::new(color))) } ["m", r, g, b] => { let color = Rgb( @@ -48,49 +51,53 @@ fn parse_cmd(tx: &Sender, s: &str) -> Result<(), String> { b.parse::() .map_err(|_| String::from("Blue could not be parsed"))?, ); - change_pattern(tx, Box::new(pattern::MovingPixel::new(color))) + change_pattern(strip_tx, Box::new(pattern::MovingPixel::new(color))) } ["m", c] => { let color_value = c .parse::() .map_err(|_| String::from("Could not parse color"))?; let color = Rgb(color_value, color_value, color_value); - change_pattern(tx, Box::new(pattern::MovingPixel::new(color))) + change_pattern(strip_tx, Box::new(pattern::MovingPixel::new(color))) }, ["c", r, g, b] => { let color = parse_color(r, g, b)?; - change_pattern(tx, Box::new(pattern::Solid::new(color))) + change_pattern(strip_tx, Box::new(pattern::Solid::new(color))) } ["c", c] => { let color = parse_color(c, c, c)?; - change_pattern(tx, Box::new(pattern::Solid::new(color))) + change_pattern(strip_tx, Box::new(pattern::Solid::new(color))) }, - ["r"] =>change_pattern(tx, Box::new(pattern::MovingRainbow::new(4, true))), + ["r"] =>change_pattern(strip_tx, Box::new(pattern::MovingRainbow::new(4, true))), ["r", w] => { let width = w.parse::().map_err(|_| String::from("Width could not be parsed"))?; - change_pattern(tx, Box::new(pattern::MovingRainbow::new(width, true))) + change_pattern(strip_tx, Box::new(pattern::MovingRainbow::new(width, true))) }, ["r", w, f] => { let width = w.parse::().map_err(|_| String::from("Width could not be parsed"))?; - change_pattern(tx, Box::new(pattern::MovingRainbow::new(width, ["t", "T"].contains(f)))) + change_pattern(strip_tx, Box::new(pattern::MovingRainbow::new(width, ["t", "T"].contains(f)))) }, ["b", r1, g1, b1, r2, g2, b2, r3, g3, b3] => { let left = parse_color(r1, g1, b1)?; let right = parse_color(r2, g2, b2)?; let combined = parse_color(r3, g3, b3)?; - change_pattern(tx, Box::new(pattern::Collide::new(left, right, combined))) + change_pattern(strip_tx, Box::new(pattern::Collide::new(left, right, combined))) } - ["x"] => tx + ["x"] => strip_tx .send(strip::Message::ClearLights) .map_err(|e| e.to_string()), - ["q"] => tx.send(strip::Message::Quit).map_err(|e| e.to_string()), - ["s", n] => tx + ["q"] => { + strip_tx.send(strip::Message::Quit).map_err(|e| e.to_string())?; + // TODO + panic!("i"); + }, + ["s", n] => strip_tx .send(strip::Message::SetNumLights( n.parse::() .map_err(|_| String::from("Could not parse light count"))?, )) .map_err(|e| e.to_string()), - ["t", n] => tx.send(strip::Message::SetTickTime( + ["t", n] => strip_tx.send(strip::Message::SetTickTime( n.parse::() .map_err(|_| String::from("Could not parse light count"))?, )) @@ -110,13 +117,17 @@ fn parse_color(r: &str, g: &str, b: &str) -> Result { )) } -fn change_pattern(tx: &Sender, pat: Box) -> Result<(), String> { - tx.send(strip::Message::ChangePattern(pat)) +fn change_pattern( + strip_tx: &Sender, + pat: Box, +) -> Result<(), String> { + strip_tx + .send(strip::Message::ChangePattern(pat)) .map_err(|e| e.to_string()) } -fn get_line(prompt: &str) -> Result { - print!("{}: ", prompt); +fn get_line(message_tx: &Sender, prompt: &str) -> ProgramResult { + let _ = message_tx.send(errors::Message::InputPrompt(String::from(prompt))); std::io::stdout() .flush() .map_err(|_| ProgramError::UiError(String::from("Could not flush stdout")))?; diff --git a/src/webui.rs b/src/webui.rs index c0f5713..471410e 100644 --- a/src/webui.rs +++ b/src/webui.rs @@ -1,9 +1,10 @@ +use crate::errors; use crate::pattern; use crate::strip; use actix_web::error::JsonPayloadError; use actix_web::web::JsonConfig; use actix_web::{post, web, App, HttpServer, Responder, Result}; -use actix_web_static_files; +use actix_web_static_files::ResourceFiles; use std::io; use std::sync::{Arc, Mutex}; @@ -23,38 +24,42 @@ async fn set_color( println!("Got params: {:?}", params); data.strip_tx .lock() - .or(Err(io::Error::new( + .map_err(|_| io::Error::new( io::ErrorKind::Other, "Failed to get a lock", - )))? + ))? .send(strip::Message::ChangePattern(params.0.to_pattern())) - .or(Err(io::Error::new( + .map_err(|_| io::Error::new( io::ErrorKind::Other, "Failed to send to channel", - )))?; + ))?; Ok(format!("{:?}", params)) } #[actix_web::main] -pub async fn start(tx: Sender) -> std::io::Result<()> { - println!("Starting webui"); +pub async fn start( + message_tx: Sender, + strip_tx: Sender, +) -> std::io::Result<()> { + let _ = message_tx.send(errors::Message::String(String::from("Starting webui"))); HttpServer::new(move || { let generated = generate(); App::new() .data(AppState { - strip_tx: Arc::new(Mutex::new(tx.clone())), + strip_tx: Arc::new(Mutex::new(strip_tx.clone())), }) .service( web::scope("/api") .app_data( JsonConfig::default().error_handler(|err: JsonPayloadError, _req| { + // let _ = message_tx.send(errors::Message::String(format!("JSON error: {:?}", err))); println!("JSON error: {:?}", err); err.into() }), ) .service(set_color), ) - .service(actix_web_static_files::ResourceFiles::new("/", generated)) + .service(ResourceFiles::new("/", generated)) }) .bind(("0.0.0.0", 8080))? .run() diff --git a/web/src/Form.svelte b/web/src/Form.svelte index 5544f86..64628bb 100644 --- a/web/src/Form.svelte +++ b/web/src/Form.svelte @@ -62,17 +62,17 @@ const response = fetch(url, { method: 'POST', // *GET, POST, PUT, DELETE, etc. // mode: 'no-cors', // no-cors, *cors, same-origin - //cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached - //credentials: 'same-origin', // include, *same-origin, omit + // cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached + // credentials: 'same-origin', // include, *same-origin, omit headers: { 'Content-Type': 'application/json' // 'Content-Type': 'application/x-www-form-urlencoded', }, - //redirect: 'follow', // manual, *follow, error - //referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url + // redirect: 'follow', // manual, *follow, error + // referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url body: JSON.stringify(getFormData()) // body data type must match "Content-Type" header }).then(r => console.log(response)); - //return response.json(); // parses JSON response into native JavaScript objects + // return response.json(); // parses JSON response into native JavaScript objects }