Compare commits

..

3 commits

Author SHA1 Message Date
54a0f7f533
Feat: fully working gui
Some checks reported errors
continuous-integration/drone/push Build was killed
2023-02-17 14:14:00 +01:00
1a1320c09c
Chore: use diffrent topics for time and confirmation 2023-02-17 14:13:23 +01:00
5b2aefe659
Fix: ignore dublicated uuids 2023-02-17 12:15:04 +01:00
7 changed files with 553 additions and 134 deletions

248
receiver/Cargo.lock generated
View file

@ -247,6 +247,21 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "chrono"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"time",
"wasm-bindgen",
"winapi",
]
[[package]] [[package]]
name = "cipher" name = "cipher"
version = "0.4.3" version = "0.4.3"
@ -364,6 +379,12 @@ version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58baae561b85ca19b3122a9ddd35c8ec40c3bcd14fe89921824eae73f7baffbf" checksum = "58baae561b85ca19b3122a9ddd35c8ec40c3bcd14fe89921824eae73f7baffbf"
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]] [[package]]
name = "core-foundation" name = "core-foundation"
version = "0.6.4" version = "0.6.4"
@ -568,6 +589,50 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
[[package]]
name = "cxx"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86d3488e7665a7a483b57e25bdd90d0aeb2bc7608c8d0346acf2ad3f1caf1d62"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48fcaf066a053a41a81dfb14d57d99738b767febb8b735c3016e469fac5da690"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ef98b8b717a829ca5603af80e1f9e2e48013ab227b68ef37872ef84ee479bf"
[[package]]
name = "cxxbridge-macro"
version = "1.0.91"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "d3d12" name = "d3d12"
version = "0.5.0" version = "0.5.0"
@ -614,6 +679,19 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "derive_more"
version = "0.99.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.9.0" version = "0.9.0"
@ -769,6 +847,15 @@ dependencies = [
"miniz_oxide", "miniz_oxide",
] ]
[[package]]
name = "float_next_after"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fc612c5837986b7104a87a0df74a5460931f1c5274be12f8d0f40aa2f30d632"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "flume" name = "flume"
version = "0.10.14" version = "0.10.14"
@ -980,7 +1067,7 @@ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -1139,6 +1226,30 @@ dependencies = [
"digest 0.10.6", "digest 0.10.6",
] ]
[[package]]
name = "iana-time-zone"
version = "0.1.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765"
dependencies = [
"android_system_properties",
"core-foundation-sys 0.8.3",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"winapi",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca"
dependencies = [
"cxx",
"cxx-build",
]
[[package]] [[package]]
name = "iced" name = "iced"
version = "0.7.0" version = "0.7.0"
@ -1154,6 +1265,20 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "iced_aw"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a108808b3a5753ca85f460cffef776c33aa2e0dd557aab9261c7d2c60bbdb6d"
dependencies = [
"chrono",
"iced_graphics",
"iced_native",
"iced_style",
"lazy_static",
"num-traits",
]
[[package]] [[package]]
name = "iced_core" name = "iced_core"
version = "0.7.0" version = "0.7.0"
@ -1189,6 +1314,7 @@ dependencies = [
"iced_native", "iced_native",
"iced_style", "iced_style",
"log", "log",
"lyon",
"raw-window-handle 0.5.0", "raw-window-handle 0.5.0",
"thiserror", "thiserror",
] ]
@ -1347,6 +1473,21 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "libm"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
[[package]]
name = "link-cplusplus"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "linked-hash-map" name = "linked-hash-map"
version = "0.5.6" version = "0.5.6"
@ -1372,6 +1513,58 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "lyon"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e7f9cda98b5430809e63ca5197b06c7d191bf7e26dfc467d5a3f0290e2a74f"
dependencies = [
"lyon_algorithms",
"lyon_tessellation",
]
[[package]]
name = "lyon_algorithms"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb7a1845c15729d73d25d42cb650b647f73c3494453a5c3cd3aae0df3ac5c6c"
dependencies = [
"lyon_path",
"num-traits",
]
[[package]]
name = "lyon_geom"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74df1ff0a0147282eb10699537a03baa7d31972b58984a1d44ce0624043fe8ad"
dependencies = [
"arrayvec 0.7.2",
"euclid",
"num-traits",
]
[[package]]
name = "lyon_path"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8358c012e5651e4619cfd0b5b75c0f77866181a01b0909aab4bae14adf660"
dependencies = [
"lyon_geom",
"num-traits",
]
[[package]]
name = "lyon_tessellation"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d2124218d5428149f9e09520b9acc024334a607e671f032d06567b61008977c"
dependencies = [
"float_next_after",
"lyon_path",
"thiserror",
]
[[package]] [[package]]
name = "malloc_buf" name = "malloc_buf"
version = "0.0.6" version = "0.0.6"
@ -1451,7 +1644,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.42.0", "windows-sys 0.42.0",
] ]
@ -1599,6 +1792,16 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.15" version = "0.2.15"
@ -1606,6 +1809,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"libm",
] ]
[[package]] [[package]]
@ -2057,9 +2261,11 @@ version = "0.1.0"
dependencies = [ dependencies = [
"base64", "base64",
"crypto_helper", "crypto_helper",
"derive_more",
"enigo", "enigo",
"hex", "hex",
"iced", "iced",
"iced_aw",
"rumqttc", "rumqttc",
"serde", "serde",
"serde_json", "serde_json",
@ -2121,6 +2327,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.20.8" version = "0.20.8"
@ -2190,6 +2405,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "scratch"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2"
[[package]] [[package]]
name = "sct" name = "sct"
version = "0.7.0" version = "0.7.0"
@ -2235,6 +2456,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "semver"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.152" version = "1.0.152"
@ -2495,6 +2722,17 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]] [[package]]
name = "tiny-skia" name = "tiny-skia"
version = "0.7.0" version = "0.7.0"
@ -2670,6 +2908,12 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"

View file

@ -14,6 +14,8 @@ serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
iced = "0.7.0" iced = "0.7.0"
rumqttc = { version = "0.20.0", default-features = false, features = ["use-rustls"] } rumqttc = { version = "0.20.0", default-features = false, features = ["use-rustls"] }
iced_aw = "0.3.0"
derive_more = "0.99.17"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
enigo = "0.0.14" enigo = "0.0.14"

View file

@ -1,10 +1,11 @@
use crypto_helper::crypto::Crypto; use crypto_helper::crypto::Crypto;
use mqtt::{Event, ConnectionError};
use std::{time::Duration, cell::RefCell}; use std::{time::Duration, cell::RefCell};
extern crate rumqttc as mqtt; extern crate rumqttc as mqtt;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub type TimeCallback = fn(u32) -> Result<(),()>; pub type TimeCallback<T> = fn(u32, &T) -> Result<(),()>;
#[derive(Serialize, Deserialize, PartialEq)] #[derive(Serialize, Deserialize, PartialEq)]
enum MqttMessageKind { enum MqttMessageKind {
@ -19,70 +20,97 @@ struct MqttMessage {
time: u32, time: u32,
} }
pub struct Comm { struct Topics {
client: RefCell<mqtt::Client>, time: String,
connection: RefCell<mqtt::Connection>, confirmation: String
handler: TimeCallback,
crypto: Crypto,
topic: String,
} }
impl Comm { pub struct Comm<T> {
client: RefCell<mqtt::Client>,
connection: RefCell<mqtt::Connection>,
handler: TimeCallback<T>,
crypto: Crypto,
topics: Topics,
last_uuids: RefCell<Vec<uuid::Uuid>>
}
impl<T> Comm<T> {
pub fn new( pub fn new(
broker_domain: &str, broker_domain: &str,
password: &str, password: &str,
handler: TimeCallback, handler: TimeCallback<T>,
) -> Result<Self, mqtt::ClientError> { ) -> Result<Self, mqtt::ClientError> {
let mut create_opts = let mut create_opts =
mqtt::MqttOptions::new(uuid::Uuid::new_v4().to_string(), broker_domain, 8883); mqtt::MqttOptions::new(uuid::Uuid::new_v4().to_string(), broker_domain, 8883);
create_opts.set_keep_alive(Duration::from_secs(5)); create_opts.set_keep_alive(Duration::from_secs(5));
let transport = mqtt::Transport::Tls(mqtt::TlsConfiguration::default()); let transport = mqtt::Transport::Tls(mqtt::TlsConfiguration::default());
create_opts.set_transport(transport); create_opts.set_transport(transport);
let (mut cli, conn) = mqtt::Client::new(create_opts, 10); let (mut cli, conn) = mqtt::Client::new(create_opts, 10);
let crypto = Crypto::new(password, broker_domain); let crypto = Crypto::new(password, broker_domain);
let topic = format!("org.speedclimbing.ok-ready-go.{password}"); let time_topic = format!("org.speedclimbing.ok-ready-go.{password}.time");
let topic = Crypto::sha256(&crypto.encrypt(&topic)); let time_topic = Crypto::sha256(&crypto.encrypt(&time_topic));
cli.subscribe(&topic, mqtt::QoS::AtMostOnce)?; let confirmation_topic = format!("org.speedclimbing.ok-ready-go.{password}.confirmation");
let confirmation_topic = Crypto::sha256(&crypto.encrypt(&confirmation_topic));
cli.subscribe(&time_topic, mqtt::QoS::AtMostOnce)?;
Ok(Comm { Ok(Comm {
client: RefCell::new(cli), client: RefCell::new(cli),
connection: RefCell::new(conn), connection: RefCell::new(conn),
handler, handler,
crypto, crypto,
topic, topics: Topics {
time: time_topic,
confirmation: confirmation_topic
},
last_uuids: RefCell::new(Vec::new())
}) })
} }
pub fn listen(&self) { pub fn handle_next_event(&self, handler_arg: &T) {
for (_, notification) in self.connection.borrow_mut().iter().enumerate() { let notification = self.connection.borrow_mut().iter().next();
if notification.is_none() {
return;
}
let notification = notification.unwrap();
self._handle_notification(notification, handler_arg);
}
pub fn disconnect(&self) {
self.client.borrow_mut().disconnect().unwrap();
}
fn _handle_notification(&self, notification: Result<Event, ConnectionError>, handler_arg: &T) {
if let Err(e) = notification { if let Err(e) = notification {
println!("Error: {:?}", e); println!("Error: {:?}", e);
continue; return;
} }
let notification = notification.unwrap(); let notification = notification.unwrap();
if let mqtt::Event::Outgoing(_) = notification { if let mqtt::Event::Outgoing(_) = notification {
continue; return;
} }
if let mqtt::Event::Incoming(notification) = notification { if let mqtt::Event::Incoming(notification) = notification {
self._handle_incoming_packet(notification); self._handle_incoming_packet(notification, handler_arg);
}
} }
} }
fn _handle_incoming_packet(&self, packet: mqtt::Packet) { fn _handle_incoming_packet(&self, packet: mqtt::Packet, handler_arg: &T) {
match packet { match packet {
mqtt::Packet::Publish(publish) => self._handle_publish_packet(publish), mqtt::Packet::Publish(publish) => self._handle_publish_packet(publish, handler_arg),
_ => return, _ => return,
} }
} }
fn _handle_publish_packet(&self, packet: mqtt::Publish) { fn _handle_publish_packet(&self, packet: mqtt::Publish, handler_arg: &T) {
if packet.topic != self.topic { if packet.topic != self.topics.time {
return; return;
} }
@ -95,7 +123,13 @@ impl Comm {
return; return;
} }
if (self.handler)(msg.time).is_err() { if self.last_uuids.borrow().contains(&msg.id) {
return;
}
self.last_uuids.borrow_mut().push(msg.id);
if (self.handler)(msg.time, handler_arg).is_err() {
println!("[WARN] error in time handler -> not sending confirmation"); println!("[WARN] error in time handler -> not sending confirmation");
return; return;
} }
@ -109,7 +143,7 @@ impl Comm {
let reply = serde_json::to_string(&reply).unwrap(); let reply = serde_json::to_string(&reply).unwrap();
let reply = self.crypto.encrypt(&reply); let reply = self.crypto.encrypt(&reply);
self.client.borrow_mut() self.client.borrow_mut()
.publish(&self.topic, mqtt::QoS::AtMostOnce, false, reply.as_bytes()) .publish(&self.topics.confirmation, mqtt::QoS::AtMostOnce, false, reply.as_bytes())
.unwrap(); .unwrap();
} }
} }

181
receiver/src/gui.rs Normal file
View file

@ -0,0 +1,181 @@
use std::sync::{Mutex, Arc};
use std::sync::mpsc::{self, TryRecvError};
use std::thread::{self};
use iced::theme;
use iced::widget::{
column, row, text,
text_input, pick_list
};
use iced::widget::{Button};
use iced::{alignment, Element, Length, Sandbox};
use iced_aw::number_input::NumberInput;
use crate::{keyboard::{self, ContinueButton}, comm};
pub struct Gui {
password: String,
c: Option<Box<dyn Fn() -> ()>>,
continue_sequence: Arc<Mutex<(u32, ContinueButton)>>
}
#[derive(Debug, Clone)]
pub enum Message {
ConnectPressed,
PasswordChanged(String),
DisconnectPressed,
ContinueButtonCountChanged(u32),
ContinueButtonChanged(ContinueButton)
}
impl Sandbox for Gui {
type Message = Message;
fn new() -> Self {
Gui {
password: "".to_owned(),
c: None,
continue_sequence: Arc::new(Mutex::new((2, ContinueButton::Tab)))
}
}
fn update(&mut self, event: Message) {
match event {
Message::PasswordChanged(p) => self.password = p,
Message::ConnectPressed => {
let res = Some(connect(self, &self.password));
self.c = res;
},
Message::DisconnectPressed => {
(self.c.as_mut().unwrap())();
self.c = None;
}
Message::ContinueButtonCountChanged(count) => {
self.continue_sequence.lock().unwrap().0 = count;
}
Message::ContinueButtonChanged(b) => {
self.continue_sequence.lock().unwrap().1 = b
}
}
}
fn view(&self) -> Element<Message> {
let connected = self.c.is_some();
let content: Element<_> = column![
text("OK! .. READY! ... GO!").size(30),
if connected {
connected_content(self)
} else {
not_connected_content(self)
}
]
.width(Length::Fill)
.spacing(20)
.padding(20)
.into();
content
}
fn title(&self) -> String {
"gui".to_owned()
}
fn theme(&self) -> iced::Theme {
iced::Theme::Dark
}
}
fn not_connected_content(gui: &Gui) -> Element<Message> {
column![
"Enter the same password as on your phone to connect.",
text_input("Enter password", &gui.password, Message::PasswordChanged)
.password()
.size(30),
button("Connect")
.width(Length::Fill)
.style(theme::Button::Primary)
.on_press(Message::ConnectPressed),
].width(Length::Fill).spacing(20).padding(0).into()
}
fn connected_content(gui: &Gui) -> Element<Message> {
let seq = gui.continue_sequence.lock().unwrap();
column![
"Connected.",
button("Disconnect")
.width(Length::Fill)
.style(theme::Button::Primary)
.on_press(Message::DisconnectPressed),
row![
NumberInput::new(seq.0, 10, Message::ContinueButtonCountChanged).step(1).size(30),
pick_list(vec![ContinueButton::Tab, ContinueButton::Enter], Some(seq.1), Message::ContinueButtonChanged).padding(5)
].spacing(20).width(Length::Fill)
].width(Length::Fill).spacing(20).padding(0).into()
}
// ----------
// components
// ----------
fn button<'a, Message: Clone>(label: &str) -> Button<'a, Message> {
iced::widget::button(text(label).horizontal_alignment(alignment::Horizontal::Center))
.padding(12)
}
// -------
// helpers
// -------
fn connect(gui: &Gui, password: &str) -> Box<dyn Fn() -> ()> {
let password = password.to_owned();
let mux = gui.continue_sequence.clone();
let (tx, rx) = mpsc::channel();
let handle = thread::spawn(move || {
let c = comm::Comm::new("broker.emqx.io", &password, handle_time).unwrap();
loop {
match rx.try_recv() {
Ok(()) | Err(TryRecvError::Disconnected) => {
println!("Comm is terminating.");
break;
},
Err(TryRecvError::Empty) => {}
}
c.handle_next_event(&mux);
}
c.disconnect();
});
Box::new(move || {
tx.send(()).unwrap();
while !handle.is_finished() {};
})
}
fn millis_to_string(millis: u32) -> String {
let seconds = (millis as f64) / 1000.0;
let formatted = format!("{:.2}", seconds);
formatted.replace(".", ",")
}
fn handle_time(time: u32, mux: &Arc<Mutex<(u32, ContinueButton)>>) -> Result<(), ()> {
let (continue_button_count, continue_button) = *mux.lock().unwrap();
println!("Got time: {}, count: {}, button: {}", time, continue_button_count, continue_button);
let time_with_comma = millis_to_string(time);
println!("-> Trying to type {time_with_comma}");
keyboard::type_text(&time_with_comma)?;
for _ in 0..continue_button_count {
println!("-> Trying to click {continue_button}");
keyboard::click_button(continue_button)?;
}
Ok(())
}

View file

@ -1,3 +1,11 @@
use derive_more::Display;
#[derive(Debug, Clone, Display, PartialEq, Eq, Copy)]
pub enum ContinueButton {
Tab,
Enter
}
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
use enigo::{KeyboardControllable}; use enigo::{KeyboardControllable};
@ -8,8 +16,12 @@ pub fn type_text(text: &str) -> Result<(),()> {
} }
#[cfg(target_family = "unix")] #[cfg(target_family = "unix")]
pub fn click_tab() -> Result<(),()> { pub fn click_button(button: ContinueButton) -> Result<(),()> {
enigo::Enigo::default().key_click(enigo::Key::Tab); let button = match button {
ContinueButton::Tab => enigo::Key::Tab,
ContinueButton::Enter => enigo::Key::Return,
};
enigo::Enigo::default().key_click(button);
Ok(()) Ok(())
} }

View file

@ -1,90 +1,21 @@
use iced::theme; use iced::Sandbox;
use iced::widget::{
checkbox, column, container, horizontal_space, image, radio, row, scrollable, slider, text,
text_input, toggler, vertical_space,
};
use iced::widget::{Button, Column, Container, Slider};
use iced::{alignment, Element, Length, Sandbox, Settings};
mod comm; mod comm;
mod keyboard; mod keyboard;
mod gui;
struct Gui {
password: String,
}
#[derive(Debug, Clone)]
enum Message {
Pressed,
PasswordChanged(String),
}
impl Sandbox for Gui {
type Message = Message;
fn new() -> Self {
Gui {password: "".to_owned()}
}
fn update(&mut self, event: Message) {
match event {
Message::PasswordChanged(p) => self.password = p,
Message::Pressed => println!("Pressed")
}
}
fn view(&self) -> Element<Message> {
let content: Element<_> = column![
text_input("Enter password", &self.password, Message::PasswordChanged)
.password()
.size(30),
button("Connect")
.style(theme::Button::Primary)
.on_press(Message::Pressed),
]
.max_width(540)
.spacing(20)
.padding(20)
.into();
content
}
fn title(&self) -> String {
"gui".to_owned()
}
}
fn button<'a, Message: Clone>(label: &str) -> Button<'a, Message> {
iced::widget::button(text(label).horizontal_alignment(alignment::Horizontal::Center))
.padding(12)
}
fn millis_to_string(millis: u32) -> String {
let seconds = (millis as f64) / 1000.0;
let formatted = format!("{:.2}", seconds);
formatted.replace(".", ",")
}
fn handle_time(time: u32) -> Result<(),()> {
println!("Got time: {}", time);
let time_with_comma = millis_to_string(time);
println!("Trying to type {time_with_comma}");
keyboard::type_text(&time_with_comma)?;
keyboard::click_tab()?;
keyboard::click_tab()?;
Ok(())
}
fn main() { fn main() {
let password = "test"; let settings = iced::Settings {
let broker_domain = "broker.emqx.io"; window: iced::window::Settings {
size: (300, 500),
resizable: true,
decorations: true,
..Default::default()
},
let c = comm::Comm::new(&broker_domain, &password, handle_time).unwrap(); ..Default::default()
};
//Gui::run(Settings::default()); gui::Gui::run(settings).unwrap();
println!("Processing requests...");
c.listen();
} }

View file

@ -1,15 +1,17 @@
let wasm_inited_resolve; let wasm_inited_resolve;
const wasm_inited = new Promise((resolve) => {wasm_inited_resolve = resolve;}); const wasm_inited = new Promise((resolve) => {
wasm_inited_resolve = resolve;
});
document.addEventListener("wasm-loaded", () => { document.addEventListener("wasm-loaded", () => {
wasm_inited_resolve(wasm_bindgen) wasm_inited_resolve(wasm_bindgen);
}); });
document.addEventListener("alpine:init", () => { document.addEventListener("alpine:init", () => {
Alpine.store("mqtt", { Alpine.store("mqtt", {
connected: false, connected: false,
_client: null, _client: null,
_topic: null, _topics: null,
_c: null, _c: null,
_pendingPromises: {}, _pendingPromises: {},
@ -18,7 +20,9 @@ document.addEventListener("alpine:init", () => {
if (!this.connected) return null; if (!this.connected) return null;
const id = uuidv4(); const id = uuidv4();
const promise = new Promise((resolve, reject) => {this._pendingPromises[id] = [resolve, reject]}); const promise = new Promise((resolve, reject) => {
this._pendingPromises[id] = [resolve, reject];
});
this._publish({ this._publish({
id: id, // used to prevent replay attacks and to identify confirm messages id: id, // used to prevent replay attacks and to identify confirm messages
kind: "Time", // can be "time" or "confirm" kind: "Time", // can be "time" or "confirm"
@ -47,12 +51,18 @@ document.addEventListener("alpine:init", () => {
console.log("Test", this._encrypt("test")); console.log("Test", this._encrypt("test"));
this._topic = Crypto.sha256( this._topics = {
this._encrypt(`org.speedclimbing.ok-ready-go.${password}`) time: Crypto.sha256(
).toString(); this._encrypt(`org.speedclimbing.ok-ready-go.${password}.time`)
).toString(),
confirmation: Crypto.sha256(
this._encrypt(
`org.speedclimbing.ok-ready-go.${password}.confirmation`
)
).toString(),
};
console.log("Connecting to MQTT broker..."); console.log("Connecting to MQTT broker...");
console.log("topic:", this._topic);
const options = { const options = {
// Clean session // Clean session
@ -65,7 +75,7 @@ document.addEventListener("alpine:init", () => {
this._client.on("connect", () => { this._client.on("connect", () => {
Alpine.store("localState")._state = 0; Alpine.store("localState")._state = 0;
that._client.subscribe(that._topic); that._client.subscribe(that._topics.confirmation);
this.connected = true; this.connected = true;
}); });
@ -74,7 +84,12 @@ document.addEventListener("alpine:init", () => {
message = that._decrypt(message.toString()); message = that._decrypt(message.toString());
const data = JSON.parse(message); const data = JSON.parse(message);
if (topic !== that._topic || data.kind !== "Confirm" || Object.keys(this._pendingPromises).indexOf(data.id) === -1) return; if (
topic !== that._topics.confirmation ||
data.kind !== "Confirm" ||
Object.keys(this._pendingPromises).indexOf(data.id) === -1
)
return;
console.log("<<< ", data); console.log("<<< ", data);
this._pendingPromises[data.id][0](); this._pendingPromises[data.id][0]();
@ -88,7 +103,7 @@ document.addEventListener("alpine:init", () => {
this._client = null; this._client = null;
this.connected = false; this.connected = false;
this._topic = null; this._topics = null;
for (const promiseId in this._pendingPromises) { for (const promiseId in this._pendingPromises) {
this._pendingPromises[promiseId][1](); this._pendingPromises[promiseId][1]();
@ -100,7 +115,7 @@ document.addEventListener("alpine:init", () => {
_publish(data) { _publish(data) {
const encryptedData = this._encrypt(JSON.stringify(data)); const encryptedData = this._encrypt(JSON.stringify(data));
console.log(">>> ", data); console.log(">>> ", data);
this._client.publish(this._topic, encryptedData, { this._client.publish(this._topics.time, encryptedData, {
qos: 1, qos: 1,
retain: false, retain: false,
}); });