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 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)
}

View File

@ -1,11 +1,23 @@
use core::any::Any;
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)]
pub enum ProgramError {
General(String),
UiError(String),
Boxed(Box<dyn Any + Send>),
IoError(io::Error),
}
impl From<String> 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),
}
}
}

View File

@ -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::<Message>();
let strip_handle = thread::spawn(move || -> Result<(), ProgramError> {
fn main() -> ProgramResult<()> {
// Strip control transmitter and receiver
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 {
// 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<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)
}
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()

View File

@ -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::<Rgb>()
.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(())
}

View File

@ -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<Message>) -> Result<(), ProgramError> {
pub fn strip_loop(
&mut self,
message_tx: &Sender<errors::Message>,
rx: &Receiver<Message>,
) -> 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);
}

View File

@ -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<strip::Message>) -> Result<(), ProgramError> {
pub fn console_ui_loop(
message_tx: &Sender<errors::Message>,
strip_tx: &Sender<strip::Message>,
) -> 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<strip::Message>, s: &str) -> Result<(), String> {
fn parse_cmd(strip_tx: &Sender<strip::Message>, s: &str) -> Result<(), String> {
match s
.split(char::is_whitespace)
.collect::<Vec<&str>>()
@ -30,14 +33,14 @@ fn parse_cmd(tx: &Sender<strip::Message>, s: &str) -> Result<(), String> {
b.parse::<u8>()
.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::<u8>()
.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<strip::Message>, s: &str) -> Result<(), String> {
b.parse::<u8>()
.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::<u8>()
.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::<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] => {
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] => {
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::<u16>()
.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::<u64>()
.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> {
tx.send(strip::Message::ChangePattern(pat))
fn change_pattern(
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())
}
fn get_line(prompt: &str) -> Result<String, ProgramError> {
print!("{}: ", prompt);
fn get_line(message_tx: &Sender<errors::Message>, prompt: &str) -> ProgramResult<String> {
let _ = message_tx.send(errors::Message::InputPrompt(String::from(prompt)));
std::io::stdout()
.flush()
.map_err(|_| ProgramError::UiError(String::from("Could not flush stdout")))?;

View File

@ -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<strip::Message>) -> std::io::Result<()> {
println!("Starting webui");
pub async fn start(
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 || {
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()

View File

@ -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
}
</script>