Add new type of action and general improvements

This commit is contained in:
NGnius (Graham) 2022-09-14 20:39:52 -04:00
parent 79f730b9c6
commit 5d347a260c
20 changed files with 554 additions and 66 deletions

27
backend/Cargo.lock generated
View file

@ -2,6 +2,15 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "aho-corasick"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "async-recursion" name = "async-recursion"
version = "1.0.0" version = "1.0.0"
@ -416,6 +425,7 @@ dependencies = [
"async-trait", "async-trait",
"clap", "clap",
"log", "log",
"regex",
"serde", "serde",
"serde_json", "serde_json",
"simplelog", "simplelog",
@ -664,6 +674,23 @@ dependencies = [
"bitflags", "bitflags",
] ]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]] [[package]]
name = "remove_dir_all" name = "remove_dir_all"
version = "0.5.3" version = "0.5.3"

View file

@ -20,6 +20,8 @@ async-trait = "0.1.57"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
regex = { version = "1" }
# logging # logging
log = "0.4" log = "0.4"
simplelog = "0.12" simplelog = "0.12"

View file

@ -1,19 +1,9 @@
use std::sync::mpsc::{TryRecvError, Receiver}; use std::sync::mpsc::{RecvError, Receiver};
use std::sync::Mutex;
/// Receive on a blocking channel in an async manner (by polling conservatively) /// Receive on a blocking channel in an async manner
pub async fn channel_recv<T>(rx: Receiver<T>) -> Result<T, TryRecvError> { pub async fn channel_recv<T: Send + 'static>(rx: Receiver<T>) -> Result<T, RecvError> {
let sleep_duration = std::time::Duration::from_millis(10); tokio::task::spawn_blocking(move || rx.recv()).await.map_err(|e| {
let receiver = Mutex::new(rx); log::error!("Async JoinError while receiving from sync channel: {}", e);
loop { RecvError
let received = receiver.lock().unwrap().try_recv(); })?
match received {
Err(TryRecvError::Disconnected) => {
return Err(TryRecvError::Disconnected);
},
Err(_) => {},
Ok(x) => return Ok(x),
}
tokio::time::sleep(sleep_duration).await;
}
} }

View file

@ -1,4 +1,4 @@
use std::sync::{Mutex, mpsc::{Sender, channel, self}}; use std::sync::{Mutex, mpsc::{Sender, channel}};
use usdpl_back::core::serdes::Primitive; use usdpl_back::core::serdes::Primitive;
use usdpl_back::AsyncCallable; use usdpl_back::AsyncCallable;
@ -44,15 +44,14 @@ impl AsyncCallable for GetDisplayEndpoint {
); );
match send_result { match send_result {
Ok(_) => { Ok(_) => {
// TODO: don't poll for response
log::info!("waiting for display for item #{}", index); log::info!("waiting for display for item #{}", index);
match super::async_utils::channel_recv(receiver).await { match super::async_utils::channel_recv(receiver).await {
Err(mpsc::TryRecvError::Disconnected) => { Err(_) => {
let msg = format!("Failed to response for get_display for #{}", index); let msg = format!("Failed to response for get_display for #{}", index);
log::warn!("{}", msg); log::warn!("{}", msg);
return vec![ApiDisplayResult::failure(msg, "receiving channel disconnected").to_primitive()]; return vec![ApiDisplayResult::failure(msg, "receiving channel disconnected").to_primitive()];
}, },
Err(_) => return vec![], // impossible //Err(_) => return vec![], // impossible
Ok(x) => { Ok(x) => {
log::debug!("got display for item #{}", index); log::debug!("got display for item #{}", index);
return vec![ApiDisplayResult::success(x).to_primitive()]; return vec![ApiDisplayResult::success(x).to_primitive()];

View file

@ -3,7 +3,7 @@ use serde_json::Value;
use usdpl_back::core::serdes::Primitive; use usdpl_back::core::serdes::Primitive;
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
#[serde(tag = "element")] #[serde(tag = "result")]
pub enum ApiDisplayResult { pub enum ApiDisplayResult {
#[serde(rename = "value")] #[serde(rename = "value")]
Value(ApiValue), Value(ApiValue),

View file

@ -5,9 +5,16 @@ use serde::{Serialize, Deserialize};
pub enum ActionConfig { pub enum ActionConfig {
#[serde(rename = "command")] #[serde(rename = "command")]
Command(CommandAction), Command(CommandAction),
#[serde(rename = "transform")]
Transform(super::TransformAction),
#[serde(rename = "mirror")]
Mirror(MirrorAction),
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
pub struct CommandAction { pub struct CommandAction {
pub run: String, pub run: String,
} }
#[derive(Serialize, Deserialize, Clone)]
pub struct MirrorAction;

View file

@ -21,12 +21,18 @@ impl BaseConfig {
let reader = std::io::BufReader::new(file); let reader = std::io::BufReader::new(file);
match serde_json::from_reader(reader) { match serde_json::from_reader(reader) {
Ok(conf) => return conf, Ok(conf) => return conf,
Err(e) => log::error!("Failed to deserialize {}: {}", path.display(), e), Err(e) => {
log::error!("Failed to deserialize {}: {}", path.display(), e);
panic!("Failed to deserialize {}: {}", path.display(), e)
},
} }
}, },
Err(e) => log::error!("Failed to open {}: {}", path.display(), e), Err(e) => {
log::error!("Failed to open {}: {}", path.display(), e);
panic!("Failed to open {}: {}", path.display(), e);
}
} }
panic!("Cannot open {}", path.display()) //panic!("Cannot open {}", path.display())
} }
#[inline] #[inline]

View file

@ -7,9 +7,10 @@ mod reading;
mod result_display; mod result_display;
mod slider; mod slider;
mod toggle; mod toggle;
mod transformer;
pub use about::AboutConfig; pub use about::AboutConfig;
pub use action::{ActionConfig, CommandAction}; pub use action::{ActionConfig, CommandAction, MirrorAction};
pub use base::BaseConfig; pub use base::BaseConfig;
pub use button::ButtonConfig; pub use button::ButtonConfig;
pub use element::ElementConfig; pub use element::ElementConfig;
@ -17,6 +18,7 @@ pub use reading::ReadingConfig;
pub use result_display::ResultDisplayConfig; pub use result_display::ResultDisplayConfig;
pub use slider::SliderConfig; pub use slider::SliderConfig;
pub use toggle::ToggleConfig; pub use toggle::ToggleConfig;
pub use transformer::{TransformAction, TransformTypeAction, ReplaceTransformAction, ExpandTransformAction, LogTransformAction, LogLevel, PatternConfig};
#[cfg(test)] #[cfg(test)]
mod test { mod test {

View file

@ -0,0 +1,69 @@
//! Transformers, robots in disguise! (or value-based transformations)
use serde::{Serialize, Deserialize};
use super::ActionConfig;
#[derive(Serialize, Deserialize, Clone)]
pub struct TransformAction {
pub target: Box<ActionConfig>,
pub transformer: TransformTypeAction,
}
#[derive(Serialize, Deserialize, Clone)]
#[serde(tag = "rule")]
pub enum TransformTypeAction {
#[serde(rename = "pre-replace")]
PreReplace(ReplaceTransformAction),
#[serde(rename = "post-replace")]
PostReplace(ReplaceTransformAction),
#[serde(rename = "pre-expand")]
PreExpand(ExpandTransformAction),
#[serde(rename = "post-expand")]
PostExpand(ExpandTransformAction),
#[serde(rename = "log")]
Log(LogTransformAction),
}
#[derive(Serialize, Deserialize, Clone)]
pub struct ReplaceTransformAction {
/// Regex
pub patterns: Vec<PatternConfig>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct PatternConfig {
/// Regex
pub pattern: String,
/// Formatting info https://docs.rs/regex/latest/regex/struct.Regex.html#replacement-string-syntax
pub format: String,
// Regex case_insensitive flags
pub i: Option<bool>,
// Regex multi_line flags
pub m: Option<bool>,
// Regex dot_matches_new_line flags
pub s: Option<bool>,
// Regex swap_greed flags
#[serde(rename = "U")]
pub u: Option<bool>,
// Regex ignore_whitespace flags
pub x: Option<bool>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct ExpandTransformAction {
pub format: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct LogTransformAction {
pub level: LogLevel,
}
#[derive(Serialize, Deserialize, Clone)]
pub enum LogLevel {
DEBUG,
INFO,
WARN,
ERROR,
}

View file

@ -5,11 +5,11 @@ use crate::config::{ElementConfig, ActionConfig};
pub type ActError = String; pub type ActError = String;
/// Something capable of performing an action. /// Something capable of performing an action.
pub trait Act: Sized { pub trait Act<'a>: Sized + 'a {
type Param; type Param;
type Config: ?Sized; type Config: ?Sized + 'a;
type Return; type Return;
fn build(config: &Self::Config, parameter: Self::Param) -> Result<Self, ActError>; fn build(config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError>;
fn run(self) -> Self::Return; fn run(self) -> Self::Return;
} }
@ -19,12 +19,12 @@ pub struct Actor {
index: usize, index: usize,
} }
impl Act for Actor { impl<'a> Act<'a> for Actor {
type Param = (usize, Primitive); type Param = (usize, Primitive);
type Config = ElementConfig; type Config = ElementConfig;
type Return = Primitive; type Return = Primitive;
fn build(config: &ElementConfig, parameter: Self::Param) -> Result<Self, ActError> { fn build(config: &'a ElementConfig, parameter: Self::Param) -> Result<Self, ActError> {
let a_type = match config { let a_type = match config {
ElementConfig::Button(b) => ActorType::build(&b.on_click, parameter.1), ElementConfig::Button(b) => ActorType::build(&b.on_click, parameter.1),
ElementConfig::Toggle(t) => ActorType::build(&t.on_toggle, parameter.1), ElementConfig::Toggle(t) => ActorType::build(&t.on_toggle, parameter.1),
@ -48,23 +48,31 @@ impl Act for Actor {
pub enum ActorType { pub enum ActorType {
Command(super::CommandActor), Command(super::CommandActor),
Transform(super::TransformActor),
Mirror(Primitive),
} }
impl Act for ActorType { impl<'a> Act<'a> for ActorType {
type Param = Primitive; type Param = Primitive;
type Config = ActionConfig; type Config = ActionConfig;
type Return = Primitive; type Return = Primitive;
fn build(config: &Self::Config, parameter: Self::Param) -> Result<Self, ActError> { fn build(config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
Ok(match config { Ok(match config {
ActionConfig::Command(c) => ActionConfig::Command(c) =>
Self::Command(super::CommandActor::build(c, parameter)?), Self::Command(super::CommandActor::build(c, parameter)?),
ActionConfig::Transform(t) =>
Self::Transform(super::TransformActor::build(t, parameter)?),
ActionConfig::Mirror(_) =>
Self::Mirror(parameter)
}) })
} }
fn run(self) -> Self::Return { fn run(self) -> Self::Return {
match self { match self {
Self::Command(c) => c.run().into(), Self::Command(c) => c.run().into(),
Self::Transform(t) => t.run(),
Self::Mirror(p) => p,
} }
} }
} }

View file

@ -5,8 +5,6 @@ use usdpl_back::core::serdes::Primitive;
use crate::config::CommandAction; use crate::config::CommandAction;
use super::{Act, ActError}; use super::{Act, ActError};
const VALUE_ENV_VAR: &str = "KAYLON_VALUE";
/// Runs a CLI command in Bash /// Runs a CLI command in Bash
pub struct CommandActor { pub struct CommandActor {
shell: String, shell: String,
@ -31,12 +29,12 @@ impl CommandActor {
} }
} }
impl Act for CommandActor { impl<'a> Act<'a> for CommandActor {
type Param = Primitive; type Param = Primitive;
type Config = CommandAction; type Config = CommandAction;
type Return = String; type Return = String;
fn build(config: &CommandAction, parameter: Primitive) -> Result<Self, ActError> { fn build(config: &'a CommandAction, parameter: Primitive) -> Result<Self, ActError> {
Ok( Ok(
Self { Self {
shell: "bash".to_owned(), shell: "bash".to_owned(),
@ -49,14 +47,14 @@ impl Act for CommandActor {
fn run(self) -> Self::Return { fn run(self) -> Self::Return {
let output = Command::new(&self.shell) let output = Command::new(&self.shell)
.args(["-c", &self.run]) .args(["-c", &self.run])
.env(VALUE_ENV_VAR, &self.variable) .env(super::VALUE_VAR, &self.variable)
.output() .output()
.expect(&format!("Cannot run `{}`", &self.run)); .expect(&format!("Cannot run `{}`", &self.run));
if !output.stderr.is_empty() { if !output.stderr.is_empty() {
log::error!("Error running `{}`: {}", &self.run, String::from_utf8(output.stderr).unwrap_or_else(|_| "<non utf-8 stderr output>".to_owned())) log::error!("Error running `{}`: {}", &self.run, String::from_utf8(output.stderr).unwrap_or_else(|_| "<non utf-8 stderr output>".to_owned()))
} }
let result = String::from_utf8(output.stdout).expect(&format!("Cannot parse stdout from `{}` as UTF-8", self.run)); let result = String::from_utf8(output.stdout).expect(&format!("Cannot parse stdout from `{}` as UTF-8", self.run));
log::debug!("CommandActor ran `{}` (${}=\"{}\") -> `{}`", &self.run, VALUE_ENV_VAR, &self.variable, &result); log::debug!("CommandActor ran `{}` (${}=\"{}\") -> `{}`", &self.run, super::VALUE_VAR, &self.variable, &result);
result result
} }
} }

View file

@ -0,0 +1,11 @@
mod actor;
mod command_actor;
mod periodic_actor;
mod transform_actor;
pub use actor::{Actor, Act, ActError, ActorType};
pub use command_actor::CommandActor;
pub use periodic_actor::PeriodicActor;
pub use transform_actor::TransformActor;
pub const VALUE_VAR: &str = "KAYLON_VALUE";

View file

@ -4,7 +4,8 @@ use std::time::Duration;
use usdpl_back::core::serdes::Primitive; use usdpl_back::core::serdes::Primitive;
use crate::config::ReadingConfig; use crate::config::ReadingConfig;
use super::{Act, ActError, ActorType, RouterCommand}; use super::{Act, ActError, ActorType};
use crate::runtime::RouterCommand;
/// Runs an action periodically /// Runs an action periodically
pub struct PeriodicActor { pub struct PeriodicActor {
@ -13,12 +14,12 @@ pub struct PeriodicActor {
index: usize, index: usize,
} }
impl Act for PeriodicActor { impl<'a> Act<'a> for PeriodicActor {
type Param = (usize, Sender<RouterCommand>); type Param = (usize, Sender<RouterCommand>);
type Config = ReadingConfig; type Config = ReadingConfig;
type Return = (); type Return = ();
fn build(config: &Self::Config, parameter: Self::Param) -> Result<Self, ActError> { fn build(config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
ActorType::build(&config.on_period, Primitive::Empty)?; ActorType::build(&config.on_period, Primitive::Empty)?;
Ok( Ok(
Self { Self {

View file

@ -0,0 +1,320 @@
use regex::{Regex, RegexBuilder};
use usdpl_back::core::serdes::Primitive;
use crate::runtime::primitive_utils;
use crate::config::{TransformAction, ActionConfig, TransformTypeAction, ReplaceTransformAction, ExpandTransformAction, LogTransformAction, LogLevel, PatternConfig};
use super::{Act, ActError, ActorType};
/// Changes the output or input of an act
pub enum TransformActor {
PreReplace(PreReplaceTransformActor),
PostReplace(PostReplaceTransformActor),
PreExpand(PreExpandTransformActor),
PostExpand(PostExpandTransformActor),
Log(LogTransformActor),
}
impl<'a> Act<'a> for TransformActor {
type Param = Primitive;
type Config = TransformAction;
type Return = Primitive;
fn build(config: &'a TransformAction, parameter: Primitive) -> Result<Self, ActError> {
let result = Ok(match &config.transformer {
TransformTypeAction::PreReplace(x) =>
Self::PreReplace(PreReplaceTransformActor::build(&(x, &config.target), parameter)?),
TransformTypeAction::PostReplace(x) =>
Self::PostReplace(PostReplaceTransformActor::build(&(x, &config.target), parameter)?),
TransformTypeAction::PreExpand(x) =>
Self::PreExpand(PreExpandTransformActor::build(&(x, &config.target), parameter)?),
TransformTypeAction::PostExpand(x) =>
Self::PostExpand(PostExpandTransformActor::build(&(x, &config.target), parameter)?),
TransformTypeAction::Log(x) =>
Self::Log(LogTransformActor::build(&(x, &config.target), parameter)?),
});
result
}
fn run(self) -> Self::Return {
match self {
Self::PreReplace(x) => x.run(),
Self::PostReplace(x) => x.run(),
Self::PreExpand(x) => x.run(),
Self::PostExpand(x) => x.run(),
Self::Log(x) => x.run()
}
}
}
pub(super) struct TransformPostActor {
op_fn: Box<dyn (FnOnce(Primitive) -> Primitive) + Send>,
actor: Box<ActorType>,
}
impl<'a> Act<'a> for TransformPostActor {
type Param = (Primitive, Box<dyn (FnOnce(Primitive) -> Primitive) + Send>);
type Config = ActionConfig;
type Return = Primitive;
fn build(config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
Ok(
Self {
op_fn: parameter.1,
actor: Box::new(ActorType::build(config, parameter.0)?),
}
)
}
fn run(self) -> Self::Return {
(self.op_fn)(self.actor.run())
}
}
/// executes op_fn and ActionConfig::build in Actor::build()
/// this blocks the main execution thread,
/// but an Err() from ActionConfig::build will be be propogated correctly
pub(super) struct TransformEagerPreActor {
actor: Box<ActorType>,
}
impl<'a> Act<'a> for TransformEagerPreActor {
type Param = (Primitive, Box<dyn (FnOnce(Primitive) -> Primitive) + Send>);
type Config = ActionConfig;
type Return = Primitive;
fn build(config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
let primitive = (parameter.1)(parameter.0);
Ok(
Self {
actor: Box::new(ActorType::build(config, primitive)?),
}
)
}
fn run(self) -> Self::Return {
self.actor.run()
}
}
/// executes op_fn and ActionConfig::build in Actor.run()
/// this doesn't block the main execution thread,
/// but an Err() from ActionConfig::build will produce an empty result
pub(super) struct TransformLazyPreActor {
op_fn: Box<dyn (FnOnce(Primitive) -> Primitive) + Send>,
action: ActionConfig,
primitive: Primitive,
}
impl<'a> Act<'a> for TransformLazyPreActor {
type Param = (Primitive, Box<dyn (FnOnce(Primitive) -> Primitive) + Send>);
type Config = ActionConfig;
type Return = Primitive;
fn build(config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
Ok(
Self {
op_fn: parameter.1,
action: config.to_owned(),
primitive: parameter.0,
}
)
}
fn run(self) -> Self::Return {
let primitive = (self.op_fn)(self.primitive);
match ActorType::build(&self.action, primitive) {
Ok(action) => action.run(),
Err(e) => {
log::error!("Failed to lazily build action for pre-transformer: {}", e);
Primitive::Empty
}
}
}
}
struct PatternRule {
pattern: Regex,
format: String,
}
impl PatternRule {
#[inline]
fn from_config(config: &PatternConfig) -> Result<Self, ActError> {
let re = RegexBuilder::new(&config.pattern)
.case_insensitive(config.i.unwrap_or(false))
.multi_line(config.m.unwrap_or(false))
.dot_matches_new_line(config.s.unwrap_or(false))
.swap_greed(config.u.unwrap_or(false))
.ignore_whitespace(config.x.unwrap_or(false))
.build()
.map_err(|e| format!("Failed to compile regex `{}`: {}", config.pattern, e))?;
Ok(Self {
pattern: re,
format: config.format.clone(),
})
}
}
fn replace_fn(config: &ReplaceTransformAction) -> Result<impl FnOnce(Primitive) -> Primitive, ActError> {
let mut patterns: Vec<PatternRule> = Vec::with_capacity(config.patterns.len());
for pattern in config.patterns.iter() {
patterns.push(PatternRule::from_config(pattern)?);
}
Ok(move |p| {
let mut stringy = primitive_utils::display(p);
for pattern in patterns {
stringy = pattern.pattern.replace(&stringy, pattern.format).into_owned();
}
stringy.into()
})
}
pub struct PreReplaceTransformActor {
transformer: TransformEagerPreActor,
}
impl<'a> Act<'a> for PreReplaceTransformActor {
type Param = Primitive;
type Config = (&'a ReplaceTransformAction, &'a ActionConfig);
type Return = Primitive;
fn build(config: &'a Self::Config, parameter: Primitive) -> Result<Self, ActError> {
Ok(
Self {
transformer: TransformEagerPreActor::build(
config.1,
(parameter,
Box::new(replace_fn(config.0)?)
))?,
}
)
}
fn run(self) -> Self::Return {
self.transformer.run()
}
}
pub struct PostReplaceTransformActor {
transformer: TransformPostActor,
}
impl<'a> Act<'a> for PostReplaceTransformActor {
type Param = Primitive;
type Config = (&'a ReplaceTransformAction, &'a ActionConfig);
type Return = Primitive;
fn build(config: &'a Self::Config, parameter: Primitive) -> Result<Self, ActError> {
Ok(
Self {
transformer: TransformPostActor::build(
config.1,
(parameter,
Box::new(replace_fn(config.0)?)
))?,
}
)
}
fn run(self) -> Self::Return {
self.transformer.run()
}
}
fn expand_fn(config: &ExpandTransformAction) -> Result<impl FnOnce(Primitive) -> Primitive, ActError> {
let format = config.format.clone();
Ok(move |p| {
let stringy = primitive_utils::display(p);
let pattern1 = format!("${}", super::VALUE_VAR);
let pattern2 = format!("${{{}}}", super::VALUE_VAR);
format.replace(&pattern1, &pattern2).replace(&pattern2, &stringy).into()
})
}
pub struct PreExpandTransformActor {
transformer: TransformLazyPreActor,
}
impl<'a> Act<'a> for PreExpandTransformActor {
type Param = Primitive;
type Config = (&'a ExpandTransformAction, &'a ActionConfig);
type Return = Primitive;
fn build(config: &'a Self::Config, parameter: Primitive) -> Result<Self, ActError> {
Ok(
Self {
transformer: TransformLazyPreActor::build(
config.1,
(parameter,
Box::new(expand_fn(config.0)?)
))?,
}
)
}
fn run(self) -> Self::Return {
self.transformer.run()
}
}
pub struct PostExpandTransformActor {
transformer: TransformPostActor,
}
impl<'a> Act<'a> for PostExpandTransformActor {
type Param = Primitive;
type Config = (&'a ExpandTransformAction, &'a ActionConfig);
type Return = Primitive;
fn build(config: &'a Self::Config, parameter: Primitive) -> Result<Self, ActError> {
Ok(
Self {
transformer: TransformPostActor::build(
config.1,
(parameter,
Box::new(expand_fn(config.0)?)
))?,
}
)
}
fn run(self) -> Self::Return {
self.transformer.run()
}
}
pub struct LogTransformActor {
generic: TransformPostActor,
}
impl<'a> Act<'a> for LogTransformActor {
type Param = Primitive;
type Config = (&'a LogTransformAction, &'a ActionConfig);
type Return = Primitive;
fn build(config: &'a Self::Config, parameter: Primitive) -> Result<Self, ActError> {
Ok(
Self {
generic: TransformPostActor::build(
config.1,
(parameter,
match config.0.level {
LogLevel::DEBUG => Box::new(|p| {log::debug!("{}", primitive_utils::debug(&p));p}),
LogLevel::INFO => Box::new(|p| {log::info!("{}", primitive_utils::debug(&p));p}),
LogLevel::WARN => Box::new(|p| {log::warn!("{}", primitive_utils::debug(&p));p}),
LogLevel::ERROR => Box::new(|p| {log::error!("{}", primitive_utils::debug(&p));p}),
}
))?,
}
)
}
fn run(self) -> Self::Return {
self.generic.run()
}
}

View file

@ -1,14 +1,10 @@
mod actor; mod actors;
mod command_actor;
mod communication; mod communication;
mod executor; mod executor;
mod periodic_actor;
mod primitive_utils; mod primitive_utils;
mod result_router; mod result_router;
pub use actor::{Actor, Act, ActError, ActorType}; pub use actors::*;
pub use command_actor::CommandActor;
pub use communication::{QueueItem, QueueAction}; pub use communication::{QueueItem, QueueAction};
pub use executor::RuntimeExecutor; pub use executor::RuntimeExecutor;
pub use periodic_actor::PeriodicActor;
pub use result_router::{ResultRouter, RouterCommand}; pub use result_router::{ResultRouter, RouterCommand};

View file

@ -51,7 +51,7 @@ pub fn debug(primitive: &Primitive) -> String {
} }
} }
/*#[inline] #[inline]
pub fn display(primitive: Primitive) -> String { pub fn display(primitive: Primitive) -> String {
match primitive { match primitive {
Primitive::Empty => "".to_owned(), Primitive::Empty => "".to_owned(),
@ -65,7 +65,7 @@ pub fn display(primitive: Primitive) -> String {
Primitive::Bool(x) => x.to_string(), Primitive::Bool(x) => x.to_string(),
Primitive::Json(x) => x, Primitive::Json(x) => x,
} }
}*/ }
#[inline] #[inline]
pub fn clone(primitive: &Primitive) -> Primitive { pub fn clone(primitive: &Primitive) -> Primitive {

View file

@ -40,12 +40,12 @@ impl ResultRouter {
} }
} }
impl Act for ResultRouter { impl<'a> Act<'a> for ResultRouter {
type Param = usize; type Param = usize;
type Config = (); type Config = ();
type Return = Sender<RouterCommand>; type Return = Sender<RouterCommand>;
fn build(_config: &Self::Config, parameter: Self::Param) -> Result<Self, ActError> { fn build(_config: &'a Self::Config, parameter: Self::Param) -> Result<Self, ActError> {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
let mut cache_vec = Vec::with_capacity(parameter); let mut cache_vec = Vec::with_capacity(parameter);
for _ in 0..parameter { for _ in 0..parameter {

View file

@ -30,7 +30,7 @@
}, },
{ {
"element": "result-display", "element": "result-display",
"title": "", "title": " ",
"result_of": 3 "result_of": 3
}, },
{ {
@ -46,7 +46,7 @@
}, },
{ {
"element": "result-display", "element": "result-display",
"title": "", "title": " ",
"result_of": 5 "result_of": 5
}, },
{ {
@ -54,8 +54,41 @@
"title": "Fan Speed", "title": "Fan Speed",
"period_ms": 1000, "period_ms": 1000,
"on_period": { "on_period": {
"action": "command", "action": "transform",
"run": "cat /sys/class/hwmon/hwmon5/fan1_input" "target": {
"action": "command",
"run": "cat /sys/class/hwmon/hwmon5/fan1_input"
},
"transformer": {
"rule": "post-expand",
"format": "$KAYLON_VALUE RPM"
}
}
},
{
"element": "toggle",
"title": "Lighthouses",
"description": "Turn Valve Index Lighthouses on",
"on_toggle": {
"action": "transform",
"target": {
"action": "transform",
"target": {
"action": "command",
"run": "python3 ./bin/lighthouse_ctrl.py ${KAYLON_VALUE}"
},
"transformer": {
"rule": "post-replace",
"patterns": [{"pattern": ".*", "format": "Done", "s": true}]
}
},
"transformer": {
"rule": "pre-replace",
"patterns": [
{"pattern": "TRUE", "format": "on", "i": true},
{"pattern": "FALSE", "format": "off", "i": true}
]
}
} }
} }
], ],

View file

@ -67,13 +67,13 @@ export type CResultDisplay = {
export type CElement = CButton | CToggle | CSlider | CReading | CResultDisplay; export type CElement = CButton | CToggle | CSlider | CReading | CResultDisplay;
export type CErrorResult = { export type CErrorResult = {
element: string; // "error" result: string; // "error"
message: string; message: string;
exception: string; exception: string;
} }
export type CValueResult = { export type CValueResult = {
element: string; // "value" result: string; // "value"
value: any; value: any;
} }

View file

@ -32,10 +32,12 @@ let about: backend.CAbout | null = null;
let update = () => {}; let update = () => {};
let updateTasks: (() => void)[] = [];
function displayCallback(index: number) { function displayCallback(index: number) {
return (newVal: backend.CDisplayResponse) => { return (newVal: backend.CDisplayResponse) => {
if (newVal != null) { if (newVal != null) {
switch (newVal.element) { switch (newVal.result) {
case "value": case "value":
let val = newVal as backend.CValueResult; let val = newVal as backend.CValueResult;
console.log("KAYLON: Got display for " + index.toString(), val); console.log("KAYLON: Got display for " + index.toString(), val);
@ -52,11 +54,18 @@ function displayCallback(index: number) {
} else { } else {
console.warn("KAYLON: Ignoring null display result for " + index.toString()); console.warn("KAYLON: Ignoring null display result for " + index.toString());
} }
backend.resolve(backend.getDisplay(index), displayCallback(index)); updateTasks.push(() => backend.resolve(backend.getDisplay(index), displayCallback(index)));
update(); update();
} }
} }
function onGetElements() {
for (let i = 0; i < items.length; i++) {
console.log("KAYLON: req display for item #" + i.toString());
backend.resolve(backend.getDisplay(i), displayCallback(i));
}
}
// init USDPL WASM frontend // init USDPL WASM frontend
// this is required to interface with the backend // this is required to interface with the backend
(async () => { (async () => {
@ -68,11 +77,8 @@ function displayCallback(index: number) {
let result = await elements_promise; let result = await elements_promise;
console.log("KAYLON: got elements", result); console.log("KAYLON: got elements", result);
if (result != null) { if (result != null) {
items = await backend.getElements(); items = result;
for (let i = 0; i < items.length; i++) { onGetElements();
console.log("KAYLON: req display for item #" + i.toString());
backend.resolve(backend.getDisplay(i), displayCallback(i));
}
} else { } else {
console.warn("KAYLON: backend connection failed"); console.warn("KAYLON: backend connection failed");
} }
@ -90,6 +96,13 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
update(); update();
} }
// perform tasks (like updating display elements) only while rendering the plugin
let taskItem = updateTasks.pop();
while (taskItem != undefined) {
taskItem();
taskItem = updateTasks.pop();
}
return ( return (
<PanelSection> <PanelSection>
{items.map( {items.map(
@ -106,6 +119,12 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => {
(reload_items: backend.CElement[]) => { (reload_items: backend.CElement[]) => {
items = reload_items; items = reload_items;
console.log("KAYLON: got elements", reload_items); console.log("KAYLON: got elements", reload_items);
if (reload_items != null) {
items = reload_items;
onGetElements();
} else {
console.warn("KAYLON: backend connection failed");
}
update(); update();
}); });
backend.resolve(backend.getAbout(), backend.resolve(backend.getAbout(),