Many fixes related to error message passing

This commit is contained in:
Austen Adler 2021-08-12 22:40:20 -04:00
parent e2952cb614
commit 4bc1ef4ba2
9 changed files with 149 additions and 76 deletions

View File

@ -1,4 +1,3 @@
use hex;
use serde::Deserialize; use serde::Deserialize;
use std::num::ParseIntError; use std::num::ParseIntError;
use std::str::FromStr; 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(); let (to_r, to_g, to_b) = to_color.to_float_tuple();
// TODO: Do not use as u8 // TODO: Do not use as u8
let r = ((to_r - from_r) * factor + from_r) as u8; let r = (to_r - from_r).mul_add(factor, from_r) as u8;
let g = ((to_g - from_g) * factor + from_g) as u8; let g = (to_g - from_g).mul_add(factor, from_g) as u8;
let b = ((to_b - from_b) * factor + from_b) as u8; let b = (to_b - from_b).mul_add(factor, from_b) as u8;
Rgb(r, g, b) Rgb(r, g, b)
} }

View File

@ -1,11 +1,23 @@
use core::any::Any; use core::any::Any;
use std::fmt; use std::fmt;
use std::io;
pub type ProgramResult<T> = Result<T, ProgramError>;
#[derive(Debug)]
pub enum Message {
Error(ProgramError),
Terminated,
String(String),
InputPrompt(String),
}
#[derive(Debug)] #[derive(Debug)]
pub enum ProgramError { pub enum ProgramError {
General(String), General(String),
UiError(String), UiError(String),
Boxed(Box<dyn Any + Send>), Boxed(Box<dyn Any + Send>),
IoError(io::Error),
} }
impl From<String> for ProgramError { impl From<String> for ProgramError {
@ -32,6 +44,7 @@ impl fmt::Display for ProgramError {
Self::General(s) => write!(f, "{}", s), Self::General(s) => write!(f, "{}", s),
Self::UiError(s) => write!(f, "Critial UI error: {}", s), Self::UiError(s) => write!(f, "Critial UI error: {}", s),
Self::Boxed(s) => write!(f, "{:?}", s), Self::Boxed(s) => write!(f, "{:?}", s),
Self::IoError(e) => write!(f, "{:?}", e),
} }
} }
} }

View File

@ -32,16 +32,22 @@ mod pattern;
mod strip; mod strip;
mod ui; mod ui;
mod webui; mod webui;
use errors::ProgramError; use errors::{ProgramError, ProgramResult};
use std::io; use std::io;
use std::sync::mpsc::channel; use std::io::Write;
use std::sync::mpsc::{channel, Sender};
use std::thread; use std::thread;
use strip::{LedStrip, Message}; use strip::LedStrip;
use ui::console_ui_loop; use ui::console_ui_loop;
fn main() -> Result<(), ProgramError> { fn main() -> ProgramResult<()> {
let (tx, rx) = channel::<Message>(); // Strip control transmitter and receiver
let strip_handle = thread::spawn(move || -> Result<(), ProgramError> { let (strip_tx, strip_rx) = channel::<strip::Message>();
let (console_strip_tx, webui_strip_tx) = (strip_tx.clone(), strip_tx);
let (message_tx, message_rx) = channel::<errors::Message>();
make_child(message_tx.clone(), move |message_tx| -> ProgramResult<()> {
let mut strip = LedStrip::new(strip::Config { let mut strip = LedStrip::new(strip::Config {
// I have 89 right now, but start off with 20 // I have 89 right now, but start off with 20
num_lights: 89, num_lights: 89,
@ -51,25 +57,47 @@ fn main() -> Result<(), ProgramError> {
global_brightness_max: 255, global_brightness_max: 255,
tick_time_ms: strip::DEFAULT_TICK_TIME_MS, 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 make_child(message_tx.clone(), move |message_tx| -> ProgramResult<()> {
let (g, h) = (tx.clone(), tx.clone()); console_ui_loop(message_tx, &console_strip_tx)
let _console_ui_handle = thread::spawn(move || -> Result<(), ProgramError> {
let ret = console_ui_loop(&g);
println!("Console ui dead: {:?}", ret);
ret
}); });
// I do not care if the web ui crashes make_child(message_tx, move |message_tx| -> ProgramResult<()> {
let _web_ui_handle = thread::spawn(move || -> Result<(), io::Error> { webui::start(message_tx.clone(), webui_strip_tx).map_err(ProgramError::IoError)
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()?;
strip_handle.join()? let mut input_prompt: Option<String> = 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<F: 'static>(message_tx: Sender<errors::Message>, f: F)
where
F: FnOnce(&Sender<errors::Message>) -> 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)),
});
} }

View File

@ -30,7 +30,7 @@ impl Pattern for MovingRainbow {
Ok(true) Ok(true)
} }
fn init(&mut self, num_lights: u16) -> Result<(), ()> { fn init(&mut self, num_lights: u16) -> Result<(), ()> {
if num_lights < 1 || num_lights > 255 { if !(1..=255).contains(&num_lights) {
return Err(()); return Err(());
} }
if self.width < 1 { if self.width < 1 {
@ -39,14 +39,15 @@ impl Pattern for MovingRainbow {
// The length of the buffer // The length of the buffer
// Always a factor of RAINBOW.len() * width // 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 let buf_length = num_lights
.checked_sub(1) .checked_sub(1)
.ok_or(())? .ok_or(())?
.div_euclid(length_factor) .div_euclid(length_factor)
.checked_add(1) .checked_add(1)
.ok_or(())? .ok_or(())?
* length_factor; .saturating_mul(length_factor)
;
self.lights_buf = RAINBOW self.lights_buf = RAINBOW
.iter() .iter()

View File

@ -32,7 +32,7 @@ impl Orb {
color, color,
center_width, center_width,
backoff_width, backoff_width,
total_width: center_width + backoff_width * 2, total_width: center_width.saturating_add(backoff_width.saturating_mul(2)),
bounces: false, bounces: false,
step: 0, step: 0,
step_max: 0, step_max: 0,
@ -75,7 +75,7 @@ impl Pattern for Orb {
self.lights_buf = iter::empty::<Rgb>() self.lights_buf = iter::empty::<Rgb>()
.chain(ramp.clone().into_iter()) .chain(ramp.clone().into_iter())
.chain(iter::repeat(self.color).take(self.center_width.into())) .chain(iter::repeat(self.color).take(self.center_width.into()))
.chain(ramp.clone().into_iter().rev()) .chain(ramp.into_iter().rev())
.chain( .chain(
iter::repeat(other_color) iter::repeat(other_color)
.take(num_lights.saturating_sub(self.total_width.into()).into()), .take(num_lights.saturating_sub(self.total_width.into()).into()),
@ -86,7 +86,7 @@ impl Pattern for Orb {
.lights_buf .lights_buf
.len() .len()
.checked_sub(self.total_width.into()) .checked_sub(self.total_width.into())
.unwrap_or(self.lights_buf.len()); .unwrap_or_else(|| self.lights_buf.len());
Ok(()) Ok(())
} }

View File

@ -1,10 +1,11 @@
use crate::color; use crate::color;
use crate::errors;
use crate::errors::ProgramError; use crate::errors::ProgramError;
use crate::pattern::{self, Pattern}; use crate::pattern::{self, Pattern};
use std::cmp; use std::cmp;
use std::ops::Add; use std::ops::Add;
use std::process; use std::process;
use std::sync::mpsc::Receiver; use std::sync::mpsc::{Receiver, Sender};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use ws2818_rgb_led_spi_driver::adapter_gen::Ws28xxAdapter; use ws2818_rgb_led_spi_driver::adapter_gen::Ws28xxAdapter;
use ws2818_rgb_led_spi_driver::adapter_spi::Ws28xxSpiAdapter; use ws2818_rgb_led_spi_driver::adapter_spi::Ws28xxSpiAdapter;
@ -99,7 +100,11 @@ impl LedStrip {
} }
} }
pub fn strip_loop(&mut self, rx: &Receiver<Message>) -> Result<(), ProgramError> { pub fn strip_loop(
&mut self,
message_tx: &Sender<errors::Message>,
rx: &Receiver<Message>,
) -> Result<(), ProgramError> {
let mut exit = false; let mut exit = false;
loop { loop {
let target_time = Instant::now().add(Duration::from_millis(self.config.tick_time_ms)); 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() { if pat.init(self.config.num_lights).is_ok() {
self.pattern = pat; self.pattern = pat;
} else { } else {
println!("Clearing light strip: {:?}", pat); let _ = message_tx.send(errors::Message::String(format!(
"Clearing light strip: {:?}",
pat
)));
} }
} }
Message::ChangePattern(pat) => { Message::ChangePattern(pat) => {
@ -119,7 +127,10 @@ impl LedStrip {
if pat.init(self.config.num_lights).is_ok() { if pat.init(self.config.num_lights).is_ok() {
self.pattern = pat; self.pattern = pat;
} else { } else {
println!("Error with pattern: {:?}", pat); let _ = message_tx.send(errors::Message::String(format!(
"Error with pattern: {:?}",
pat
)));
} }
} }
Message::SetNumLights(num_lights) => { Message::SetNumLights(num_lights) => {
@ -127,7 +138,10 @@ impl LedStrip {
} }
Message::SetTickTime(tick_time_ms) => { Message::SetTickTime(tick_time_ms) => {
if tick_time_ms < MIN_TICK_TIME { 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; self.config.tick_time_ms = tick_time_ms;
} }
@ -137,7 +151,9 @@ impl LedStrip {
if pat.init(self.config.num_lights).is_ok() { if pat.init(self.config.num_lights).is_ok() {
self.pattern = Box::new(pat); self.pattern = Box::new(pat);
} else { } 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 { if exit {
println!("Exiting as requested"); let _ = message_tx.send(errors::Message::String(String::from("Exiting as requested")));
process::exit(0); process::exit(0);
} }

View File

@ -1,21 +1,24 @@
use crate::color::Rgb; use crate::color::Rgb;
use crate::errors::ProgramError; use crate::errors::{self, ProgramError, ProgramResult};
use crate::pattern::{self, Pattern}; use crate::pattern::{self, Pattern};
use crate::strip; use crate::strip;
use std::io; use std::io;
use std::io::Write; use std::io::Write;
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
pub fn console_ui_loop(tx: &Sender<strip::Message>) -> Result<(), ProgramError> { pub fn console_ui_loop(
message_tx: &Sender<errors::Message>,
strip_tx: &Sender<strip::Message>,
) -> ProgramResult<()> {
loop { loop {
let line = get_line("Command (cfqs)")?; let line = get_line(message_tx, "Command (cfqs)")?;
if let Err(msg) = parse_cmd(tx, &line) { if let Err(msg) = parse_cmd(strip_tx, &line) {
println!("Command error: {}", msg); println!("Command error: {}", msg);
} }
} }
} }
fn parse_cmd(tx: &Sender<strip::Message>, s: &str) -> Result<(), String> { fn parse_cmd(strip_tx: &Sender<strip::Message>, s: &str) -> Result<(), String> {
match s match s
.split(char::is_whitespace) .split(char::is_whitespace)
.collect::<Vec<&str>>() .collect::<Vec<&str>>()
@ -30,14 +33,14 @@ fn parse_cmd(tx: &Sender<strip::Message>, s: &str) -> Result<(), String> {
b.parse::<u8>() b.parse::<u8>()
.map_err(|_| String::from("Blue could not be parsed"))?, .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] => { ["f", c] => {
let color_value = c let color_value = c
.parse::<u8>() .parse::<u8>()
.map_err(|_| String::from("Could not parse color"))?; .map_err(|_| String::from("Could not parse color"))?;
let color = Rgb(color_value, color_value, color_value); 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] => { ["m", r, g, b] => {
let color = Rgb( let color = Rgb(
@ -48,49 +51,53 @@ fn parse_cmd(tx: &Sender<strip::Message>, s: &str) -> Result<(), String> {
b.parse::<u8>() b.parse::<u8>()
.map_err(|_| String::from("Blue could not be parsed"))?, .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] => { ["m", c] => {
let color_value = c let color_value = c
.parse::<u8>() .parse::<u8>()
.map_err(|_| String::from("Could not parse color"))?; .map_err(|_| String::from("Could not parse color"))?;
let color = Rgb(color_value, color_value, color_value); 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] => { ["c", r, g, b] => {
let color = parse_color(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] => { ["c", c] => {
let color = parse_color(c, 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] => { ["r", w] => {
let width = w.parse::<u8>().map_err(|_| String::from("Width could not be parsed"))?; let width = w.parse::<u8>().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] => { ["r", w, f] => {
let width = w.parse::<u8>().map_err(|_| String::from("Width could not be parsed"))?; let width = w.parse::<u8>().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] => { ["b", r1, g1, b1, r2, g2, b2, r3, g3, b3] => {
let left = parse_color(r1, g1, b1)?; let left = parse_color(r1, g1, b1)?;
let right = parse_color(r2, g2, b2)?; let right = parse_color(r2, g2, b2)?;
let combined = parse_color(r3, g3, b3)?; 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) .send(strip::Message::ClearLights)
.map_err(|e| e.to_string()), .map_err(|e| e.to_string()),
["q"] => tx.send(strip::Message::Quit).map_err(|e| e.to_string()), ["q"] => {
["s", n] => tx strip_tx.send(strip::Message::Quit).map_err(|e| e.to_string())?;
// TODO
panic!("i");
},
["s", n] => strip_tx
.send(strip::Message::SetNumLights( .send(strip::Message::SetNumLights(
n.parse::<u16>() n.parse::<u16>()
.map_err(|_| String::from("Could not parse light count"))?, .map_err(|_| String::from("Could not parse light count"))?,
)) ))
.map_err(|e| e.to_string()), .map_err(|e| e.to_string()),
["t", n] => tx.send(strip::Message::SetTickTime( ["t", n] => strip_tx.send(strip::Message::SetTickTime(
n.parse::<u64>() n.parse::<u64>()
.map_err(|_| String::from("Could not parse light count"))?, .map_err(|_| String::from("Could not parse light count"))?,
)) ))
@ -110,13 +117,17 @@ fn parse_color(r: &str, g: &str, b: &str) -> Result<Rgb, String> {
)) ))
} }
fn change_pattern(tx: &Sender<strip::Message>, pat: Box<dyn Pattern + Send>) -> Result<(), String> { fn change_pattern(
tx.send(strip::Message::ChangePattern(pat)) strip_tx: &Sender<strip::Message>,
pat: Box<dyn Pattern + Send>,
) -> Result<(), String> {
strip_tx
.send(strip::Message::ChangePattern(pat))
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
} }
fn get_line(prompt: &str) -> Result<String, ProgramError> { fn get_line(message_tx: &Sender<errors::Message>, prompt: &str) -> ProgramResult<String> {
print!("{}: ", prompt); let _ = message_tx.send(errors::Message::InputPrompt(String::from(prompt)));
std::io::stdout() std::io::stdout()
.flush() .flush()
.map_err(|_| ProgramError::UiError(String::from("Could not flush stdout")))?; .map_err(|_| ProgramError::UiError(String::from("Could not flush stdout")))?;

View File

@ -1,9 +1,10 @@
use crate::errors;
use crate::pattern; use crate::pattern;
use crate::strip; use crate::strip;
use actix_web::error::JsonPayloadError; use actix_web::error::JsonPayloadError;
use actix_web::web::JsonConfig; use actix_web::web::JsonConfig;
use actix_web::{post, web, App, HttpServer, Responder, Result}; 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::io;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -23,38 +24,42 @@ async fn set_color(
println!("Got params: {:?}", params); println!("Got params: {:?}", params);
data.strip_tx data.strip_tx
.lock() .lock()
.or(Err(io::Error::new( .map_err(|_| io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
"Failed to get a lock", "Failed to get a lock",
)))? ))?
.send(strip::Message::ChangePattern(params.0.to_pattern())) .send(strip::Message::ChangePattern(params.0.to_pattern()))
.or(Err(io::Error::new( .map_err(|_| io::Error::new(
io::ErrorKind::Other, io::ErrorKind::Other,
"Failed to send to channel", "Failed to send to channel",
)))?; ))?;
Ok(format!("{:?}", params)) Ok(format!("{:?}", params))
} }
#[actix_web::main] #[actix_web::main]
pub async fn start(tx: Sender<strip::Message>) -> std::io::Result<()> { pub async fn start(
println!("Starting webui"); message_tx: Sender<errors::Message>,
strip_tx: Sender<strip::Message>,
) -> std::io::Result<()> {
let _ = message_tx.send(errors::Message::String(String::from("Starting webui")));
HttpServer::new(move || { HttpServer::new(move || {
let generated = generate(); let generated = generate();
App::new() App::new()
.data(AppState { .data(AppState {
strip_tx: Arc::new(Mutex::new(tx.clone())), strip_tx: Arc::new(Mutex::new(strip_tx.clone())),
}) })
.service( .service(
web::scope("/api") web::scope("/api")
.app_data( .app_data(
JsonConfig::default().error_handler(|err: JsonPayloadError, _req| { JsonConfig::default().error_handler(|err: JsonPayloadError, _req| {
// let _ = message_tx.send(errors::Message::String(format!("JSON error: {:?}", err)));
println!("JSON error: {:?}", err); println!("JSON error: {:?}", err);
err.into() err.into()
}), }),
) )
.service(set_color), .service(set_color),
) )
.service(actix_web_static_files::ResourceFiles::new("/", generated)) .service(ResourceFiles::new("/", generated))
}) })
.bind(("0.0.0.0", 8080))? .bind(("0.0.0.0", 8080))?
.run() .run()

View File

@ -62,17 +62,17 @@
const response = fetch(url, { const response = fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc. method: 'POST', // *GET, POST, PUT, DELETE, etc.
// mode: 'no-cors', // no-cors, *cors, same-origin // mode: 'no-cors', // no-cors, *cors, same-origin
//cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached // cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
//credentials: 'same-origin', // include, *same-origin, omit // credentials: 'same-origin', // include, *same-origin, omit
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
// 'Content-Type': 'application/x-www-form-urlencoded', // 'Content-Type': 'application/x-www-form-urlencoded',
}, },
//redirect: 'follow', // manual, *follow, error // 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 // 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 body: JSON.stringify(getFormData()) // body data type must match "Content-Type" header
}).then(r => console.log(response)); }).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
} }
</script> </script>