Improve scalability and documentation

This commit is contained in:
NGnius (Graham) 2022-09-12 21:08:50 -04:00
parent 569eab5880
commit 79f730b9c6
26 changed files with 465 additions and 162 deletions

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 NGnius
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

83
backend/Cargo.lock generated
View file

@ -88,6 +88,42 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "3.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd"
dependencies = [
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
"once_cell",
"textwrap",
]
[[package]]
name = "clap_derive"
version = "3.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.2" version = "0.2.2"
@ -264,6 +300,12 @@ dependencies = [
"http", "http",
] ]
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.1.19"
@ -372,6 +414,7 @@ name = "kaylon"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"clap",
"log", "log",
"serde", "serde",
"serde_json", "serde_json",
@ -484,6 +527,12 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "os_str_bytes"
version = "6.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@ -528,6 +577,30 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.40" version = "1.0.40"
@ -749,6 +822,12 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.31" version = "1.0.31"
@ -984,6 +1063,8 @@ dependencies = [
[[package]] [[package]]
name = "usdpl-back" name = "usdpl-back"
version = "0.7.0" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca96dac4ee471e9534940f99cb36f5212cbfaf4e7779eb3ba970d3c511d9583"
dependencies = [ dependencies = [
"async-recursion", "async-recursion",
"async-trait", "async-trait",
@ -997,6 +1078,8 @@ dependencies = [
[[package]] [[package]]
name = "usdpl-core" name = "usdpl-core"
version = "0.6.0" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "862153581fac266458521f49e5906a71c1eee1665cb4c7d71e9586bd34b45394"
dependencies = [ dependencies = [
"base64", "base64",
] ]

View file

@ -1,15 +1,19 @@
[package] [package]
name = "kaylon" # TODO replace with plugin name (also in build.sh) name = "kaylon"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
authors = ["NGnius <ngniusness@gmail.com>"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html description = "Better than the Borg"
license = "MIT"
repository = "https://github.com/NGnius/kaylon"
[dependencies] [dependencies]
usdpl-back = { version = "0.7.0", features = ["decky"], path = "../../usdpl-rs/usdpl-back" } usdpl-back = { version = "0.7.0", features = ["decky"]}
clap = { version = "3.2", features = ["derive", "std"], default-features = false }
# async # async
tokio = { version = "*", features = ["sync", "time"] } tokio = { version = "*", features = ["time"] }
async-trait = "0.1.57" async-trait = "0.1.57"
# json # json

View file

@ -4,6 +4,7 @@ use super::ApiParameterType;
use crate::runtime::{QueueAction, QueueItem}; use crate::runtime::{QueueAction, QueueItem};
/// API web method to retrieve AboutConfig from the back-end, as described in the config file
pub fn get_about(sender: Sender<QueueItem>) -> impl Fn(ApiParameterType) -> ApiParameterType { pub fn get_about(sender: Sender<QueueItem>) -> impl Fn(ApiParameterType) -> ApiParameterType {
let sender = Mutex::new(sender); let sender = Mutex::new(sender);
move |_| { move |_| {

View file

@ -0,0 +1,19 @@
use std::sync::mpsc::{TryRecvError, Receiver};
use std::sync::Mutex;
/// 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;
}
}

View file

@ -3,10 +3,12 @@ use std::sync::{Mutex, mpsc::{Sender, channel, self}};
use usdpl_back::core::serdes::Primitive; use usdpl_back::core::serdes::Primitive;
use usdpl_back::AsyncCallable; use usdpl_back::AsyncCallable;
use super::ApiParameterType; use super::{ApiParameterType, ApiDisplayResult};
use crate::runtime::{QueueAction, QueueItem}; use crate::runtime::{QueueAction, QueueItem};
/// API web method to retrieve the latest display result for an element,
// or wait for the next display result if no display result is cached
pub struct GetDisplayEndpoint { pub struct GetDisplayEndpoint {
//sender: tokio::sync::mpsc::Sender<SetCallbackAsync>, //sender: tokio::sync::mpsc::Sender<SetCallbackAsync>,
//receiver: Mutex<Option<tokio::sync::mpsc::Receiver<SetCallbackAsync>>>, //receiver: Mutex<Option<tokio::sync::mpsc::Receiver<SetCallbackAsync>>>,
@ -40,33 +42,31 @@ impl AsyncCallable for GetDisplayEndpoint {
} }
} }
); );
if let Ok(_) = send_result { match send_result {
Ok(_) => {
// TODO: don't poll for response // TODO: don't poll for response
log::info!("waiting for display for item #{}", index); log::info!("waiting for display for item #{}", index);
let sleep_duration = std::time::Duration::from_millis(10); match super::async_utils::channel_recv(receiver).await {
let receiver = Mutex::new(receiver);
loop {
let received = receiver.lock().unwrap().try_recv();
match received {
Err(mpsc::TryRecvError::Disconnected) => { Err(mpsc::TryRecvError::Disconnected) => {
log::info!("Failed to response for get_display for #{}", index); let msg = format!("Failed to response for get_display for #{}", index);
return vec![Primitive::Empty]; log::warn!("{}", msg);
return vec![ApiDisplayResult::failure(msg, "receiving channel disconnected").to_primitive()];
}, },
Err(_) => {}, Err(_) => return vec![], // impossible
Ok(x) => { Ok(x) => {
log::debug!("got display for item #{}", index); log::debug!("got display for item #{}", index);
return vec![x]; return vec![ApiDisplayResult::success(x).to_primitive()];
}, },
} }
tokio::time::sleep(sleep_duration).await; },
Err(_e) => {
let msg = format!("Failed to get_display for #{}", index);
log::warn!("{}", msg);
vec![ApiDisplayResult::failure(msg, "sending channel disconnected").to_primitive()]
}
} }
} else { } else {
log::info!("Failed to get_display for #{}", index); vec![ApiDisplayResult::failure("Failed to get param 0", "invalid call parameters").to_primitive()]
vec![Primitive::Empty]
}
} else {
vec![Primitive::Empty]
} }
} }
} }

View file

@ -4,6 +4,7 @@ use super::ApiParameterType;
use crate::runtime::{QueueAction, QueueItem}; use crate::runtime::{QueueAction, QueueItem};
/// API web method to retrieve all ElementConfig items from the back-end, as described in the config file
pub fn get_items(sender: Sender<QueueItem>) -> impl Fn(ApiParameterType) -> ApiParameterType { pub fn get_items(sender: Sender<QueueItem>) -> impl Fn(ApiParameterType) -> ApiParameterType {
let sender = Mutex::new(sender); let sender = Mutex::new(sender);
move |_| { move |_| {

View file

@ -1,13 +1,16 @@
mod about; mod about;
pub(crate) mod async_utils;
mod get_display; mod get_display;
mod get_item; mod get_items;
mod on_update; mod on_update;
mod reload; mod reload;
mod types;
pub use about::get_about; pub use about::get_about;
pub use get_display::GetDisplayEndpoint; pub use get_display::GetDisplayEndpoint;
pub use get_item::get_items; pub use get_items::get_items;
pub use on_update::on_update; pub use on_update::on_update;
pub use reload::reload; pub use reload::reload;
pub(super) use types::*;
pub(super) type ApiParameterType = Vec<usdpl_back::core::serdes::Primitive>; pub(super) type ApiParameterType = Vec<usdpl_back::core::serdes::Primitive>;

View file

@ -6,6 +6,7 @@ use super::ApiParameterType;
use crate::runtime::{QueueAction, QueueItem}; use crate::runtime::{QueueAction, QueueItem};
/// API web method to notify the back-end of an update event (e.g. click, slider slide, toggle)
pub fn on_update(sender: Sender<QueueItem>) -> impl Fn(ApiParameterType) -> ApiParameterType { pub fn on_update(sender: Sender<QueueItem>) -> impl Fn(ApiParameterType) -> ApiParameterType {
let sender = Mutex::new(sender); let sender = Mutex::new(sender);
move |mut params: ApiParameterType| { move |mut params: ApiParameterType| {

70
backend/src/api/types.rs Normal file
View file

@ -0,0 +1,70 @@
use serde::{Serialize, Deserialize};
use serde_json::Value;
use usdpl_back::core::serdes::Primitive;
#[derive(Serialize, Deserialize, Clone)]
#[serde(tag = "element")]
pub enum ApiDisplayResult {
#[serde(rename = "value")]
Value(ApiValue),
#[serde(rename = "error")]
Error(ApiError),
}
impl ApiDisplayResult {
pub fn dump(&self) -> String {
serde_json::to_string(self).unwrap()
}
pub fn to_primitive(self) -> Primitive {
Primitive::Json(self.dump())
}
pub fn success(primitive: Primitive) -> Self {
Self::Value(ApiValue::new(primitive))
}
pub fn failure<S: Into<String>, D: core::fmt::Display>(msg: S, err: D) -> Self {
Self::Error(ApiError::new(msg, err))
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct ApiValue {
pub value: Value,
}
impl ApiValue {
pub fn new(primitive: Primitive) -> Self {
let val = match primitive {
Primitive::Empty => Value::Null,
Primitive::String(s) => Value::String(s),
Primitive::F32(x) => x.into(),
Primitive::F64(x) => x.into(),
Primitive::U32(x) => Value::Number(x.into()),
Primitive::U64(x) => Value::Number(x.into()),
Primitive::I32(x) => Value::Number(x.into()),
Primitive::I64(x) => Value::Number(x.into()),
Primitive::Bool(x) => Value::Bool(x),
Primitive::Json(x) => serde_json::from_str(&x).unwrap_or(Value::Null),
};
Self {
value: val,
}
}
}
#[derive(Serialize, Deserialize, Clone)]
pub struct ApiError {
pub message: String,
pub exception: String,
}
impl ApiError {
pub fn new<S: Into<String>, D: core::fmt::Display>(msg: S, err: D) -> Self {
Self {
message: msg.into(),
exception: err.to_string(),
}
}
}

19
backend/src/cli.rs Normal file
View file

@ -0,0 +1,19 @@
use clap::Parser;
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
pub struct CliArgs {
/// Path to config file
#[clap(long)]
pub config: Option<std::path::PathBuf>,
/// Path to log file
#[clap(long)]
pub log: Option<std::path::PathBuf>,
}
impl CliArgs {
pub fn cli() -> Self {
Self::parse()
}
}

View file

@ -1,4 +1,5 @@
mod api; mod api;
mod cli;
mod config; mod config;
mod consts; mod consts;
mod runtime; mod runtime;
@ -9,16 +10,19 @@ use usdpl_back::Instance;
use usdpl_back::core::serdes::Primitive; use usdpl_back::core::serdes::Primitive;
fn main() -> Result<(), ()> { fn main() -> Result<(), ()> {
let log_filepath = format!("/tmp/{}.log", consts::PACKAGE_NAME); let cli_args = cli::CliArgs::cli();
let log_filepath = cli_args.log.unwrap_or_else(|| format!("/tmp/{}.log", consts::PACKAGE_NAME).into());
WriteLogger::init( WriteLogger::init(
#[cfg(debug_assertions)]{LevelFilter::Debug}, #[cfg(debug_assertions)]{LevelFilter::Debug},
#[cfg(not(debug_assertions))]{LevelFilter::Info}, #[cfg(not(debug_assertions))]{LevelFilter::Info},
Default::default(), Default::default(),
std::fs::File::create(&log_filepath).unwrap() std::fs::File::create(log_filepath).unwrap()
).unwrap(); ).unwrap();
let kaylon_conf = config::BaseConfig::load(consts::FILEPATH); let filepath = cli_args.config.unwrap_or(consts::FILEPATH.into());
let (executor, sender) = runtime::RuntimeExecutor::new(kaylon_conf);
let kaylon_conf = config::BaseConfig::load(&filepath);
let (executor, sender) = runtime::RuntimeExecutor::new(kaylon_conf, filepath);
log::info!("Starting back-end ({} v{})", consts::PACKAGE_NAME, consts::PACKAGE_VERSION); log::info!("Starting back-end ({} v{})", consts::PACKAGE_NAME, consts::PACKAGE_VERSION);
println!("Starting back-end ({} v{})", consts::PACKAGE_NAME, consts::PACKAGE_VERSION); println!("Starting back-end ({} v{})", consts::PACKAGE_NAME, consts::PACKAGE_VERSION);

View file

@ -4,6 +4,7 @@ use crate::config::{ElementConfig, ActionConfig};
pub type ActError = String; pub type ActError = String;
/// Something capable of performing an action.
pub trait Act: Sized { pub trait Act: Sized {
type Param; type Param;
type Config: ?Sized; type Config: ?Sized;
@ -12,6 +13,7 @@ pub trait Act: Sized {
fn run(self) -> Self::Return; fn run(self) -> Self::Return;
} }
/// Action performer for a regular element
pub struct Actor { pub struct Actor {
actor_type: ActorType, actor_type: ActorType,
index: usize, index: usize,

View file

@ -7,6 +7,7 @@ use super::{Act, ActError};
const VALUE_ENV_VAR: &str = "KAYLON_VALUE"; const VALUE_ENV_VAR: &str = "KAYLON_VALUE";
/// Runs a CLI command in Bash
pub struct CommandActor { pub struct CommandActor {
shell: String, shell: String,
run: String, run: String,

View file

@ -4,6 +4,7 @@ use usdpl_back::core::serdes::Primitive;
use crate::config::{AboutConfig, ElementConfig}; use crate::config::{AboutConfig, ElementConfig};
/// An API operation for the executor to perform
pub enum QueueAction { pub enum QueueAction {
GetAbout { GetAbout {
respond_to: Sender<AboutConfig>, respond_to: Sender<AboutConfig>,
@ -21,6 +22,7 @@ pub enum QueueAction {
} }
} }
/// Wrapper for an executor command
pub struct QueueItem { pub struct QueueItem {
pub action: QueueAction, pub action: QueueAction,
} }

View file

@ -1,4 +1,5 @@
use std::thread; use std::thread;
use std::path::{Path, PathBuf};
use std::sync::mpsc::{self, Receiver, Sender}; use std::sync::mpsc::{self, Receiver, Sender};
use crate::config::{BaseConfig, ElementConfig}; use crate::config::{BaseConfig, ElementConfig};
@ -7,15 +8,17 @@ use super::{ResultRouter, RouterCommand};
pub struct RuntimeExecutor { pub struct RuntimeExecutor {
config_data: BaseConfig, config_data: BaseConfig,
tasks_receiver: Receiver<QueueItem> tasks_receiver: Receiver<QueueItem>,
config_path: PathBuf,
} }
impl RuntimeExecutor { impl RuntimeExecutor {
pub fn new(conf: BaseConfig) -> (Self, Sender<QueueItem>) { pub fn new<P: AsRef<Path>>(conf: BaseConfig, path: P) -> (Self, Sender<QueueItem>) {
let (tx, rx) = mpsc::channel(); let (tx, rx) = mpsc::channel();
(Self { (Self {
config_data: conf, config_data: conf,
tasks_receiver: rx, tasks_receiver: rx,
config_path: path.as_ref().to_path_buf(),
}, tx) }, tx)
} }
@ -36,6 +39,7 @@ impl RuntimeExecutor {
ExecutorState { ExecutorState {
result_handler: ExecutorState::build_router(self.config_data.items().len()), result_handler: ExecutorState::build_router(self.config_data.items().len()),
config_data: self.config_data, config_data: self.config_data,
config_path: self.config_path,
}, },
self.tasks_receiver self.tasks_receiver
) )
@ -45,15 +49,19 @@ impl RuntimeExecutor {
struct ExecutorState { struct ExecutorState {
config_data: BaseConfig, config_data: BaseConfig,
result_handler: Sender<RouterCommand>, result_handler: Sender<RouterCommand>,
config_path: PathBuf,
} }
impl ExecutorState { impl ExecutorState {
fn handle_item(&mut self, item: QueueItem) { fn handle_item(&mut self, item: QueueItem) {
match item.action { match item.action {
QueueAction::GetAbout { respond_to } => { QueueAction::GetAbout { respond_to } => {
// retrieve about information from (in-memory) config file
respond_to.send(self.config_data.get_about().clone()).unwrap_or(()); respond_to.send(self.config_data.get_about().clone()).unwrap_or(());
}, },
QueueAction::DoUpdate { index, value } => { QueueAction::DoUpdate { index, value } => {
// trigger update event for element
// i.e. on_click, on_toggle, etc. action
if let Some(item) = self.config_data.get_item(index) { if let Some(item) = self.config_data.get_item(index) {
match super::Actor::build(item, (index, value)) { match super::Actor::build(item, (index, value)) {
Ok(act) => { Ok(act) => {
@ -73,23 +81,30 @@ impl ExecutorState {
} }
}, },
QueueAction::DoReload { respond_to } => { QueueAction::DoReload { respond_to } => {
self.config_data = BaseConfig::load(crate::consts::FILEPATH); // reload config file from storage
self.config_data = BaseConfig::load(&self.config_path);
self.populate_router(); self.populate_router();
respond_to.send(self.config_data.items().clone()).unwrap_or(()); respond_to.send(self.config_data.items().clone()).unwrap_or(());
}, },
QueueAction::SetCallback { index, respond_to } => { QueueAction::SetCallback { index, respond_to } => {
// register a callback with the ResultRouter for an element's action
// the next time that action is performed, the result will be sent through the callback
if let Some(elem) = self.config_data.get_item(index) { if let Some(elem) = self.config_data.get_item(index) {
let display_of = match elem { let display_of = match elem {
ElementConfig::ResultDisplay(c) => c.result_of, ElementConfig::ResultDisplay(c) => c.result_of,
_ => index, _ => index,
}; };
if let Err(_) = self.result_handler.send( if let Err(e) = self.result_handler.send(
RouterCommand::AddSender { RouterCommand::AddSender {
index: display_of, index: display_of,
sender: respond_to, sender: respond_to,
}) { }) {
log::warn!("Failed to send to ResultRouter, rebuilding router"); log::warn!("Failed to send to ResultRouter, rebuilding router");
self.result_handler = ExecutorState::build_router(self.config_data.items().len()); self.result_handler = ExecutorState::build_router(self.config_data.items().len());
if let Err(_) = self.result_handler.send(e.0) {
// don't retry if another error occurs
log::error!("Failed to send to ResultRouter again, did not SetCallback for item #{}", index);
}
} }
} }
} }

View file

@ -6,6 +6,7 @@ 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, RouterCommand};
/// Runs an action periodically
pub struct PeriodicActor { pub struct PeriodicActor {
config: ReadingConfig, config: ReadingConfig,
result_handler: Sender<RouterCommand>, result_handler: Sender<RouterCommand>,

View file

@ -20,9 +20,13 @@ pub enum RouterCommand {
} }
pub struct ResultRouter { pub struct ResultRouter {
/// receiver for new router commands to perform
comm: Receiver<RouterCommand>, comm: Receiver<RouterCommand>,
/// active callbacks; more than one sender may listen for a result
senders: Vec<[Option<Sender<Primitive>>; MAX_HANDLERS_PER_ITEM]>, senders: Vec<[Option<Sender<Primitive>>; MAX_HANDLERS_PER_ITEM]>,
/// cache of sender, for Act paradigm
comm_tx: Option<Sender<RouterCommand>>, comm_tx: Option<Sender<RouterCommand>>,
/// cache of unheard results
cache: Vec<Option<Primitive>>, cache: Vec<Option<Primitive>>,
} }
@ -62,16 +66,21 @@ impl Act for ResultRouter {
for command in self.comm.iter() { for command in self.comm.iter() {
match command { match command {
RouterCommand::AddSender { index, sender } => { RouterCommand::AddSender { index, sender } => {
// register result listener
log::debug!("Handling AddSender for item #{}", index); log::debug!("Handling AddSender for item #{}", index);
if let Some(senders) = self.senders.get_mut(index) { if let Some(senders) = self.senders.get_mut(index) {
// send cached value, if available // send cached value, if available.
// This avoids race conditions from a result being received before
// a listener has been registered. This is especially an issue during
// program start, when actions run immediately and listeners come from
// the slow front-end (web request in the browser)
if self.cache[index].is_some() { if self.cache[index].is_some() {
log::debug!("Routing cached result for item #{}", index); log::debug!("Routing cached result for item #{}", index);
let result = self.cache[index].take().unwrap(); let result = self.cache[index].take().unwrap();
match sender.send(result) { match sender.send(result) {
Ok(_) => {}, Ok(_) => {},
Err(e) => { Err(e) => {
self.cache[index] = Some(e.0); self.cache[index] = Some(e.0); // re-cache if send fails
log::debug!("ResultRouter ignoring AddSender since sending cached value failed"); log::debug!("ResultRouter ignoring AddSender since sending cached value failed");
continue; continue;
}, },
@ -94,9 +103,11 @@ impl Act for ResultRouter {
} }
} }
RouterCommand::HandleResult {index, result} => { RouterCommand::HandleResult {index, result} => {
// send a result to all (relevant) listeners
log::debug!("Handling HandleResult for item #{}", index); log::debug!("Handling HandleResult for item #{}", index);
if let Some(senders) = self.senders.get_mut(index) { if let Some(senders) = self.senders.get_mut(index) {
if Self::all_senders_none(senders) { if Self::all_senders_none(senders) {
// cache result if it won't be heard
self.cache[index] = Some(result); self.cache[index] = Some(result);
log::debug!("Cached result for item #{}", index); log::debug!("Cached result for item #{}", index);
} else { } else {

View file

@ -1,47 +1,62 @@
{ {
"api-version": "v0.0.0", "api-version": "v0.0.0",
"items": [ "items": [
{
"element": "result-display",
"title": "Best Kaylon",
"result_of": 1
},
{ {
"element": "button", "element": "button",
"title": "Test Button", "title": "Tell Me!",
"on_click": { "on_click": {
"action": "command", "action": "command",
"run": "echo 'hello button'" "run": "echo ISAAC"
}
},
{
"element": "toggle",
"title": "Test Toggle",
"description": "Toggle description",
"on_toggle": {
"action": "command",
"run": "echo 'hello toggle ${KAYLON_VALUE}'"
}
},
{
"element": "slider",
"title": "Test Slider",
"min": 0,
"max": 3,
"notches": null,
"on_set": {
"action": "command",
"run": "echo 'hello slider'"
}
},
{
"element": "reading",
"title": "Test Reading",
"period_ms": 10000,
"on_period": {
"action": "command",
"run": "echo 'hello reading'"
} }
}, },
{ {
"element": "result-display", "element": "result-display",
"title": "Test Result", "title": "Smartest Kaylon",
"result_of": 1 "result_of": 1
},
{
"element": "toggle",
"title": "Switch",
"description": "Toggle me!",
"on_toggle": {
"action": "command",
"run": "echo \"Toggle is now ${KAYLON_VALUE}\""
}
},
{
"element": "result-display",
"title": "",
"result_of": 3
},
{
"element": "slider",
"title": "Slider",
"min": 0,
"max": 10,
"notches": null,
"on_set": {
"action": "command",
"run": "echo \"Slider is now ${KAYLON_VALUE}\""
}
},
{
"element": "result-display",
"title": "",
"result_of": 5
},
{
"element": "reading",
"title": "Fan Speed",
"period_ms": 1000,
"on_period": {
"action": "command",
"run": "cat /sys/class/hwmon/hwmon5/fan1_input"
}
} }
], ],
"about": { "about": {

View file

@ -11,6 +11,6 @@ class Plugin:
# Asyncio-compatible long-running code, executed in a task when the plugin is loaded # Asyncio-compatible long-running code, executed in a task when the plugin is loaded
async def _main(self): async def _main(self):
# startup # startup
#self.backend_proc = subprocess.Popen([PARENT_DIR + "/bin/backend"]) #self.backend_proc = subprocess.Popen([PARENT_DIR + "/bin/backend", "--config", ""])
while True: while True:
await asyncio.sleep(1) await asyncio.sleep(1)

171
package-lock.json generated
View file

@ -9,7 +9,7 @@
"version": "0.0.1", "version": "0.0.1",
"license": "GPL-2.0-or-later", "license": "GPL-2.0-or-later",
"dependencies": { "dependencies": {
"decky-frontend-lib": "^1.0.1", "decky-frontend-lib": "*",
"react-icons": "^4.3.1", "react-icons": "^4.3.1",
"usdpl-front": "file:./src/usdpl_front" "usdpl-front": "file:./src/usdpl_front"
}, },
@ -77,9 +77,9 @@
"dev": true "dev": true
}, },
"node_modules/@jridgewell/trace-mapping": { "node_modules/@jridgewell/trace-mapping": {
"version": "0.3.14", "version": "0.3.15",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/resolve-uri": "^3.0.3",
@ -153,9 +153,9 @@
} }
}, },
"node_modules/@rollup/plugin-typescript": { "node_modules/@rollup/plugin-typescript": {
"version": "8.3.4", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.5.0.tgz",
"integrity": "sha512-wt7JnYE9antX6BOXtsxGoeVSu4dZfw0dU3xykfOQ4hC3EddxRbVG/K0xiY1Wup7QOHJcjLYXWAn0Kx9Z1SBHHg==", "integrity": "sha512-wMv1/scv0m/rXx21wD2IsBbJFba8wGF3ErJIr6IKRfRj49S85Lszbxb4DCo8iILpluTjk2GAAu9CoZt4G3ppgQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@rollup/pluginutils": "^3.1.0", "@rollup/pluginutils": "^3.1.0",
@ -199,9 +199,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/eslint": { "node_modules/@types/eslint": {
"version": "8.4.5", "version": "8.4.6",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz",
"integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", "integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@types/estree": "*", "@types/estree": "*",
@ -231,9 +231,9 @@
"dev": true "dev": true
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "18.6.4", "version": "18.7.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.4.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.17.tgz",
"integrity": "sha512-I4BD3L+6AWiUobfxZ49DlU43gtI+FTHSv9pE2Zekg6KjMpre4ByusaljW3vYSLJrvQ1ck1hUaeVu8HVlY3vzHg==", "integrity": "sha512-0UyfUnt02zIuqp7yC8RYtDkp/vo8bFaQ13KkSEvUAohPOAlnVNbj5Fi3fgPSuwzakS+EvvnnZ4x9y7i6ASaSPQ==",
"dev": true "dev": true
}, },
"node_modules/@types/prop-types": { "node_modules/@types/prop-types": {
@ -539,9 +539,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001374", "version": "1.0.30001397",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001374.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001397.tgz",
"integrity": "sha512-mWvzatRx3w+j5wx/mpFN5v5twlPrabG8NqX2c6e45LCpymdoGqNvRkRutFUqpRTXKFQFNQJasvK0YT7suW6/Hw==", "integrity": "sha512-SW9N2TbCdLf0eiNDRrrQXx2sOkaakNZbCjgNpPyMJJbiOrU5QzMIrXOVMRM1myBXTD5iTkdrtU/EguCrBocHlA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -588,9 +588,12 @@
"dev": true "dev": true
}, },
"node_modules/decky-frontend-lib": { "node_modules/decky-frontend-lib": {
"version": "1.3.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/decky-frontend-lib/-/decky-frontend-lib-1.3.0.tgz", "resolved": "https://registry.npmjs.org/decky-frontend-lib/-/decky-frontend-lib-3.0.0.tgz",
"integrity": "sha512-j3MfgfSlAE1TUZ5BCmwh01GLFUZsCkDH/FRfTWGYbaJ+49sy6WM9u/1hFHTuX43lK9ZM4BABvweKjsi2AnzsMw==" "integrity": "sha512-ZqJ9ii7QoYWHFfkU8hV82IHi3+McZDmE4wS22duXpgRI8r5BBMiZItw6tYkc24ZtsJIVso83FFt7adcEBqBwJA==",
"dependencies": {
"minimist": "^1.2.6"
}
}, },
"node_modules/deepmerge": { "node_modules/deepmerge": {
"version": "4.2.2", "version": "4.2.2",
@ -602,9 +605,9 @@
} }
}, },
"node_modules/electron-to-chromium": { "node_modules/electron-to-chromium": {
"version": "1.4.211", "version": "1.4.248",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.211.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.248.tgz",
"integrity": "sha512-BZSbMpyFQU0KBJ1JG26XGeFI3i4op+qOYGxftmZXFZoHkhLgsSv4DHDJfl8ogII3hIuzGt51PaZ195OVu0yJ9A==", "integrity": "sha512-qShjzEYpa57NnhbW2K+g+Fl+eNoDvQ7I+2MRwWnU6Z6F0HhXekzsECCLv+y2OJUsRodjqoSfwHkIX42VUFtUzg==",
"dev": true "dev": true
}, },
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
@ -955,8 +958,7 @@
"node_modules/minimist": { "node_modules/minimist": {
"version": "1.2.6", "version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
"dev": true
}, },
"node_modules/neo-async": { "node_modules/neo-async": {
"version": "2.6.2", "version": "2.6.2",
@ -1080,9 +1082,9 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "2.77.2", "version": "2.79.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.0.tgz",
"integrity": "sha512-m/4YzYgLcpMQbxX3NmAqDvwLATZzxt8bIegO78FZLl+lAgKJBd1DRAOeEiZcKOIOPjxE6ewHWHNgGEalFXuz1g==", "integrity": "sha512-x4KsrCgwQ7ZJPcFA/SUu6QVcYlO7uRLfLAy0DSA4NS2eG8japdbpM50ToH7z4iObodRYOJ0soneF0iaQRJ6zhA==",
"dev": true, "dev": true,
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
@ -1264,9 +1266,9 @@
} }
}, },
"node_modules/terser": { "node_modules/terser": {
"version": "5.14.2", "version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/source-map": "^0.3.2", "@jridgewell/source-map": "^0.3.2",
@ -1282,16 +1284,16 @@
} }
}, },
"node_modules/terser-webpack-plugin": { "node_modules/terser-webpack-plugin": {
"version": "5.3.3", "version": "5.3.6",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz",
"integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==", "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@jridgewell/trace-mapping": "^0.3.7", "@jridgewell/trace-mapping": "^0.3.14",
"jest-worker": "^27.4.5", "jest-worker": "^27.4.5",
"schema-utils": "^3.1.1", "schema-utils": "^3.1.1",
"serialize-javascript": "^6.0.0", "serialize-javascript": "^6.0.0",
"terser": "^5.7.2" "terser": "^5.14.1"
}, },
"engines": { "engines": {
"node": ">= 10.13.0" "node": ">= 10.13.0"
@ -1322,9 +1324,9 @@
"dev": true "dev": true
}, },
"node_modules/typescript": { "node_modules/typescript": {
"version": "4.7.4", "version": "4.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
"dev": true, "dev": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
@ -1335,9 +1337,9 @@
} }
}, },
"node_modules/update-browserslist-db": { "node_modules/update-browserslist-db": {
"version": "1.0.5", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.8.tgz",
"integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", "integrity": "sha512-GHg7C4M7oJSJYW/ED/5QOJ7nL/E0lwTOBGsOorA7jqHr8ExUhPfwAotIAmdSw/LWv3SMLSNpzTAgeLG9zaZKTA==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -1461,11 +1463,8 @@
"dev": true "dev": true
}, },
"src/usdpl_front": { "src/usdpl_front": {
"version": "0.6.0", "version": "0.6.2",
"license": "GPL-3.0-only" "license": "GPL-3.0-only"
},
"src/usdpl-front": {
"extraneous": true
} }
}, },
"dependencies": { "dependencies": {
@ -1509,9 +1508,9 @@
"dev": true "dev": true
}, },
"@jridgewell/trace-mapping": { "@jridgewell/trace-mapping": {
"version": "0.3.14", "version": "0.3.15",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
"dev": true, "dev": true,
"requires": { "requires": {
"@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/resolve-uri": "^3.0.3",
@ -1567,9 +1566,9 @@
} }
}, },
"@rollup/plugin-typescript": { "@rollup/plugin-typescript": {
"version": "8.3.4", "version": "8.5.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.3.4.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-8.5.0.tgz",
"integrity": "sha512-wt7JnYE9antX6BOXtsxGoeVSu4dZfw0dU3xykfOQ4hC3EddxRbVG/K0xiY1Wup7QOHJcjLYXWAn0Kx9Z1SBHHg==", "integrity": "sha512-wMv1/scv0m/rXx21wD2IsBbJFba8wGF3ErJIr6IKRfRj49S85Lszbxb4DCo8iILpluTjk2GAAu9CoZt4G3ppgQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@rollup/pluginutils": "^3.1.0", "@rollup/pluginutils": "^3.1.0",
@ -1596,9 +1595,9 @@
} }
}, },
"@types/eslint": { "@types/eslint": {
"version": "8.4.5", "version": "8.4.6",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz",
"integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", "integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/estree": "*", "@types/estree": "*",
@ -1628,9 +1627,9 @@
"dev": true "dev": true
}, },
"@types/node": { "@types/node": {
"version": "18.6.4", "version": "18.7.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.4.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.17.tgz",
"integrity": "sha512-I4BD3L+6AWiUobfxZ49DlU43gtI+FTHSv9pE2Zekg6KjMpre4ByusaljW3vYSLJrvQ1ck1hUaeVu8HVlY3vzHg==", "integrity": "sha512-0UyfUnt02zIuqp7yC8RYtDkp/vo8bFaQ13KkSEvUAohPOAlnVNbj5Fi3fgPSuwzakS+EvvnnZ4x9y7i6ASaSPQ==",
"dev": true "dev": true
}, },
"@types/prop-types": { "@types/prop-types": {
@ -1900,9 +1899,9 @@
"dev": true "dev": true
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001374", "version": "1.0.30001397",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001374.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001397.tgz",
"integrity": "sha512-mWvzatRx3w+j5wx/mpFN5v5twlPrabG8NqX2c6e45LCpymdoGqNvRkRutFUqpRTXKFQFNQJasvK0YT7suW6/Hw==", "integrity": "sha512-SW9N2TbCdLf0eiNDRrrQXx2sOkaakNZbCjgNpPyMJJbiOrU5QzMIrXOVMRM1myBXTD5iTkdrtU/EguCrBocHlA==",
"dev": true "dev": true
}, },
"chrome-trace-event": { "chrome-trace-event": {
@ -1936,9 +1935,12 @@
"dev": true "dev": true
}, },
"decky-frontend-lib": { "decky-frontend-lib": {
"version": "1.3.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/decky-frontend-lib/-/decky-frontend-lib-1.3.0.tgz", "resolved": "https://registry.npmjs.org/decky-frontend-lib/-/decky-frontend-lib-3.0.0.tgz",
"integrity": "sha512-j3MfgfSlAE1TUZ5BCmwh01GLFUZsCkDH/FRfTWGYbaJ+49sy6WM9u/1hFHTuX43lK9ZM4BABvweKjsi2AnzsMw==" "integrity": "sha512-ZqJ9ii7QoYWHFfkU8hV82IHi3+McZDmE4wS22duXpgRI8r5BBMiZItw6tYkc24ZtsJIVso83FFt7adcEBqBwJA==",
"requires": {
"minimist": "^1.2.6"
}
}, },
"deepmerge": { "deepmerge": {
"version": "4.2.2", "version": "4.2.2",
@ -1947,9 +1949,9 @@
"dev": true "dev": true
}, },
"electron-to-chromium": { "electron-to-chromium": {
"version": "1.4.211", "version": "1.4.248",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.211.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.248.tgz",
"integrity": "sha512-BZSbMpyFQU0KBJ1JG26XGeFI3i4op+qOYGxftmZXFZoHkhLgsSv4DHDJfl8ogII3hIuzGt51PaZ195OVu0yJ9A==", "integrity": "sha512-qShjzEYpa57NnhbW2K+g+Fl+eNoDvQ7I+2MRwWnU6Z6F0HhXekzsECCLv+y2OJUsRodjqoSfwHkIX42VUFtUzg==",
"dev": true "dev": true
}, },
"enhanced-resolve": { "enhanced-resolve": {
@ -2232,8 +2234,7 @@
"minimist": { "minimist": {
"version": "1.2.6", "version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
"dev": true
}, },
"neo-async": { "neo-async": {
"version": "2.6.2", "version": "2.6.2",
@ -2331,9 +2332,9 @@
} }
}, },
"rollup": { "rollup": {
"version": "2.77.2", "version": "2.79.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.0.tgz",
"integrity": "sha512-m/4YzYgLcpMQbxX3NmAqDvwLATZzxt8bIegO78FZLl+lAgKJBd1DRAOeEiZcKOIOPjxE6ewHWHNgGEalFXuz1g==", "integrity": "sha512-x4KsrCgwQ7ZJPcFA/SUu6QVcYlO7uRLfLAy0DSA4NS2eG8japdbpM50ToH7z4iObodRYOJ0soneF0iaQRJ6zhA==",
"dev": true, "dev": true,
"requires": { "requires": {
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
@ -2457,9 +2458,9 @@
"dev": true "dev": true
}, },
"terser": { "terser": {
"version": "5.14.2", "version": "5.15.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", "resolved": "https://registry.npmjs.org/terser/-/terser-5.15.0.tgz",
"integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", "integrity": "sha512-L1BJiXVmheAQQy+as0oF3Pwtlo4s3Wi1X2zNZ2NxOB4wx9bdS9Vk67XQENLFdLYGCK/Z2di53mTj/hBafR+dTA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@jridgewell/source-map": "^0.3.2", "@jridgewell/source-map": "^0.3.2",
@ -2469,16 +2470,16 @@
} }
}, },
"terser-webpack-plugin": { "terser-webpack-plugin": {
"version": "5.3.3", "version": "5.3.6",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz",
"integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==", "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@jridgewell/trace-mapping": "^0.3.7", "@jridgewell/trace-mapping": "^0.3.14",
"jest-worker": "^27.4.5", "jest-worker": "^27.4.5",
"schema-utils": "^3.1.1", "schema-utils": "^3.1.1",
"serialize-javascript": "^6.0.0", "serialize-javascript": "^6.0.0",
"terser": "^5.7.2" "terser": "^5.14.1"
} }
}, },
"tslib": { "tslib": {
@ -2488,15 +2489,15 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "4.7.4", "version": "4.8.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.3.tgz",
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", "integrity": "sha512-goMHfm00nWPa8UvR/CPSvykqf6dVV8x/dp0c5mFTMTIu0u0FlGWRioyy7Nn0PGAdHxpJZnuO/ut+PpQ8UiHAig==",
"dev": true "dev": true
}, },
"update-browserslist-db": { "update-browserslist-db": {
"version": "1.0.5", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.8.tgz",
"integrity": "sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q==", "integrity": "sha512-GHg7C4M7oJSJYW/ED/5QOJ7nL/E0lwTOBGsOorA7jqHr8ExUhPfwAotIAmdSw/LWv3SMLSNpzTAgeLG9zaZKTA==",
"dev": true, "dev": true,
"requires": { "requires": {
"escalade": "^3.1.1", "escalade": "^3.1.1",

View file

@ -1,9 +1,9 @@
{ {
"name": "Kaylon", "name": "Kaylon",
"author": "NGnius", "author": "NGnius",
"flags": ["debug", "_root"], "flags": ["_debug", "_root"],
"publish": { "publish": {
"tags": ["template", "root"], "tags": ["automation", "cli"],
"description": "Better than the Borg", "description": "Better than the Borg",
"image": "" "image": ""
} }

View file

@ -66,6 +66,19 @@ export type CResultDisplay = {
export type CElement = CButton | CToggle | CSlider | CReading | CResultDisplay; export type CElement = CButton | CToggle | CSlider | CReading | CResultDisplay;
export type CErrorResult = {
element: string; // "error"
message: string;
exception: string;
}
export type CValueResult = {
element: string; // "value"
value: any;
}
export type CDisplayResponse = CValueResult | CErrorResult;
export async function getElements(): Promise<CElement[]> { export async function getElements(): Promise<CElement[]> {
return (await call_backend("get_items", []))[0]; return (await call_backend("get_items", []))[0];
} }
@ -74,7 +87,7 @@ export async function onUpdate(index: number, value: any): Promise<any> {
return (await call_backend("on_update", [index, value]))[0]; return (await call_backend("on_update", [index, value]))[0];
} }
export async function getDisplay(index: number): Promise<string | null> { export async function getDisplay(index: number): Promise<CDisplayResponse> {
return (await call_backend("get_display", [index]))[0]; return (await call_backend("get_display", [index]))[0];
} }

View file

@ -33,10 +33,26 @@ let about: backend.CAbout | null = null;
let update = () => {}; let update = () => {};
function displayCallback(index: number) { function displayCallback(index: number) {
return (newVal: any) => { return (newVal: backend.CDisplayResponse) => {
set_value(DISPLAY_KEY + index.toString(), newVal); if (newVal != null) {
switch (newVal.element) {
case "value":
let val = newVal as backend.CValueResult;
console.log("KAYLON: Got display for " + index.toString(), val);
set_value(DISPLAY_KEY + index.toString(), val.value);
break;
case "error":
let err = newVal as backend.CErrorResult;
console.warn("KAYLON: Got display error for " + index.toString(), err);
break;
default:
console.error("KAYLON: Got invalid display response for " + index.toString(), newVal);
break;
}
} else {
console.warn("KAYLON: Ignoring null display result for " + index.toString());
}
backend.resolve(backend.getDisplay(index), displayCallback(index)); backend.resolve(backend.getDisplay(index), displayCallback(index));
console.log("Got display for " + index.toString(), newVal);
update(); update();
} }
} }

File diff suppressed because one or more lines are too long

Binary file not shown.