diff --git a/.gitignore b/.gitignore index 3cc8315..832acf9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target /test-config +/client/dist diff --git a/Cargo.lock b/Cargo.lock index d23639d..134fbe5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,12 +89,66 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "anyhow" +version = "1.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d19de80eff169429ac1e9f48fffb163916b448a44e8e046186232046d9e1f9" + [[package]] name = "anymap2" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c" +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "async-trait" +version = "0.1.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf6721fb0140e4f897002dd086c06f6c27775df19cfe1fccb21181a48fd2c98" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +dependencies = [ + "bytemuck", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -131,6 +185,12 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "binascii" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" + [[package]] name = "bincode" version = "1.3.3" @@ -176,6 +236,12 @@ version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +[[package]] +name = "bytemuck" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" + [[package]] name = "bytes" version = "1.5.0" @@ -256,6 +322,9 @@ checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" name = "client" version = "0.1.0" dependencies = [ + "reqwasm", + "shared-types", + "wasm-bindgen-futures", "yew", ] @@ -273,7 +342,7 @@ checksum = "79cff32df5cfea75e6484eeff0b4e48ad3977fb6582676a7862b3590dddc7a87" dependencies = [ "serde", "serde_json", - "yansi", + "yansi 0.5.1", ] [[package]] @@ -303,13 +372,24 @@ dependencies = [ "version_check", ] +[[package]] +name = "cookie" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd91cf61412820176e137621345ee43b3f4423e589e7ae4e50d601d93e35ef8" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "cookie_store" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d606d0fba62e13cf04db20536c05cb7f13673c161cb47a47a82b9b9e7d3f1daa" dependencies = [ - "cookie", + "cookie 0.16.2", "idna 0.2.3", "log", "publicsuffix", @@ -377,6 +457,39 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "devise" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6eacefd3f541c66fc61433d65e54e0e46e0a029a819a7dbbc7a7b489e8a85f8" +dependencies = [ + "devise_codegen", + "devise_core", +] + +[[package]] +name = "devise_codegen" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8cf4b8dd484ede80fd5c547592c46c3745a617c8af278e2b72bea86b2dfed6" +dependencies = [ + "devise_core", + "quote", +] + +[[package]] +name = "devise_core" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" +dependencies = [ + "bitflags 2.4.1", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.43", +] + [[package]] name = "digest" version = "0.10.7" @@ -387,6 +500,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -418,6 +537,20 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +[[package]] +name = "figment" +version = "0.10.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "649f3e5d826594057e9a519626304d8da859ea8a0b18ce99500c586b8d45faee" +dependencies = [ + "atomic 0.6.0", + "pear", + "serde", + "toml", + "uncased", + "version_check", +] + [[package]] name = "fnv" version = "1.0.7" @@ -525,6 +658,19 @@ dependencies = [ "slab", ] +[[package]] +name = "generator" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -554,6 +700,12 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "gloo" version = "0.8.1" @@ -715,6 +867,26 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-net" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2899cb1a13be9020b010967adc6b2a8a343b6f1428b90238c9d53ca24decc6db" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils 0.1.7", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "gloo-net" version = "0.3.1" @@ -1097,6 +1269,25 @@ dependencies = [ "syn 2.0.43", ] +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "indexmap" version = "2.1.0" @@ -1105,8 +1296,15 @@ checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown", + "serde", ] +[[package]] +name = "inlinable_string" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" + [[package]] name = "ipnet" version = "2.9.0" @@ -1179,6 +1377,21 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1258,6 +1471,26 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "multer" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http", + "httparse", + "log", + "memchr", + "mime", + "spin", + "tokio", + "tokio-util", + "version_check", +] + [[package]] name = "native-tls" version = "0.2.11" @@ -1399,6 +1632,29 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "pear" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ccca0f6c17acc81df8e242ed473ec144cbf5c98037e69aa6d144780aad103c8" +dependencies = [ + "inlinable_string", + "pear_codegen", + "yansi 1.0.0-rc.1", +] + +[[package]] +name = "pear_codegen" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e22670e8eb757cff11d6c199ca7b987f352f0346e0be4dd23869ec72cb53c77" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.43", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1494,7 +1750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" dependencies = [ "once_cell", - "toml_edit", + "toml_edit 0.19.15", ] [[package]] @@ -1530,6 +1786,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", + "version_check", + "yansi 1.0.0-rc.1", +] + [[package]] name = "prokio" version = "0.1.0" @@ -1611,6 +1880,26 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "ref-cast" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53313ec9f12686aeeffb43462c3ac77aa25f590a5f630eb2cde0de59417b29c7" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2566c4bf6845f2c2e83b27043c3f5dfcd5ba8f2937d6c00dc009bfb51a079dc4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.43", +] + [[package]] name = "regex" version = "1.10.2" @@ -1655,6 +1944,15 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +[[package]] +name = "reqwasm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b89870d729c501fa7a68c43bf4d938bbb3a8c156d333d90faa0e8b3e3212fb" +dependencies = [ + "gloo-net 0.1.0", +] + [[package]] name = "reqwest" version = "0.11.23" @@ -1663,7 +1961,7 @@ checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" dependencies = [ "base64", "bytes", - "cookie", + "cookie 0.16.2", "cookie_store", "encoding_rs", "futures-core", @@ -1714,6 +2012,88 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rocket" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e7bb57ccb26670d73b6a47396c83139447b9e7878cab627fdfe9ea8da489150" +dependencies = [ + "async-stream", + "async-trait", + "atomic 0.5.3", + "binascii", + "bytes", + "either", + "figment", + "futures", + "indexmap", + "log", + "memchr", + "multer", + "num_cpus", + "parking_lot", + "pin-project-lite", + "rand", + "ref-cast", + "rocket_codegen", + "rocket_http", + "serde", + "serde_json", + "state", + "tempfile", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "ubyte", + "version_check", + "yansi 1.0.0-rc.1", +] + +[[package]] +name = "rocket_codegen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2238066abf75f21be6cd7dc1a09d5414a671f4246e384e49fe3f8a4936bd04c" +dependencies = [ + "devise", + "glob", + "indexmap", + "proc-macro2", + "quote", + "rocket_http", + "syn 2.0.43", + "unicode-xid", + "version_check", +] + +[[package]] +name = "rocket_http" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a1663694d059fe5f943ea5481363e48050acedd241d46deb2e27f71110389e" +dependencies = [ + "cookie 0.18.0", + "either", + "futures", + "http", + "hyper", + "indexmap", + "log", + "memchr", + "pear", + "percent-encoding", + "pin-project-lite", + "ref-cast", + "serde", + "smallvec", + "stable-pattern", + "state", + "time", + "tokio", + "uncased", +] + [[package]] name = "ron" version = "0.8.1" @@ -1837,6 +2217,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -1935,6 +2321,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1967,6 +2362,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shared-types" +version = "0.1.0" +dependencies = [ + "serde", + "teslatte", + "thiserror", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2013,6 +2417,24 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +[[package]] +name = "stable-pattern" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" +dependencies = [ + "memchr", +] + +[[package]] +name = "state" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" +dependencies = [ + "loom", +] + [[package]] name = "strsim" version = "0.10.0" @@ -2145,9 +2567,13 @@ dependencies = [ name = "tesla-charge-controller" version = "0.1.0" dependencies = [ + "anyhow", "clap", + "include_dir", + "rocket", "ron", "serde", + "shared-types", "teslatte", "thiserror", "tokio", @@ -2359,11 +2785,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.21.0", +] + [[package]] name = "toml_datetime" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -2376,6 +2817,19 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_edit" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -2455,6 +2909,25 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ubyte" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea" +dependencies = [ + "serde", +] + +[[package]] +name = "uncased" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" +dependencies = [ + "serde", + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.14" @@ -2488,6 +2961,12 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "untrusted" version = "0.9.0" @@ -2557,6 +3036,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" dependencies = [ "cfg-if", + "serde", + "serde_json", "wasm-bindgen-macro", ] @@ -2654,6 +3135,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-core" version = "0.51.1" @@ -2820,6 +3310,15 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +[[package]] +name = "yansi" +version = "1.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377" +dependencies = [ + "is-terminal", +] + [[package]] name = "yew" version = "0.21.0" diff --git a/Cargo.toml b/Cargo.toml index ef1368c..71cc446 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,10 @@ [workspace] -members = ["server", "client"] +members = ["server", "client", "shared-types"] default-members = ["server"] resolver = "2" + +[workspace.dependencies] +shared-types = { path = "./shared-types" } +serde = { version = "1.0", features = ["derive"] } +teslatte = { path = "./vendored/teslatte" } +thiserror = "1.0" diff --git a/client/Cargo.toml b/client/Cargo.toml index 0954170..feb4c24 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -6,4 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +shared-types = { workspace = true } yew = { version = "0.21", features = ["csr"] } +reqwasm = "0.5" +wasm-bindgen-futures = "0.4" diff --git a/client/index.html b/client/index.html index 91450e4..4e443a5 100644 --- a/client/index.html +++ b/client/index.html @@ -3,7 +3,7 @@ - Yew App + Tesla Charge Control diff --git a/client/src/main.rs b/client/src/main.rs index 3c55d8c..880050a 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,20 +1,49 @@ use yew::prelude::*; +const API_URL: &str = if cfg!(debug_assertions) { + "http://localhost:8000" +} else { + "" +}; + #[function_component] fn App() -> Html { - let counter = use_state(|| 0); + let counter = use_state(Vec::new); let onclick = { let counter = counter.clone(); move |_| { - let value = *counter + 1; - counter.set(value); + let counter = counter.clone(); + wasm_bindgen_futures::spawn_local(async move { + counter.set( + match reqwasm::http::Request::get(&format!("{API_URL}/charge-state")) + .send() + .await + { + Ok(response) => match response.json::().await { + Ok(v) => vec![ + format!("Battery {}%", v.battery_level), + format!("Range: {:.1}km", v.range_km()), + format!("Charging at {} amps", v.charge_amps), + ], + Err(e) => vec![format!("error getting text: {e:#?}")], + }, + Err(e) => vec![format!("request error: {e:#?}")], + }, + ); + }) } }; - + let counter = as Clone>::clone(&counter); + let text_elements: Vec = counter + .iter() + .map(|text| { + html! {

{text}

} + }) + .collect(); html! {
- -

{ *counter }

+ + {text_elements}
} } diff --git a/server/Cargo.toml b/server/Cargo.toml index 8aad05c..082d23b 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -24,9 +24,13 @@ assets = [ ] [dependencies] +shared-types = { workspace = true, features = ["teslatte"] } clap = { version = "4.0", features = ["derive"] } ron = "0.8" -serde = { version = "1.0", features = ["derive"] } +serde = { workspace = true } tokio = { version = "1.35.1", features = ["full"] } -teslatte = { path = "../vendored/teslatte" } -thiserror = "1.0" +teslatte = { workspace = true } +thiserror = { workspace = true } +rocket = { version = "0.5", features = ["json"] } +anyhow = "1.0" +include_dir = "0.7" diff --git a/server/Rocket.toml b/server/Rocket.toml new file mode 100644 index 0000000..914f96f --- /dev/null +++ b/server/Rocket.toml @@ -0,0 +1,3 @@ +[default.shutdown] +ctrlc = false +signals = ["term", "hup"] diff --git a/server/src/config.rs b/server/src/config.rs index 575951b..c222ec8 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -2,15 +2,22 @@ use std::time::Duration; use serde::{Deserialize, Serialize}; +use crate::Coords; + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Config { - watch_interval: Duration, + pub watch_interval: Duration, + pub coords: Coords, } impl Default for Config { fn default() -> Self { Self { watch_interval: Duration::from_secs(60), + coords: Coords { + latitude: 0., + longitude: 0., + }, } } } diff --git a/server/src/errors.rs b/server/src/errors.rs index 644d0c6..0fbac0f 100644 --- a/server/src/errors.rs +++ b/server/src/errors.rs @@ -16,6 +16,7 @@ impl SaveError { } } } + #[derive(Error, Debug)] pub enum AuthLoadError { #[error("stdio error")] @@ -38,3 +39,15 @@ impl AuthLoadError { } } } + +#[derive(Error, Debug)] +pub enum RequestError { + #[error("stdio error")] + StdIo(#[from] std::io::Error), + #[error("ron - spanned error")] + RonSpanned(#[from] ron::error::SpannedError), + #[error("teslatte error")] + Teslatte(#[from] teslatte::error::TeslatteError), + #[error("save error")] + Save(#[from] SaveError), +} diff --git a/server/src/main.rs b/server/src/main.rs index 078d9ff..bb9e840 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,15 +1,21 @@ +#[macro_use] +extern crate rocket; + +use anyhow::Result; use clap::{Parser, Subcommand}; + use serde::{Deserialize, Serialize}; -use std::{path::PathBuf, time::Duration}; +use std::path::PathBuf; use teslatte::{ auth::{AccessToken, RefreshToken}, - FleetApi, FleetVehicleApi, + FleetApi, }; use crate::{config::Config, errors::*}; mod config; mod errors; +mod server; #[derive(Parser, Debug, Clone)] #[clap(author, version, about, long_about = None)] @@ -31,8 +37,9 @@ enum Commands { #[tokio::main] async fn main() { let args = Args::parse(); + let auth_path = args.config_dir.join("auth"); - // let config_path = args.config_dir.join("config"); + let config_path = args.config_dir.join("config"); match args.command { Commands::GenerateConfig => { @@ -43,18 +50,27 @@ async fn main() { } Commands::Watch => match get_auth(auth_path).await { Ok(api) => { + let config: Config = + ron::from_str(&std::fs::read_to_string(&config_path).unwrap()).unwrap(); let products = api.products().await; match products { Ok(res) => match res.first() { Some(teslatte::products::Product::Vehicle(vehicle)) => { - api.wake_up(&vehicle.vin).await.unwrap(); - loop { - match api.flash_lights(&vehicle.vin).await { - Ok(_r) => println!("flashed"), - Err(e) => println!("error: {e:#?}"), - } - std::thread::sleep(Duration::from_secs(10)); - } + // match api.wake_up(&vehicle.vin).await { + // Ok(_r) => { + // println!("woke up! response: {_r:#?}"); + // } + // Err(e) => { + // println!("error waking up: {e:#?}"); + // } + // } + + server::launch_server(server::ServerState { + config, + api, + vehicle: vehicle.clone(), + }) + .await; } _ => println!("No first item"), }, @@ -66,15 +82,18 @@ async fn main() { } } -#[derive(Debug)] -pub enum MaybePostResponse { - None, - PostResponse(teslatte::PostResponse), +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +pub struct Coords { + pub latitude: f64, + pub longitude: f64, } -impl From for MaybePostResponse { - fn from(value: teslatte::PostResponse) -> Self { - MaybePostResponse::PostResponse(value) +const COORD_PRECISION: f64 = 0.001; + +impl Coords { + fn overlaps(&self, other: &Coords) -> bool { + (self.latitude - other.latitude).abs() < COORD_PRECISION + && (self.longitude - other.longitude).abs() < COORD_PRECISION } } @@ -84,6 +103,7 @@ async fn get_auth(auth_path: PathBuf) -> Result { api.refresh().await?; println!("Refreshed auth key"); save_key(auth_path, &api)?; + // api.print_responses = teslatte::PrintResponses::Pretty; Ok(api) } diff --git a/server/src/server/mod.rs b/server/src/server/mod.rs new file mode 100644 index 0000000..278bc2b --- /dev/null +++ b/server/src/server/mod.rs @@ -0,0 +1,172 @@ +use std::path::PathBuf; + +use anyhow::{Context, Result}; +use include_dir::{include_dir, Dir}; +use rocket::{ + fairing::{Fairing, Info, Kind}, + http::{ContentType, Header, Status}, + outcome::IntoOutcome, + response::Responder, + route::{Handler, Outcome}, + serde::json::Json, + Data, Request, Response, State, +}; +use teslatte::{ + vehicles::{Endpoint, GetVehicleData, VehicleData}, + FleetApi, FleetVehicleApi, +}; + +use crate::{config::Config, Coords}; + +pub struct ServerState { + pub config: Config, + pub api: FleetApi, + pub vehicle: Box, +} + +pub async fn launch_server(state: ServerState) { + let _ = rocket(state).launch().await; +} + +fn rocket(state: ServerState) -> rocket::Rocket { + rocket::build() + .attach(Cors) + .manage(state) + .mount("/", UiStatic {}) + .mount("/", routes![home, charge_state,]) +} + +static UI_DIR: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../client/dist"); + +#[derive(Clone, Copy, Debug)] +struct UiStatic {} + +impl From for Vec { + fn from(server: UiStatic) -> Self { + vec![rocket::Route::ranked( + None, + rocket::http::Method::Get, + "/", + server, + )] + } +} + +#[rocket::async_trait] +impl Handler for UiStatic { + async fn handle<'r>(&self, req: &'r Request<'_>, data: Data<'r>) -> Outcome<'r> { + use rocket::http::uri::fmt::Path; + + let path = req + .segments::>(0..) + .ok() + .and_then(|segments| segments.to_path_buf(true).ok()); + println!("path: {path:#?}"); + match path { + Some(p) => { + if p.as_os_str() == "" { + let index = UI_DIR.get_file("index.html").map(|v| RawHtml { + data: v.contents().to_vec(), + name: PathBuf::from("index.html"), + }); + + index.respond_to(req).or_forward((data, Status::NotFound)) + } else { + let file = UI_DIR.get_file(&p).map(|v| RawHtml { + data: v.contents().to_vec(), + name: p, + }); + file.respond_to(req).or_forward((data, Status::NotFound)) + } + } + None => Outcome::forward(data, Status::NotFound), + } + } +} + +struct RawHtml { + data: Vec, + name: PathBuf, +} + +impl<'r> Responder<'r, 'static> for RawHtml { + fn respond_to(self, request: &'r Request<'_>) -> rocket::response::Result<'static> { + let mut response = self.data.respond_to(request)?; + if let Some(ext) = self.name.extension() { + if let Some(ct) = ContentType::from_extension(&ext.to_string_lossy()) { + response.set_header(ct); + } + } + + Ok(response) + } +} + +#[get("/home")] +async fn home(state: &State) -> Option { + let coords = state.get_coords().await.ok()?; + Some(if coords.overlaps(&state.config.coords) { + String::from("At home") + } else { + String::from("Not home") + }) +} + +#[get("/charge-state")] +async fn charge_state(state: &State) -> Option> { + let charge_state = state.get_charge_state().await.ok()?; + Some(Json(charge_state)) +} + +impl ServerState { + async fn get_coords(&self) -> Result { + let vehicle_data = self + .api + .vehicle_data(&GetVehicleData { + vehicle_id: self.vehicle.id.clone(), + endpoints: vec![Endpoint::LocationData].into(), + }) + .await?; + let drive_state = vehicle_data.drive_state.context("no drive state")?; + let latitude = drive_state.latitude.context("no latitude")?; + let longitude = drive_state.longitude.context("no longitude")?; + Ok(Coords { + latitude, + longitude, + }) + } + + async fn get_charge_state(&self) -> Result { + let vehicle_data = self + .api + .vehicle_data(&GetVehicleData { + vehicle_id: self.vehicle.id.clone(), + endpoints: vec![Endpoint::ChargeState].into(), + }) + .await?; + let charge_state = vehicle_data.charge_state.context("no drive state")?; + Ok(charge_state.try_into()?) + } +} + +pub struct Cors; + +#[rocket::async_trait] +impl Fairing for Cors { + fn info(&self) -> Info { + Info { + name: "Add CORS headers to responses", + kind: Kind::Response, + } + } + + async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) { + response.set_header(Header::new("Access-Control-Allow-Origin", "*")); + response.set_header(Header::new( + "Access-Control-Allow-Methods", + "POST, GET, PATCH, OPTIONS", + )); + response.set_header(Header::new("Access-Control-Allow-Headers", "*")); + response.set_header(Header::new("Access-Control-Allow-Credentials", "true")); + } +} diff --git a/shared-types/Cargo.toml b/shared-types/Cargo.toml new file mode 100644 index 0000000..21be0d5 --- /dev/null +++ b/shared-types/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "shared-types" +version = "0.1.0" +edition = "2021" + +[features] +default = [] +teslatte = ["dep:teslatte"] + +[dependencies] +serde = { workspace = true } +teslatte = { workspace = true, optional = true } +thiserror = { workspace = true } diff --git a/shared-types/src/lib.rs b/shared-types/src/lib.rs new file mode 100644 index 0000000..f2f18c6 --- /dev/null +++ b/shared-types/src/lib.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; + +#[cfg(feature = "teslatte")] +mod teslatte_impls; + +#[derive(Clone, Copy, Serialize, Deserialize, Debug)] +pub struct ChargeState { + pub battery_level: i64, + pub battery_range: f64, + pub charge_amps: i64, + pub charge_current_request: i64, + pub charge_current_request_max: i64, + pub charge_enable_request: bool, +} + +impl ChargeState { + pub fn range_km(&self) -> f64 { + self.battery_range * 1.60934 + } +} diff --git a/shared-types/src/teslatte_impls.rs b/shared-types/src/teslatte_impls.rs new file mode 100644 index 0000000..275351d --- /dev/null +++ b/shared-types/src/teslatte_impls.rs @@ -0,0 +1,24 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ConvertError { + #[error("Error")] + Error, +} + +impl TryFrom for crate::ChargeState { + type Error = ConvertError; + + fn try_from( + value: teslatte::vehicles::ChargeState, + ) -> std::prelude::v1::Result { + Ok(crate::ChargeState { + battery_level: value.battery_level, + battery_range: value.battery_range, + charge_amps: value.charge_amps, + charge_current_request: value.charge_current_request, + charge_current_request_max: value.charge_current_request_max, + charge_enable_request: value.charge_enable_request, + }) + } +} diff --git a/vendored/teslatte b/vendored/teslatte index 22bfecd..d1c1488 160000 --- a/vendored/teslatte +++ b/vendored/teslatte @@ -1 +1 @@ -Subproject commit 22bfecd8138ecacfe171255244360917f77ddb00 +Subproject commit d1c1488d7d666563520f14bd023f9ff78e425860