commit
This commit is contained in:
parent
13f307d387
commit
3e129bd3d6
20 changed files with 1245 additions and 41 deletions
618
Cargo.lock
generated
618
Cargo.lock
generated
|
@ -149,6 +149,15 @@ dependencies = [
|
|||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.14.0"
|
||||
|
@ -161,6 +170,12 @@ version = "1.14.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.5.0"
|
||||
|
@ -317,6 +332,15 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.3.2"
|
||||
|
@ -326,6 +350,16 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.3"
|
||||
|
@ -361,6 +395,12 @@ dependencies = [
|
|||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
||||
|
||||
[[package]]
|
||||
name = "data-url"
|
||||
version = "0.3.1"
|
||||
|
@ -410,7 +450,6 @@ name = "deckster_mode"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"deckster_shared",
|
||||
"either",
|
||||
"im",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -454,6 +493,16 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.9.0"
|
||||
|
@ -466,6 +515,15 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-map"
|
||||
version = "3.0.0-beta.2"
|
||||
|
@ -556,6 +614,16 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.11"
|
||||
|
@ -566,6 +634,12 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.3"
|
||||
|
@ -646,12 +720,56 @@ dependencies = [
|
|||
"ttf-parser 0.20.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.30"
|
||||
|
@ -671,12 +789,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.11"
|
||||
|
@ -706,6 +836,25 @@ version = "0.28.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.1.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
|
@ -736,6 +885,71 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "home_assistant"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"deckster_mode",
|
||||
"env_logger",
|
||||
"futures-util",
|
||||
"log",
|
||||
"native-tls",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-tungstenite",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 0.2.12",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
|
@ -752,6 +966,43 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http 0.2.12",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.59"
|
||||
|
@ -781,6 +1032,16 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "im"
|
||||
version = "15.1.0"
|
||||
|
@ -839,6 +1100,12 @@ dependencies = [
|
|||
"mach2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
|
||||
|
||||
[[package]]
|
||||
name = "is_executable"
|
||||
version = "1.0.1"
|
||||
|
@ -951,6 +1218,12 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.11"
|
||||
|
@ -1014,6 +1287,12 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.1"
|
||||
|
@ -1053,6 +1332,24 @@ dependencies = [
|
|||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.4"
|
||||
|
@ -1109,12 +1406,50 @@ version = "1.19.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.64"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "3.5.0"
|
||||
|
@ -1222,6 +1557,12 @@ dependencies = [
|
|||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pico-args"
|
||||
version = "0.5.0"
|
||||
|
@ -1398,6 +1739,46 @@ version = "0.8.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http 0.2.12",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "resvg"
|
||||
version = "0.37.0"
|
||||
|
@ -1492,6 +1873,19 @@ dependencies = [
|
|||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
||||
dependencies = [
|
||||
"bitflags 2.4.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.10"
|
||||
|
@ -1665,9 +2059,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.113"
|
||||
version = "1.0.114"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
||||
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
@ -1693,6 +2087,18 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "3.4.0"
|
||||
|
@ -1741,6 +2147,17 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.7"
|
||||
|
@ -1936,6 +2353,12 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "sys-locale"
|
||||
version = "0.3.1"
|
||||
|
@ -1945,6 +2368,40 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.56"
|
||||
|
@ -2089,6 +2546,16 @@ dependencies = [
|
|||
"syn 2.0.48",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.24.1"
|
||||
|
@ -2099,6 +2566,45 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tungstenite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.8"
|
||||
|
@ -2133,6 +2639,12 @@ dependencies = [
|
|||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
|
@ -2174,6 +2686,12 @@ dependencies = [
|
|||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.19.2"
|
||||
|
@ -2186,6 +2704,26 @@ version = "0.20.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4"
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.21.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"data-encoding",
|
||||
"http 1.1.0",
|
||||
"httparse",
|
||||
"log",
|
||||
"native-tls",
|
||||
"rand",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"url",
|
||||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
|
@ -2231,6 +2769,15 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-properties"
|
||||
version = "0.1.0"
|
||||
|
@ -2261,6 +2808,18 @@ version = "0.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "usvg"
|
||||
version = "0.37.0"
|
||||
|
@ -2322,6 +2881,12 @@ dependencies = [
|
|||
"tiny-skia-path",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "utf-8"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
|
@ -2334,6 +2899,12 @@ version = "0.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.4"
|
||||
|
@ -2350,6 +2921,15 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||
dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
@ -2381,6 +2961,18 @@ dependencies = [
|
|||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.89"
|
||||
|
@ -2410,6 +3002,16 @@ version = "0.2.89"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "weezl"
|
||||
version = "0.1.7"
|
||||
|
@ -2597,6 +3199,16 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xmlparser"
|
||||
version = "0.13.6"
|
||||
|
|
|
@ -7,6 +7,5 @@ edition = "2021"
|
|||
deckster_shared = { path = "../deckster_shared" }
|
||||
thiserror = "1.0.56"
|
||||
im = "15.1.0"
|
||||
either = "1.9.0"
|
||||
serde = { version = "1.0.196", default-features = false }
|
||||
serde_json = "1.0.113"
|
||||
|
|
|
@ -2,7 +2,6 @@ use std::any::TypeId;
|
|||
use std::io;
|
||||
use std::io::BufRead;
|
||||
|
||||
use either::Either;
|
||||
use serde::de::DeserializeOwned;
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -24,41 +23,34 @@ pub trait DecksterHandler {
|
|||
fn handle(&mut self, event: HandlerEvent);
|
||||
}
|
||||
|
||||
enum Stage<I, H: DecksterHandler> {
|
||||
Initialization(I),
|
||||
Active(H),
|
||||
}
|
||||
|
||||
pub fn run<
|
||||
GlobalConfig: Clone + DeserializeOwned + 'static,
|
||||
KeyConfig: Clone + DeserializeOwned + 'static,
|
||||
KnobConfig: Clone + DeserializeOwned + 'static,
|
||||
H: DecksterHandler,
|
||||
I: FnOnce(InitialHandlerMessage<KeyConfig, KnobConfig>) -> Result<H, HandlerInitializationError>,
|
||||
I: FnOnce(InitialHandlerMessage<GlobalConfig, KeyConfig, KnobConfig>) -> Result<H, HandlerInitializationError>,
|
||||
>(
|
||||
init_handler: I,
|
||||
) -> Result<(), RunError> {
|
||||
let mut handler: Either<H, I> = Either::Right(init_handler);
|
||||
let mut stage: Stage<I, H> = Stage::Initialization(init_handler);
|
||||
|
||||
let requires_global_config = TypeId::of::<GlobalConfig>() != TypeId::of::<()>();
|
||||
let supports_keys = TypeId::of::<KeyConfig>() != TypeId::of::<()>();
|
||||
let supports_knobs = TypeId::of::<KnobConfig>() != TypeId::of::<()>();
|
||||
|
||||
let handle = io::stdin().lock();
|
||||
for line in handle.lines() {
|
||||
let line = line?;
|
||||
let line = line.map_err(RunError::LineIo)?;
|
||||
|
||||
match handler {
|
||||
Either::Left(mut h) => {
|
||||
let event: HandlerEvent = serde_json::from_str(&line).map_err(|e| RunError::LineDeserialization {
|
||||
line,
|
||||
description: e.to_string(),
|
||||
})?;
|
||||
|
||||
let should_stop = matches!(event, HandlerEvent::Stop);
|
||||
|
||||
h.handle(event);
|
||||
handler = Either::Left(h);
|
||||
|
||||
if should_stop {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Either::Right(init_handler) => {
|
||||
let initial_message = serde_json::from_str::<InitialHandlerMessage<KeyConfig, KnobConfig>>(&line);
|
||||
match stage {
|
||||
Stage::Initialization(init_handler) => {
|
||||
// TODO: Serialize the global config and each key/knob config separately for more specific error messages.
|
||||
let initial_message = serde_json::from_str::<InitialHandlerMessage<GlobalConfig, KeyConfig, KnobConfig>>(&line);
|
||||
|
||||
match initial_message {
|
||||
Ok(initial_message) => match init_handler(initial_message) {
|
||||
|
@ -67,7 +59,7 @@ pub fn run<
|
|||
"{}",
|
||||
serde_json::to_string(&HandlerInitializationResultMessage::Ready).expect("serialization of a known value always works")
|
||||
);
|
||||
handler = Either::Left(h)
|
||||
stage = Stage::Active(h)
|
||||
}
|
||||
Err(error) => {
|
||||
println!(
|
||||
|
@ -82,6 +74,7 @@ pub fn run<
|
|||
"{}",
|
||||
serde_json::to_string(&HandlerInitializationResultMessage::Error {
|
||||
error: HandlerInitializationError::InvalidConfig {
|
||||
requires_global_config,
|
||||
supports_keys,
|
||||
supports_knobs,
|
||||
message: err.to_string().into_boxed_str(),
|
||||
|
@ -93,6 +86,21 @@ pub fn run<
|
|||
}
|
||||
}
|
||||
}
|
||||
Stage::Active(mut h) => {
|
||||
let event: HandlerEvent = serde_json::from_str(&line).map_err(|e| RunError::LineDeserialization {
|
||||
line,
|
||||
description: e.to_string(),
|
||||
})?;
|
||||
|
||||
let should_stop = matches!(event, HandlerEvent::Stop);
|
||||
|
||||
h.handle(event);
|
||||
stage = Stage::Active(h);
|
||||
|
||||
if should_stop {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,7 +90,8 @@ pub enum HandlerCommand {
|
|||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
|
||||
pub struct InitialHandlerMessage<KeyConfig: Clone, KnobConfig: Clone> {
|
||||
pub struct InitialHandlerMessage<GlobalConfig: Clone, KeyConfig: Clone, KnobConfig: Clone> {
|
||||
pub global_config: GlobalConfig,
|
||||
pub key_configs: HashMap<KeyPath, KeyConfig>,
|
||||
pub knob_configs: HashMap<KnobPath, KnobConfig>,
|
||||
}
|
||||
|
@ -106,6 +107,7 @@ pub enum HandlerInitializationResultMessage {
|
|||
pub enum HandlerInitializationError {
|
||||
#[error("The provided handler config is invalid: {message}")]
|
||||
InvalidConfig {
|
||||
requires_global_config: bool,
|
||||
supports_keys: bool,
|
||||
supports_knobs: bool,
|
||||
message: Box<str>,
|
||||
|
|
|
@ -3,6 +3,11 @@ active_button_color = "#eeffff"
|
|||
label_font_family = "Inter"
|
||||
buttons = {}
|
||||
|
||||
[handlers.home_assistant]
|
||||
base_url = "https://ha.mosv.de/"
|
||||
token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI5YjBjNWRjMjY3ODA0YzI4YjI3Y2VkMGFiZjVkYzQ5ZCIsImlhdCI6MTcwOTY1MDk1NSwiZXhwIjoyMDI1MDEwOTU1fQ.YxYgbnZnT5d0hwsxqqnqgb8IrtBRlCoCked7VqFR0wM"
|
||||
accept_invalid_certs = true
|
||||
|
||||
[initial]
|
||||
key_page = "default"
|
||||
knob_page = "default"
|
||||
|
|
|
@ -53,8 +53,7 @@ config.style.alarm2.label = "00:00"
|
|||
icon = "@ph/computer-tower"
|
||||
label = "Gaming PC"
|
||||
|
||||
host = "moira"
|
||||
handler = "home-assistant"
|
||||
config.mode = "switch"
|
||||
config.name = "switch.mwin"
|
||||
handler = "home_assistant"
|
||||
config.mode = "toggle"
|
||||
config.entity_id = "light.moritz_zimmer_stehlampe"
|
||||
config.style.on.icon = "@ph/computer-tower[color=#58fc11]"
|
20
handlers/home_assistant/Cargo.toml
Normal file
20
handlers/home_assistant/Cargo.toml
Normal file
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "home_assistant"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
deckster_mode = { path = "../../crates/deckster_mode" }
|
||||
clap = { version = "4.4.18", features = ["derive"] }
|
||||
color-eyre = "0.6.2"
|
||||
env_logger = "0.11.1"
|
||||
log = "0.4.20"
|
||||
tokio = { version = "1.35.1", features = ["macros", "parking_lot", "rt", "sync"] }
|
||||
serde = { version = "1.0.196", features = ["derive"] }
|
||||
serde_json = "1.0.114"
|
||||
reqwest = "0.11.24"
|
||||
url = { version = "2.5.0", features = ["serde"] }
|
||||
tokio-tungstenite = { version = "0.21.0", features = ["native-tls"] }
|
||||
tokio-stream = "0.1.14"
|
||||
futures-util = "0.3.30"
|
||||
native-tls = "0.2.11"
|
51
handlers/home_assistant/src/config.rs
Normal file
51
handlers/home_assistant/src/config.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use deckster_mode::shared::state::KeyStyleByStateMap;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct GlobalConfig {
|
||||
pub base_url: Url,
|
||||
pub token: Box<str>,
|
||||
#[serde(default)]
|
||||
pub accept_invalid_certs: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct KeyConfig {
|
||||
pub disconnected_state: Option<Box<str>>,
|
||||
#[serde(flatten)]
|
||||
pub mode: KeyMode,
|
||||
pub style: KeyStyleByStateMap<Box<str>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(tag = "mode", rename_all = "kebab-case")]
|
||||
pub enum KeyMode {
|
||||
Toggle { entity_id: Box<str> },
|
||||
Button { state_entity_id: Box<str>, button_entity_id: Box<str> },
|
||||
}
|
||||
|
||||
impl KeyMode {
|
||||
pub fn state_entity_id(&self) -> &Box<str> {
|
||||
match &self {
|
||||
KeyMode::Toggle { entity_id, .. } => entity_id,
|
||||
KeyMode::Button { state_entity_id, .. } => state_entity_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct KnobConfig {
|
||||
pub(crate) entity_id: Box<str>,
|
||||
pub disconnected_state: Option<Box<str>>,
|
||||
#[serde(flatten)]
|
||||
pub mode: KnobMode,
|
||||
pub style: KeyStyleByStateMap<Box<str>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(tag = "mode", rename_all = "kebab-case")]
|
||||
pub enum KnobMode {
|
||||
Select { states: Box<[Box<str>]>, wrap_around: bool },
|
||||
Range,
|
||||
}
|
289
handlers/home_assistant/src/ha_client.rs
Normal file
289
handlers/home_assistant/src/ha_client.rs
Normal file
|
@ -0,0 +1,289 @@
|
|||
use futures_util::SinkExt;
|
||||
use native_tls::TlsConnector;
|
||||
use reqwest::header::{HeaderMap, HeaderValue};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::min;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use tokio::sync::{broadcast, RwLock};
|
||||
use tokio_stream::StreamExt;
|
||||
use tokio_tungstenite::{tungstenite, Connector};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StateUpdate {
|
||||
Disconnected,
|
||||
Actual(Arc<ActualStateUpdate>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ActualStateUpdate {
|
||||
pub entity_id: Box<str>,
|
||||
pub state: Box<str>,
|
||||
pub timestamp: Box<str>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HaClient {
|
||||
state_updates_sender: broadcast::Sender<StateUpdate>,
|
||||
http_client: reqwest::Client,
|
||||
base_url: Url,
|
||||
}
|
||||
|
||||
impl HaClient {
|
||||
pub async fn new(base_url: Url, token: Box<str>, accept_invalid_certs: bool, subscribed_entity_ids: Vec<Box<str>>) -> Self {
|
||||
let http_client = reqwest::ClientBuilder::new()
|
||||
.connect_timeout(Duration::from_secs(10))
|
||||
.default_headers({
|
||||
let mut map = HeaderMap::new();
|
||||
map.insert(
|
||||
"Authorization",
|
||||
HeaderValue::from_str(&format!("Bearer {token}")).expect("the token generated by Home Assistant only contains valid characters"),
|
||||
);
|
||||
map
|
||||
})
|
||||
.danger_accept_invalid_certs(accept_invalid_certs)
|
||||
.user_agent(format!("home_assistant deckster handler (v{})", env!("CARGO_PKG_VERSION")))
|
||||
.build()
|
||||
.unwrap(); // The HTTP client being available is essential.
|
||||
|
||||
let state_updates_sender = broadcast::Sender::<StateUpdate>::new(min(subscribed_entity_ids.len(), 16));
|
||||
let state_timestamp_by_entity_id = subscribed_entity_ids.iter().map(|i| (i.clone(), "".to_owned().into_boxed_str())).collect();
|
||||
|
||||
let tls_connector = TlsConnector::builder().danger_accept_invalid_certs(accept_invalid_certs).build().unwrap();
|
||||
|
||||
tokio::spawn(do_work(
|
||||
base_url.clone(),
|
||||
token,
|
||||
tls_connector,
|
||||
state_updates_sender.clone(),
|
||||
http_client.clone(),
|
||||
state_timestamp_by_entity_id,
|
||||
));
|
||||
|
||||
if log::max_level() <= log::Level::Debug {
|
||||
let mut updates = state_updates_sender.subscribe();
|
||||
tokio::spawn(async move {
|
||||
while let Ok(u) = updates.recv().await {
|
||||
log::debug!("State update: {u:?}")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
HaClient {
|
||||
state_updates_sender,
|
||||
http_client,
|
||||
base_url,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe_to_state_updates(&self) -> broadcast::Receiver<StateUpdate> {
|
||||
self.state_updates_sender.subscribe()
|
||||
}
|
||||
|
||||
pub async fn toggle_entity(&self, entity_id: &str) {
|
||||
let (domain, _) = entity_id.split_once('.').expect("entity IDs must contain exactly one dot");
|
||||
|
||||
let result = self
|
||||
.http_client
|
||||
.post(self.base_url.join(&format!("/api/services/{domain}/toggle")).unwrap())
|
||||
.body(format!("{{\"entity_id\":\"{entity_id}\"}}"))
|
||||
.send()
|
||||
.await
|
||||
.and_then(|a| a.error_for_status());
|
||||
|
||||
if let Err(error) = result {
|
||||
log::error!(
|
||||
"POST request to {} failed: {error}",
|
||||
error.url().map(|u| u.to_string()).unwrap_or("?".to_owned())
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_work(
|
||||
base_url: Url,
|
||||
token: Box<str>,
|
||||
tls_connector: TlsConnector,
|
||||
state_updates_sender: broadcast::Sender<StateUpdate>,
|
||||
http_client: reqwest::Client,
|
||||
state_timestamp_by_entity_id: HashMap<Box<str>, Box<str>>,
|
||||
) {
|
||||
let states_url = base_url.join("/api/states/").unwrap();
|
||||
let websocket_url = {
|
||||
let mut u = base_url.clone();
|
||||
u.set_scheme(&u.scheme().replace("http", "ws")).unwrap();
|
||||
u.set_path("api/websocket");
|
||||
u.to_string()
|
||||
};
|
||||
|
||||
let mut is_first_connection_attempt = true;
|
||||
let state_timestamp_by_entity_id = Arc::new(RwLock::new(state_timestamp_by_entity_id));
|
||||
|
||||
loop {
|
||||
let connection_result =
|
||||
tokio_tungstenite::connect_async_tls_with_config(&websocket_url, None, false, Some(Connector::NativeTls(tls_connector.clone()))).await;
|
||||
|
||||
match connection_result {
|
||||
Err(tungstenite::Error::Io(error)) => {
|
||||
if is_first_connection_attempt {
|
||||
log::warn!("Establishing a WebSocket connection failed: {error}");
|
||||
log::info!("Retrying every 5 seconds…")
|
||||
}
|
||||
|
||||
is_first_connection_attempt = false;
|
||||
tokio::time::sleep(Duration::from_secs(5)).await;
|
||||
}
|
||||
Err(error) => panic!("WebSocket error: {}", error),
|
||||
Ok((mut socket, _)) => {
|
||||
log::info!("WebSocket connection successfully established.");
|
||||
|
||||
while let Some(event) = socket.next().await {
|
||||
match event {
|
||||
Err(error) => {
|
||||
log::error!("The WebSocket connection failed: {error}");
|
||||
break;
|
||||
}
|
||||
Ok(message) => match message {
|
||||
tungstenite::Message::Ping(data) => socket.send(tungstenite::Message::Pong(data)).await.unwrap(),
|
||||
tungstenite::Message::Text(data) => {
|
||||
log::trace!("Received WebSocket message: {data}");
|
||||
|
||||
match serde_json::from_str::<HaIncomingWsMessage>(&data) {
|
||||
Err(error) => log::error!("Deserializing WebSocket message failed: {error}"),
|
||||
Ok(message) => match message {
|
||||
HaIncomingWsMessage::AuthRequired { .. } => socket
|
||||
.send(tungstenite::Message::Text(
|
||||
serde_json::to_string(&HaOutgoingWsMessage::Auth { access_token: token.clone() }).unwrap(),
|
||||
))
|
||||
.await
|
||||
.unwrap(),
|
||||
HaIncomingWsMessage::AuthInvalid { .. } => panic!("Invalid access token."),
|
||||
HaIncomingWsMessage::AuthOk { .. } => {
|
||||
let subscription_message = serde_json::to_string_pretty(&HaOutgoingWsMessage::SubscribeTrigger {
|
||||
// ID may not be zero (that one took me a while)
|
||||
id: 1,
|
||||
trigger: HaTrigger::State {
|
||||
entity_id: state_timestamp_by_entity_id.read().await.keys().cloned().collect(),
|
||||
// Setting from to null prevents events being sent when only attributes have changed.
|
||||
from: serde_json::Value::Null,
|
||||
},
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
socket.send(tungstenite::Message::Text(subscription_message)).await.unwrap();
|
||||
}
|
||||
HaIncomingWsMessage::Result { id, success } => {
|
||||
if !success {
|
||||
panic!("A command ({id}) failed.");
|
||||
}
|
||||
|
||||
for entity_id in state_timestamp_by_entity_id.read().await.keys() {
|
||||
tokio::spawn(request_entity_state(
|
||||
states_url.join(entity_id).unwrap(),
|
||||
http_client.clone(),
|
||||
Arc::clone(&state_timestamp_by_entity_id),
|
||||
state_updates_sender.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
HaIncomingWsMessage::Event { event, .. } => match extract_state_update_from_event(&event) {
|
||||
None => log::error!("Invalid state change event message: {data}"),
|
||||
Some(update) => {
|
||||
// LOCK START
|
||||
let mut state_timestamp_by_entity_id = state_timestamp_by_entity_id.write().await;
|
||||
|
||||
match state_timestamp_by_entity_id.get(&update.entity_id) {
|
||||
None => log::warn!("Received unwanted state change event for entity '{}'", update.entity_id),
|
||||
Some(last_timestamp) => {
|
||||
if last_timestamp < &update.timestamp {
|
||||
state_timestamp_by_entity_id.insert(update.entity_id.clone(), update.timestamp.clone());
|
||||
state_updates_sender.send(StateUpdate::Actual(Arc::new(update))).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
// LOCK END
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => log::error!("Received unsupported WebSocket message: {message:?}"),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async fn request_entity_state(
|
||||
url: Url,
|
||||
http_client: reqwest::Client,
|
||||
state_timestamp_by_entity_id: Arc<RwLock<HashMap<Box<str>, Box<str>>>>,
|
||||
state_updates_sender: broadcast::Sender<StateUpdate>,
|
||||
) {
|
||||
match http_client.get(url).send().await.and_then(|a| a.error_for_status()) {
|
||||
Err(error) => log::error!(
|
||||
"A GET request to {} failed: {error}",
|
||||
error.url().map(|u| u.to_string()).unwrap_or("?".to_owned())
|
||||
),
|
||||
Ok(response) => match serde_json::from_str(&response.text().await.unwrap()) {
|
||||
Ok(object) => match extract_state_update_from_state(&object) {
|
||||
None => log::error!("Invalid entity state object: {object}"),
|
||||
Some(update) => {
|
||||
// LOCK START
|
||||
let mut state_timestamp_by_entity_id = state_timestamp_by_entity_id.write().await;
|
||||
let last_timestamp = state_timestamp_by_entity_id
|
||||
.get(&update.entity_id)
|
||||
.expect("Home Assistant responds with the state of the requested entity.");
|
||||
|
||||
if last_timestamp < &update.timestamp {
|
||||
state_timestamp_by_entity_id.insert(update.entity_id.clone(), update.timestamp.clone());
|
||||
state_updates_sender.send(StateUpdate::Actual(Arc::new(update))).unwrap();
|
||||
}
|
||||
// LOCK END
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
log::error!("Failed to deserialize state object: {error}");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_state_update_from_event(object: &serde_json::Value) -> Option<ActualStateUpdate> {
|
||||
extract_state_update_from_state(object.get("variables")?.get("trigger")?.get("to_state")?)
|
||||
}
|
||||
|
||||
fn extract_state_update_from_state(object: &serde_json::Value) -> Option<ActualStateUpdate> {
|
||||
Some(ActualStateUpdate {
|
||||
state: object.get("state")?.as_str()?.to_owned().into_boxed_str(),
|
||||
entity_id: object.get("entity_id")?.as_str()?.to_owned().into_boxed_str(),
|
||||
timestamp: object.get("last_changed")?.as_str()?.to_owned().into_boxed_str(),
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum HaIncomingWsMessage {
|
||||
AuthRequired { ha_version: Box<str> },
|
||||
AuthOk { ha_version: Box<str> },
|
||||
AuthInvalid { message: Box<str> },
|
||||
Result { id: usize, success: bool },
|
||||
Event { id: usize, event: serde_json::Value },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
pub enum HaOutgoingWsMessage {
|
||||
Auth { access_token: Box<str> },
|
||||
SubscribeTrigger { id: usize, trigger: HaTrigger },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(tag = "platform", rename_all = "snake_case")]
|
||||
pub enum HaTrigger {
|
||||
State { entity_id: Box<[Box<str>]>, from: serde_json::Value },
|
||||
}
|
116
handlers/home_assistant/src/handler.rs
Normal file
116
handlers/home_assistant/src/handler.rs
Normal file
|
@ -0,0 +1,116 @@
|
|||
use crate::config::{GlobalConfig, KeyConfig, KeyMode, KnobConfig, KnobMode};
|
||||
use crate::ha_client::{HaClient, StateUpdate};
|
||||
use deckster_mode::shared::handler_communication::{HandlerCommand, HandlerEvent, HandlerInitializationError, InitialHandlerMessage, KeyEvent};
|
||||
use deckster_mode::shared::path::KeyPath;
|
||||
use deckster_mode::{send_command, DecksterHandler};
|
||||
use std::thread;
|
||||
use tokio::select;
|
||||
use tokio::sync::broadcast;
|
||||
use tokio::task::LocalSet;
|
||||
|
||||
pub struct Handler {
|
||||
events_sender: broadcast::Sender<HandlerEvent>,
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub fn new(data: InitialHandlerMessage<GlobalConfig, KeyConfig, KnobConfig>) -> Result<Self, HandlerInitializationError> {
|
||||
let events_sender = broadcast::Sender::<HandlerEvent>::new(5);
|
||||
let mut subscribed_entity_ids = Vec::new();
|
||||
|
||||
for c in data.key_configs.values() {
|
||||
subscribed_entity_ids.push(c.mode.state_entity_id().clone())
|
||||
}
|
||||
|
||||
for c in data.knob_configs.values() {
|
||||
subscribed_entity_ids.push(c.entity_id.clone())
|
||||
}
|
||||
|
||||
thread::spawn({
|
||||
let events_sender = events_sender.clone();
|
||||
|
||||
move || {
|
||||
let runtime = tokio::runtime::Builder::new_current_thread().enable_time().enable_io().build().unwrap();
|
||||
let task_set = LocalSet::new();
|
||||
|
||||
let ha_client = task_set.block_on(
|
||||
&runtime,
|
||||
HaClient::new(
|
||||
data.global_config.base_url,
|
||||
data.global_config.token,
|
||||
data.global_config.accept_invalid_certs,
|
||||
subscribed_entity_ids,
|
||||
),
|
||||
);
|
||||
|
||||
for (path, config) in data.key_configs {
|
||||
task_set.spawn_local(manage_key(events_sender.subscribe(), ha_client.clone(), path, config));
|
||||
}
|
||||
|
||||
runtime.block_on(task_set)
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Handler { events_sender })
|
||||
}
|
||||
}
|
||||
|
||||
impl DecksterHandler for Handler {
|
||||
fn handle(&mut self, event: HandlerEvent) {
|
||||
// No receivers being available can be ignored.
|
||||
_ = self.events_sender.send(event);
|
||||
}
|
||||
}
|
||||
|
||||
async fn manage_key(mut events: broadcast::Receiver<HandlerEvent>, ha_client: HaClient, path: KeyPath, config: KeyConfig) {
|
||||
let state_entity_id = config.mode.state_entity_id();
|
||||
|
||||
if let Some(state) = &config.disconnected_state {
|
||||
send_command(HandlerCommand::SetKeyStyle {
|
||||
path: path.clone(),
|
||||
value: config.style.get(state).cloned(),
|
||||
})
|
||||
}
|
||||
|
||||
let mut state_updates = ha_client.subscribe_to_state_updates();
|
||||
|
||||
loop {
|
||||
select! {
|
||||
Ok(update) = state_updates.recv() => {
|
||||
match update {
|
||||
StateUpdate::Disconnected => {
|
||||
if let Some(state) = &config.disconnected_state {
|
||||
send_command(HandlerCommand::SetKeyStyle {
|
||||
path: path.clone(),
|
||||
value: config.style.get(state).cloned()
|
||||
})
|
||||
}
|
||||
}
|
||||
StateUpdate::Actual(update) => {
|
||||
if &update.entity_id == state_entity_id {
|
||||
send_command(HandlerCommand::SetKeyStyle {
|
||||
path: path.clone(),
|
||||
value: config.style.get(&update.state).cloned()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(HandlerEvent::Key { path: p, event }) = events.recv() => {
|
||||
if p != path {
|
||||
continue
|
||||
}
|
||||
|
||||
if let KeyEvent::Press = event {
|
||||
match &config.mode {
|
||||
KeyMode::Toggle { entity_id } => {
|
||||
ha_client.toggle_entity(entity_id).await;
|
||||
}
|
||||
KeyMode::Button { .. } => {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
handlers/home_assistant/src/main.rs
Normal file
29
handlers/home_assistant/src/main.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use clap::Parser;
|
||||
use color_eyre::Result;
|
||||
|
||||
use crate::handler::Handler;
|
||||
|
||||
mod config;
|
||||
mod ha_client;
|
||||
mod handler;
|
||||
mod util;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
#[command(name = "home_assistant")]
|
||||
enum CliCommand {
|
||||
#[command(name = "deckster-run", hide = true)]
|
||||
Run,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
let command = CliCommand::parse();
|
||||
|
||||
match command {
|
||||
CliCommand::Run => {
|
||||
deckster_mode::run(Handler::new)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
54
handlers/home_assistant/src/util.rs
Normal file
54
handlers/home_assistant/src/util.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::error::TrySendError;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tokio::time::timeout;
|
||||
|
||||
/// Sends a message into the output channel after a message in the input channel was received, with a delay of `duration`.
|
||||
/// The delay is reset when a new message is reset.
|
||||
pub fn spawn_debouncer(duration: Duration) -> (Sender<()>, Receiver<()>) {
|
||||
let (input_sender, mut input_receiver) = mpsc::channel::<()>(1);
|
||||
let (output_sender, output_receiver) = mpsc::channel::<()>(1);
|
||||
|
||||
tokio::spawn(async move {
|
||||
'outer: loop {
|
||||
if input_receiver.recv().await.is_none() {
|
||||
break 'outer;
|
||||
}
|
||||
|
||||
'inner: loop {
|
||||
match timeout(duration, input_receiver.recv()).await {
|
||||
Ok(None) => break 'outer,
|
||||
Ok(Some(_)) => continue 'inner,
|
||||
Err(_) => {
|
||||
if let Err(TrySendError::Closed(_)) = output_sender.try_send(()) {
|
||||
break 'outer;
|
||||
} else {
|
||||
break 'inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
(input_sender, output_receiver)
|
||||
}
|
||||
|
||||
pub fn format_duration(duration: Duration) -> String {
|
||||
let full_seconds = duration.as_secs();
|
||||
let full_minutes = full_seconds / 60;
|
||||
let hours = full_minutes / 60;
|
||||
let minutes = full_minutes % 60;
|
||||
let seconds = full_seconds % 60;
|
||||
|
||||
if hours == 0 {
|
||||
format!("{:0>2}:{:0>2}", minutes, seconds)
|
||||
} else {
|
||||
format!("{:0>2}:{:0>2}:{:0>2}", hours, minutes, seconds)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_far_future() -> Instant {
|
||||
Instant::now() + Duration::from_secs(60 * 60 * 24 * 365 * 30) // 30 years
|
||||
}
|
|
@ -15,6 +15,7 @@ enum CliCommand {
|
|||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
let command = CliCommand::parse();
|
||||
|
||||
match command {
|
||||
|
|
|
@ -13,6 +13,7 @@ enum CliCommand {
|
|||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
env_logger::init();
|
||||
let command = CliCommand::parse();
|
||||
|
||||
match command {
|
||||
|
|
|
@ -106,6 +106,7 @@ pub async fn start(config_directory: &Path, config: Config) -> Result<()> {
|
|||
handler_runner::start(
|
||||
String::default().into_boxed_str(),
|
||||
&config_directory.join("handlers"),
|
||||
&config.handlers,
|
||||
handler_hosts_config,
|
||||
handler_commands_sender.clone(),
|
||||
events_sender.subscribe(),
|
||||
|
|
|
@ -53,6 +53,7 @@ pub async fn start(config_directory: &Path, config: Config) -> Result<()> {
|
|||
handler_runner::start(
|
||||
config.host_id.clone(),
|
||||
&config_directory.join("handlers"),
|
||||
&config.handlers,
|
||||
handler_hosts_config,
|
||||
commands_sender.clone(),
|
||||
events_sender.subscribe(),
|
||||
|
|
|
@ -23,6 +23,7 @@ use crate::model::mqtt::HandlerHostsConfig;
|
|||
pub async fn start(
|
||||
host_id: Box<str>,
|
||||
handlers_directory: &Path,
|
||||
global_handler_configs: &HashMap<Box<str>, toml::Table>,
|
||||
handler_hosts_config: HandlerHostsConfig,
|
||||
commands_sender: flume::Sender<HandlerCommand>,
|
||||
mut events_receiver: tokio::sync::broadcast::Receiver<HandlerEvent>,
|
||||
|
@ -88,6 +89,7 @@ pub async fn start(
|
|||
|
||||
start_handler(
|
||||
handlers_directory,
|
||||
global_handler_configs.get(&handler_name),
|
||||
&mut handler_config_by_key_path_by_handler_name,
|
||||
&mut handler_config_by_knob_path_by_handler_name,
|
||||
&mut handler_stdin_by_name,
|
||||
|
@ -142,6 +144,7 @@ pub async fn start(
|
|||
|
||||
async fn start_handler(
|
||||
handlers_directory: &Path,
|
||||
global_config: Option<&toml::Table>,
|
||||
handler_config_by_key_path_by_handler_name: &mut HashMap<Box<str>, HashMap<KeyPath, Arc<toml::Table>>>,
|
||||
handler_config_by_knob_path_by_handler_name: &mut HashMap<Box<str>, HashMap<KnobPath, Arc<toml::Table>>>,
|
||||
handler_stdin_by_name: &mut HashMap<Box<str>, ChildStdin>,
|
||||
|
@ -170,7 +173,11 @@ async fn start_handler(
|
|||
let mut stdout_lines = BufReader::new(command.stdout.take().expect("stdout is explicitly captured and has not yet been taken")).lines();
|
||||
let mut stdin = command.stdin.take().expect("stdin is explicitly captured and has not yet been taken");
|
||||
|
||||
let initial_handler_message = InitialHandlerMessage { key_configs, knob_configs };
|
||||
let initial_handler_message = InitialHandlerMessage {
|
||||
global_config,
|
||||
key_configs,
|
||||
knob_configs,
|
||||
};
|
||||
|
||||
let serialized_message = serde_json::to_string(&initial_handler_message).unwrap().into_boxed_str().into_boxed_bytes();
|
||||
|
||||
|
@ -184,17 +191,19 @@ async fn start_handler(
|
|||
|
||||
if let HandlerInitializationResultMessage::Error { error } = result {
|
||||
#[rustfmt::skip]
|
||||
if let HandlerInitializationError::InvalidConfig { supports_keys, supports_knobs, .. } = error {
|
||||
if !supports_keys && !initial_handler_message.key_configs.is_empty() {
|
||||
if let HandlerInitializationError::InvalidConfig { requires_global_config, supports_keys, supports_knobs, .. } = error {
|
||||
if requires_global_config && initial_handler_message.global_config.is_none() {
|
||||
return Err(eyre!("The '{handler_name}' handler requires a global configuration in the deckster.toml file."));
|
||||
} else if !supports_keys && !initial_handler_message.key_configs.is_empty() {
|
||||
return Err(eyre!(
|
||||
"The '{handler_name}' handler does not support keys, but these keys tried to use it: {}",
|
||||
initial_handler_message.key_configs.keys().map(|k| k.to_string()).join(", ")
|
||||
));
|
||||
"The '{handler_name}' handler does not support keys, but these keys tried to use it: {}",
|
||||
initial_handler_message.key_configs.keys().map(|k| k.to_string()).join(", ")
|
||||
));
|
||||
} else if !supports_knobs && !initial_handler_message.knob_configs.is_empty() {
|
||||
return Err(eyre!(
|
||||
"The '{handler_name}' handler does not support knobs, but these knobs tried to use it: {}",
|
||||
initial_handler_message.knob_configs.keys().map(|k| k.to_string()).join(", ")
|
||||
));
|
||||
"The '{handler_name}' handler does not support knobs, but these knobs tried to use it: {}",
|
||||
initial_handler_message.knob_configs.keys().map(|k| k.to_string()).join(", ")
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -79,6 +79,7 @@ pub async fn main() -> Result<()> {
|
|||
icon_packs: deckster_file.icon_packs,
|
||||
initial: deckster_file.initial,
|
||||
mqtt: deckster_file.mqtt,
|
||||
handlers: deckster_file.handlers,
|
||||
}
|
||||
.validate()?;
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@ pub struct File {
|
|||
pub buttons: HashMap<ButtonPosition, ButtonConfig>, // EnumMap
|
||||
pub initial: InitialConfig,
|
||||
pub mqtt: Option<MqttConfig>,
|
||||
#[serde(default)]
|
||||
pub handlers: HashMap<Box<str>, toml::Table>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -44,6 +46,7 @@ pub struct Config {
|
|||
pub buttons: EnumMap<ButtonPosition, ButtonConfig>,
|
||||
pub initial: InitialConfig,
|
||||
pub mqtt: Option<MqttConfig>,
|
||||
pub handlers: HashMap<Box<str>, toml::Table>,
|
||||
}
|
||||
|
||||
fn inactive_button_color_default() -> RGB8Wrapper {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::model::mqtt::MqttConfig;
|
||||
|
||||
|
@ -6,4 +7,6 @@ use crate::model::mqtt::MqttConfig;
|
|||
pub struct Config {
|
||||
pub host_id: Box<str>,
|
||||
pub mqtt: MqttConfig,
|
||||
#[serde(default)]
|
||||
pub handlers: HashMap<Box<str>, toml::Table>,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue