Adjusted how we requre Sync + Send, added logger, cleanup dependencies, and added web server using warp and tokio

This commit is contained in:
Dreaded_X 2022-12-23 04:40:08 +01:00
parent 1b965cd45a
commit 7e3c3223b2
Signed by: Dreaded_X
GPG Key ID: 76BDEC4E165D8AD9
17 changed files with 809 additions and 736 deletions

873
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -13,6 +13,11 @@ dotenv = "0.15.0"
impl_cast = {path = "./impl_cast"}
google-home = {path = "./google-home"}
paste = "1.0.10"
tokio = { version = "1", features = ["full"] }
warp = "0.3"
log = "0.4"
env_logger = "0.10"
pollster = "0.2.5"
[profile.release]
lto=true

432
google-home/Cargo.lock generated
View File

@ -2,165 +2,12 @@
# It is not intended for manual editing.
version = 3
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6"
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bumpalo"
version = "3.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
[[package]]
name = "cc"
version = "1.0.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f"
dependencies = [
"iana-time-zone",
"num-integer",
"num-traits",
"serde",
"winapi",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cxx"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf"
dependencies = [
"cc",
"cxxbridge-flags",
"cxxbridge-macro",
"link-cplusplus",
]
[[package]]
name = "cxx-build"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39"
dependencies = [
"cc",
"codespan-reporting",
"once_cell",
"proc-macro2",
"quote",
"scratch",
"syn",
]
[[package]]
name = "cxxbridge-flags"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12"
[[package]]
name = "cxxbridge-macro"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "darling"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "google-home"
version = "0.1.0"
@ -169,52 +16,9 @@ dependencies = [
"impl_cast",
"serde",
"serde_json",
"serde_with",
"thiserror",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[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",
"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]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "impl_cast"
version = "0.1.0"
@ -222,81 +26,12 @@ dependencies = [
"paste",
]
[[package]]
name = "indexmap"
version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
"serde",
]
[[package]]
name = "itoa"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc"
[[package]]
name = "js-sys"
version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8"
[[package]]
name = "link-cplusplus"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369"
dependencies = [
"cc",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[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]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860"
[[package]]
name = "paste"
version = "1.0.10"
@ -327,12 +62,6 @@ version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "scratch"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898"
[[package]]
name = "serde"
version = "1.0.149"
@ -364,40 +93,6 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_with"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25bf4a5a814902cd1014dbccfa4d4560fb8432c779471e96e035602519f82eef"
dependencies = [
"base64",
"chrono",
"hex",
"indexmap",
"serde",
"serde_json",
"serde_with_macros",
"time",
]
[[package]]
name = "serde_with_macros"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3452b4c0f6c1e357f73fdb87cd1efabaa12acf328c7a528e252893baeb3f4aa"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.105"
@ -409,15 +104,6 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.37"
@ -438,126 +124,8 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376"
dependencies = [
"itoa",
"serde",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
[[package]]
name = "time-macros"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2"
dependencies = [
"time-core",
]
[[package]]
name = "unicode-ident"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3"
[[package]]
name = "unicode-width"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "wasm-bindgen"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@ -10,5 +10,4 @@ anyhow = "1.0.66"
impl_cast = { path = "../impl_cast" }
serde = { version ="1.0.149", features = ["derive"] }
serde_json = "1.0.89"
serde_with = "2.1.0"
thiserror = "1.0.37"

View File

@ -1,11 +1,12 @@
use serde::Serialize;
use serde_with::skip_serializing_none;
#[skip_serializing_none]
#[derive(Debug, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Attributes {
#[serde(skip_serializing_if = "Option::is_none")]
pub command_only_on_off: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query_only_on_off: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scene_reversible: Option<bool>,
}

View File

@ -1,5 +1,4 @@
use serde::Serialize;
use serde_with::skip_serializing_none;
use crate::{response, types::Type, traits::{AsOnOff, Trait, AsScene}, errors::{DeviceError, ErrorCode}, request::execute::CommandType};
@ -19,10 +18,9 @@ pub trait GoogleHomeDevice: AsOnOff + AsScene {
fn get_device_info(&self) -> Option<Info> {
None
}
}
// This trait exists just to hide the sync, query and execute function from the user
pub trait Fullfillment: GoogleHomeDevice {
fn sync(&self) -> response::sync::Device {
let name = self.get_device_name();
let mut device = response::sync::Device::new(&self.get_id(), &name.name, self.get_device_type());
@ -93,8 +91,6 @@ pub trait Fullfillment: GoogleHomeDevice {
}
}
impl<T: GoogleHomeDevice> Fullfillment for T {}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Name {
@ -119,13 +115,16 @@ impl Name {
}
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Info {
#[serde(skip_serializing_if = "Option::is_none")]
pub manufacturer: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hw_version: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sw_version: Option<String>,
// attributes
// customData

View File

@ -1,6 +1,6 @@
use std::collections::HashMap;
use crate::{request::{Request, Intent, self}, device::Fullfillment, response::{sync, ResponsePayload, query, execute, Response, self, State}, errors::{DeviceError, ErrorCode}};
use crate::{request::{Request, Intent, self}, device::GoogleHomeDevice, response::{sync, ResponsePayload, query, execute, Response, self, State}, errors::{DeviceError, ErrorCode}};
pub struct GoogleHome {
user_id: String,
@ -12,7 +12,7 @@ impl GoogleHome {
Self { user_id: user_id.into() }
}
pub fn handle_request(&self, request: Request, mut devices: &mut HashMap<String, &mut dyn Fullfillment>) -> Result<Response, anyhow::Error> {
pub fn handle_request(&self, request: Request, mut devices: &mut HashMap<String, &mut dyn GoogleHomeDevice>) -> Result<Response, anyhow::Error> {
// @TODO What do we do if we actually get more then one thing in the input array, right now
// we only respond to the first thing
let payload = request
@ -30,7 +30,7 @@ impl GoogleHome {
}
}
fn sync(&self, devices: &HashMap<String, &mut dyn Fullfillment>) -> sync::Payload {
fn sync(&self, devices: &HashMap<String, &mut dyn GoogleHomeDevice>) -> sync::Payload {
let mut resp_payload = sync::Payload::new(&self.user_id);
resp_payload.devices = devices
.iter()
@ -40,7 +40,7 @@ impl GoogleHome {
return resp_payload;
}
fn query(&self, payload: request::query::Payload, devices: &HashMap<String, &mut dyn Fullfillment>) -> query::Payload {
fn query(&self, payload: request::query::Payload, devices: &HashMap<String, &mut dyn GoogleHomeDevice>) -> query::Payload {
let mut resp_payload = query::Payload::new();
resp_payload.devices = payload.devices
.into_iter()
@ -62,7 +62,7 @@ impl GoogleHome {
}
fn execute(&self, payload: request::execute::Payload, devices: &mut HashMap<String, &mut dyn Fullfillment>) -> execute::Payload {
fn execute(&self, payload: request::execute::Payload, devices: &mut HashMap<String, &mut dyn GoogleHomeDevice>) -> execute::Payload {
let mut resp_payload = response::execute::Payload::new();
payload.commands
@ -240,7 +240,7 @@ mod tests {
let mut nightstand = TestOutlet::new("bedroom/nightstand");
let mut lamp = TestOutlet::new("living/lamp");
let mut scene = TestScene::new();
let mut devices: HashMap<String, &mut dyn Fullfillment> = HashMap::new();
let mut devices: HashMap<String, &mut dyn GoogleHomeDevice> = HashMap::new();
devices.insert(nightstand.get_id(), &mut nightstand);
devices.insert(lamp.get_id(), &mut lamp);
devices.insert(scene.get_id(), &mut scene);
@ -280,7 +280,7 @@ mod tests {
let mut nightstand = TestOutlet::new("bedroom/nightstand");
let mut lamp = TestOutlet::new("living/lamp");
let mut scene = TestScene::new();
let mut devices: HashMap<String, &mut dyn Fullfillment> = HashMap::new();
let mut devices: HashMap<String, &mut dyn GoogleHomeDevice> = HashMap::new();
devices.insert(nightstand.get_id(), &mut nightstand);
devices.insert(lamp.get_id(), &mut lamp);
devices.insert(scene.get_id(), &mut scene);
@ -332,7 +332,7 @@ mod tests {
let mut nightstand = TestOutlet::new("bedroom/nightstand");
let mut lamp = TestOutlet::new("living/lamp");
let mut scene = TestScene::new();
let mut devices: HashMap<String, &mut dyn Fullfillment> = HashMap::new();
let mut devices: HashMap<String, &mut dyn GoogleHomeDevice> = HashMap::new();
devices.insert(nightstand.get_id(), &mut nightstand);
devices.insert(lamp.get_id(), &mut lamp);
devices.insert(scene.get_id(), &mut scene);

View File

@ -11,5 +11,5 @@ pub mod errors;
mod attributes;
pub use fullfillment::GoogleHome;
pub use device::Fullfillment;
pub use request::Request;
pub use device::GoogleHomeDevice;

View File

@ -3,7 +3,6 @@ pub mod query;
pub mod execute;
use serde::Serialize;
use serde_with::skip_serializing_none;
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
@ -26,9 +25,9 @@ pub enum ResponsePayload {
Execute(execute::Payload),
}
#[skip_serializing_none]
#[derive(Debug, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct State {
#[serde(skip_serializing_if = "Option::is_none")]
pub on: Option<bool>,
}

View File

@ -1,13 +1,13 @@
use serde::Serialize;
use serde_with::skip_serializing_none;
use crate::{response::State, errors::ErrorCode};
#[skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Payload {
#[serde(skip_serializing_if = "Option::is_none")]
pub error_code: Option<ErrorCode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub debug_string: Option<String>,
commands: Vec<Command>,
}
@ -24,14 +24,15 @@ impl Payload {
}
}
#[skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Command {
#[serde(skip_serializing_if = "Option::is_none")]
pub error_code: Option<ErrorCode>,
ids: Vec<String>,
status: Status,
#[serde(skip_serializing_if = "Option::is_none")]
pub states: Option<States>,
}

View File

@ -1,15 +1,15 @@
use std::collections::HashMap;
use serde::Serialize;
use serde_with::skip_serializing_none;
use crate::{response::State, errors::ErrorCode};
#[skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Payload {
#[serde(skip_serializing_if = "Option::is_none")]
pub error_code: Option<ErrorCode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub debug_string: Option<String>,
pub devices: HashMap<String, Device>,
}
@ -33,12 +33,12 @@ pub enum Status {
Error,
}
#[skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Device {
online: bool,
status: Status,
#[serde(skip_serializing_if = "Option::is_none")]
error_code: Option<ErrorCode>,
#[serde(flatten)]

View File

@ -1,5 +1,4 @@
use serde::Serialize;
use serde_with::skip_serializing_none;
use crate::attributes::Attributes;
use crate::device;
@ -7,12 +6,13 @@ use crate::errors::ErrorCode;
use crate::types::Type;
use crate::traits::Trait;
#[skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Payload {
agent_user_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_code: Option<ErrorCode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub debug_string: Option<String>,
pub devices: Vec<Device>,
}
@ -27,7 +27,6 @@ impl Payload {
}
}
#[skip_serializing_none]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Device {
@ -37,8 +36,11 @@ pub struct Device {
pub traits: Vec<Trait>,
pub name: device::Name,
pub will_report_state: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub notification_supported_by_agent: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub room_hint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_info: Option<device::Info>,
pub attributes: Attributes,
}

View File

@ -6,20 +6,22 @@ pub use self::test_outlet::TestOutlet;
use std::collections::HashMap;
use google_home::{Fullfillment, traits::OnOff};
use google_home::{GoogleHomeDevice, traits::OnOff};
use crate::mqtt::Listener;
impl_cast::impl_cast!(Device, Listener);
impl_cast::impl_cast!(Device, Fullfillment);
impl_cast::impl_cast!(Device, GoogleHomeDevice);
impl_cast::impl_cast!(Device, OnOff);
pub trait Device: Sync + Send + AsFullfillment + AsListener + AsOnOff {
pub trait Device: AsGoogleHomeDevice + AsListener + AsOnOff {
fn get_id(&self) -> String;
}
// @TODO Add an inner type that we can wrap with Arc<RwLock<>> to make this type a little bit nicer
// to work with
pub struct Devices {
devices: HashMap<String, Box<dyn Device>>,
devices: HashMap<String, Box<dyn Device + Sync + Send>>,
}
macro_rules! get_cast {
@ -44,12 +46,12 @@ impl Devices {
Self { devices: HashMap::new() }
}
pub fn add_device<T: Device + 'static>(&mut self, device: T) {
pub fn add_device<T: Device + Sync + Send + 'static>(&mut self, device: T) {
self.devices.insert(device.get_id(), Box::new(device));
}
get_cast!(Listener);
get_cast!(Fullfillment);
get_cast!(GoogleHomeDevice);
get_cast!(OnOff);
pub fn get_device(&mut self, name: &str) -> Option<&mut dyn Device> {

View File

@ -1,7 +1,10 @@
use pollster::FutureExt as _;
use google_home::errors::ErrorCode;
use google_home::{GoogleHomeDevice, device, types::Type, traits};
use rumqttc::{Client, Publish};
use rumqttc::{AsyncClient, Publish};
use serde::{Deserialize, Serialize};
use log::debug;
use crate::devices::Device;
use crate::mqtt::Listener;
@ -10,13 +13,13 @@ use crate::zigbee::Zigbee;
pub struct IkeaOutlet {
name: String,
zigbee: Zigbee,
client: Client,
client: AsyncClient,
last_known_state: bool,
}
impl IkeaOutlet {
pub fn new(name: String, zigbee: Zigbee, mut client: Client) -> Self {
client.subscribe(zigbee.get_topic(), rumqttc::QoS::AtLeastOnce).unwrap();
pub fn new(name: String, zigbee: Zigbee, client: AsyncClient) -> Self {
client.subscribe(zigbee.get_topic(), rumqttc::QoS::AtLeastOnce).block_on().unwrap();
Self{ name, zigbee, client, last_known_state: false }
}
}
@ -51,16 +54,16 @@ impl Listener for IkeaOutlet {
if message.topic == self.zigbee.get_topic() {
let state = StateMessage::from(message);
print!("Updating state: {} => ", self.last_known_state);
self.last_known_state = state.state == "ON";
println!("{}", self.last_known_state);
let new_state = state.state == "ON";
debug!("Updating state: {} => {}", self.last_known_state, new_state);
self.last_known_state = new_state;
}
}
}
impl GoogleHomeDevice for IkeaOutlet {
fn get_device_type(&self) -> Type {
Type::Outlet
Type::Kettle
}
fn get_device_name(&self) -> device::Name {
@ -91,8 +94,10 @@ impl traits::OnOff for IkeaOutlet {
}
};
// @TODO Handle potential error here
self.client.publish(topic + "/set", rumqttc::QoS::AtLeastOnce, false, serde_json::to_string(&message).unwrap()).unwrap();
// @TODO Handle potential errors here
// @NOTE We are blocking here, ideally this function would just be async, however that is
// currently not really possible
self.client.publish(topic + "/set", rumqttc::QoS::AtLeastOnce, false, serde_json::to_string(&message).unwrap()).block_on().unwrap();
Ok(())
}

View File

@ -1,3 +1,5 @@
use log::debug;
use google_home::{errors::ErrorCode, traits};
use super::Device;
@ -24,7 +26,7 @@ impl traits::OnOff for TestOutlet {
}
fn set_on(&mut self, on: bool) -> Result<(), ErrorCode> {
println!("Setting on: {on}");
debug!("Setting on: {on}");
self.on = on;
Ok(())
}

View File

@ -1,90 +1,86 @@
use std::{time::Duration, sync::{Arc, RwLock}, process::exit, thread};
use std::{time::Duration, sync::{Arc, RwLock}, process::exit, net::SocketAddr};
use warp::Filter;
use rumqttc::{MqttOptions, Transport, AsyncClient};
use dotenv::dotenv;
use env_logger::Builder;
use log::{error, info, LevelFilter};
use automation::{devices::{Devices, IkeaOutlet, TestOutlet}, zigbee::Zigbee, mqtt::Notifier};
use google_home::GoogleHome;
use rumqttc::{MqttOptions, Transport, Client};
use google_home::{GoogleHome, Request};
fn get_required_env(name: &str) -> String {
match std::env::var(name) {
Ok(value) => value,
_ => {
eprintln!("Environment variable ${name} is not set!");
error!("Environment variable ${name} is not set!");
exit(-1);
}
}
}
fn main() {
#[tokio::main]
async fn main() {
// Setup logger
Builder::new()
.filter_module("automation", LevelFilter::Info)
.parse_default_env()
.init();
// Load dotfiles
dotenv().ok();
info!("Starting automation_rs...");
// Create device holder
// @TODO Make this nices to work with, we devices.rs
let devices = Arc::new(RwLock::new(Devices::new()));
// Setup MQTT
let mut mqttoptions = MqttOptions::new("rust-test", get_required_env("MQTT_HOST"), 8883);
mqttoptions.set_credentials(get_required_env("MQTT_USERNAME"), get_required_env("MQTT_PASSWORD"));
mqttoptions.set_keep_alive(Duration::from_secs(5));
mqttoptions.set_transport(Transport::tls_with_default_config());
let (client, connection) = Client::new(mqttoptions, 10);
// Create device holder
let devices = Arc::new(RwLock::new(Devices::new()));
// Create a notifier and move it to a new thread
// @TODO Maybe rename this to make it clear it has to do with mqtt
let mut notifier = Notifier::new();
let (client, eventloop) = AsyncClient::new(mqttoptions, 10);
notifier.add_listener(Arc::downgrade(&devices));
tokio::spawn(async move {
info!("Connecting to MQTT broker");
notifier.start(eventloop).await;
todo!("Error in MQTT (most likely lost connection to mqtt server), we need to handle these errors!");
});
// @TODO Load these from a config
// Create a new device and add it to the holder
devices.write().unwrap().add_device(IkeaOutlet::new("Kettle".into(), Zigbee::new("kitchen/kettle", "zigbee2mqtt/kitchen/kettle"), client.clone()));
devices.write().unwrap().add_device(TestOutlet::new());
{
for (_, d) in devices.write().unwrap().as_on_offs().iter_mut() {
d.set_on(false).unwrap();
}
}
// Google Home fullfillments
let fullfillment_google_home = warp::path("google_home")
.and(warp::post())
.and(warp::body::json())
.map(move |request: Request| {
// @TODO Verify that we are actually logged in
// Might also be smart to get the username from here
let gc = GoogleHome::new("Dreaded_X");
let result = gc.handle_request(request, &mut devices.write().unwrap().as_google_home_devices()).unwrap();
let ptr = Arc::downgrade(&devices);
{
let mut notifier = Notifier::new();
notifier.add_listener(ptr);
notifier.start(connection);
}
warp::reply::json(&result)
});
// Google Home test
let gc = GoogleHome::new("Dreaded_X");
let json = r#"{
"requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf",
"inputs": [
{
"intent": "action.devices.EXECUTE",
"payload": {
"commands": [
{
"devices": [
{
"id": "kitchen/kettle"
},
{
"id": "test_device"
}
],
"execution": [
{
"command": "action.devices.commands.OnOff",
"params": {
"on": false
}
}
]
}
]
}
}
]
}"#;
let request = serde_json::from_str(json).unwrap();
let mut binding = devices.write().unwrap();
let mut ghd = binding.as_fullfillments();
// Combine all fullfillments together
let fullfillment = warp::path("fullfillment").and(fullfillment_google_home);
let response = gc.handle_request(request, &mut ghd).unwrap();
// Combine all routes together
let routes = fullfillment;
println!("{response:?}");
// Start the web server
let addr: SocketAddr = ([127, 0, 0, 1], 7878).into();
info!("Server started on http://{addr}");
warp::serve(routes)
.run(addr)
.await;
}

View File

@ -1,13 +1,15 @@
use std::sync::{Weak, RwLock};
use log::error;
use rumqttc::{Publish, Connection, Event, Incoming};
use rumqttc::{Publish, Event, Incoming, EventLoop};
use log::trace;
pub trait Listener: Sync + Send {
pub trait Listener {
fn notify(&mut self, message: &Publish);
}
pub struct Notifier {
listeners: Vec<Weak<RwLock<dyn Listener>>>,
listeners: Vec<Weak<RwLock<dyn Listener + Sync + Send>>>,
}
impl Notifier {
@ -26,20 +28,21 @@ impl Notifier {
})
}
pub fn add_listener<T: Listener + 'static>(&mut self, listener: Weak<RwLock<T>>) {
pub fn add_listener<T: Listener + Sync + Send + 'static>(&mut self, listener: Weak<RwLock<T>>) {
self.listeners.push(listener);
}
pub fn start(&mut self, mut connection: Connection) {
for notification in connection.iter() {
pub async fn start(&mut self, mut eventloop: EventLoop) {
loop {
let notification = eventloop.poll().await;
match notification {
Ok(Event::Incoming(Incoming::Publish(p))) => {
println!("{:?}", p);
trace!("{:?}", p);
self.notify(p);
},
Ok(..) => continue,
Err(err) => {
eprintln!("{}", err);
error!("{}", err);
break
},
}