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.
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]]
name = "async-recursion"
version = "1.0.0"
@ -416,6 +425,7 @@ dependencies = [
"async-trait",
"clap",
"log",
"regex",
"serde",
"serde_json",
"simplelog",
@ -664,6 +674,23 @@ dependencies = [
"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]]
name = "remove_dir_all"
version = "0.5.3"

View file

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

View file

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

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::AsyncCallable;
@ -44,15 +44,14 @@ impl AsyncCallable for GetDisplayEndpoint {
);
match send_result {
Ok(_) => {
// TODO: don't poll for response
log::info!("waiting for display for item #{}", index);
match super::async_utils::channel_recv(receiver).await {
Err(mpsc::TryRecvError::Disconnected) => {
Err(_) => {
let msg = format!("Failed to response for get_display for #{}", index);
log::warn!("{}", msg);
return vec![ApiDisplayResult::failure(msg, "receiving channel disconnected").to_primitive()];
},
Err(_) => return vec![], // impossible
//Err(_) => return vec![], // impossible
Ok(x) => {
log::debug!("got display for item #{}", index);
return vec![ApiDisplayResult::success(x).to_primitive()];

View file

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

View file

@ -3,11 +3,18 @@ use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Clone)]
#[serde(tag = "action")]
pub enum ActionConfig {
#[serde(rename = "command")]
#[serde(rename = "command")]
Command(CommandAction),
#[serde(rename = "transform")]
Transform(super::TransformAction),
#[serde(rename = "mirror")]
Mirror(MirrorAction),
}
#[derive(Serialize, Deserialize, Clone)]
pub struct CommandAction {
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);
match serde_json::from_reader(reader) {
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]

View file

@ -7,9 +7,10 @@ mod reading;
mod result_display;
mod slider;
mod toggle;
mod transformer;
pub use about::AboutConfig;
pub use action::{ActionConfig, CommandAction};
pub use action::{ActionConfig, CommandAction, MirrorAction};
pub use base::BaseConfig;
pub use button::ButtonConfig;
pub use element::ElementConfig;
@ -17,6 +18,7 @@ pub use reading::ReadingConfig;
pub use result_display::ResultDisplayConfig;
pub use slider::SliderConfig;
pub use toggle::ToggleConfig;
pub use transformer::{TransformAction, TransformTypeAction, ReplaceTransformAction, ExpandTransformAction, LogTransformAction, LogLevel, PatternConfig};
#[cfg(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;
/// Something capable of performing an action.
pub trait Act: Sized {
pub trait Act<'a>: Sized + 'a {
type Param;
type Config: ?Sized;
type Config: ?Sized + 'a;
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;
}
@ -19,12 +19,12 @@ pub struct Actor {
index: usize,
}
impl Act for Actor {
impl<'a> Act<'a> for Actor {
type Param = (usize, Primitive);
type Config = ElementConfig;
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 {
ElementConfig::Button(b) => ActorType::build(&b.on_click, parameter.1),
ElementConfig::Toggle(t) => ActorType::build(&t.on_toggle, parameter.1),
@ -48,23 +48,31 @@ impl Act for Actor {
pub enum ActorType {
Command(super::CommandActor),
Transform(super::TransformActor),
Mirror(Primitive),
}
impl Act for ActorType {
impl<'a> Act<'a> for ActorType {
type Param = Primitive;
type Config = ActionConfig;
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 {
ActionConfig::Command(c) =>
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 {
match self {
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 super::{Act, ActError};
const VALUE_ENV_VAR: &str = "KAYLON_VALUE";
/// Runs a CLI command in Bash
pub struct CommandActor {
shell: String,
@ -31,12 +29,12 @@ impl CommandActor {
}
}
impl Act for CommandActor {
impl<'a> Act<'a> for CommandActor {
type Param = Primitive;
type Config = CommandAction;
type Return = String;
fn build(config: &CommandAction, parameter: Primitive) -> Result<Self, ActError> {
fn build(config: &'a CommandAction, parameter: Primitive) -> Result<Self, ActError> {
Ok(
Self {
shell: "bash".to_owned(),
@ -49,14 +47,14 @@ impl Act for CommandActor {
fn run(self) -> Self::Return {
let output = Command::new(&self.shell)
.args(["-c", &self.run])
.env(VALUE_ENV_VAR, &self.variable)
.env(super::VALUE_VAR, &self.variable)
.output()
.expect(&format!("Cannot run `{}`", &self.run));
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()))
}
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
}
}

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 crate::config::ReadingConfig;
use super::{Act, ActError, ActorType, RouterCommand};
use super::{Act, ActError, ActorType};
use crate::runtime::RouterCommand;
/// Runs an action periodically
pub struct PeriodicActor {
@ -13,12 +14,12 @@ pub struct PeriodicActor {
index: usize,
}
impl Act for PeriodicActor {
impl<'a> Act<'a> for PeriodicActor {
type Param = (usize, Sender<RouterCommand>);
type Config = ReadingConfig;
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)?;
Ok(
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 command_actor;
mod actors;
mod communication;
mod executor;
mod periodic_actor;
mod primitive_utils;
mod result_router;
pub use actor::{Actor, Act, ActError, ActorType};
pub use command_actor::CommandActor;
pub use actors::*;
pub use communication::{QueueItem, QueueAction};
pub use executor::RuntimeExecutor;
pub use periodic_actor::PeriodicActor;
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 {
match primitive {
Primitive::Empty => "".to_owned(),
@ -65,7 +65,7 @@ pub fn display(primitive: Primitive) -> String {
Primitive::Bool(x) => x.to_string(),
Primitive::Json(x) => x,
}
}*/
}
#[inline]
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 Config = ();
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 mut cache_vec = Vec::with_capacity(parameter);
for _ in 0..parameter {

View file

@ -30,7 +30,7 @@
},
{
"element": "result-display",
"title": "",
"title": " ",
"result_of": 3
},
{
@ -46,7 +46,7 @@
},
{
"element": "result-display",
"title": "",
"title": " ",
"result_of": 5
},
{
@ -54,8 +54,41 @@
"title": "Fan Speed",
"period_ms": 1000,
"on_period": {
"action": "command",
"run": "cat /sys/class/hwmon/hwmon5/fan1_input"
"action": "transform",
"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 CErrorResult = {
element: string; // "error"
result: string; // "error"
message: string;
exception: string;
}
export type CValueResult = {
element: string; // "value"
result: string; // "value"
value: any;
}

View file

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