diff --git a/Cargo.lock b/Cargo.lock index 8367d7f..134148b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -49,13 +49,13 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "async-trait" -version = "0.1.73" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.39", ] [[package]] @@ -98,9 +98,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bitflags" @@ -125,9 +125,9 @@ checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -213,6 +213,16 @@ dependencies = [ "serde", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -221,9 +231,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -293,6 +303,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fnv" version = "1.0.7" @@ -310,9 +330,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -325,9 +345,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -335,15 +355,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -352,38 +372,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.39", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -409,9 +429,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "js-sys", @@ -550,7 +570,7 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bytes", "headers-core", "http", @@ -590,6 +610,7 @@ dependencies = [ "log", "markdown", "pretty_env_logger", + "rand", "reqwest", "ron", "serde", @@ -632,9 +653,9 @@ checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "http" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "f95b9abcae896730d42b78e09c155ed4ddf82c07b4de772c64aee5b2d8b7c150" dependencies = [ "bytes", "fnv", @@ -690,7 +711,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -699,9 +720,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http", @@ -713,16 +734,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -744,6 +765,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "1.9.3" @@ -756,9 +783,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "itoa" @@ -768,9 +795,9 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] @@ -783,14 +810,13 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "lighter_lib" version = "0.1.0" -source = "git+https://git.nubo.sh/hulthe/lighter.git#0c8f09e825c655e4907fe30221ff4a08ffa274f1" dependencies = [ "serde", ] @@ -798,10 +824,10 @@ dependencies = [ [[package]] name = "lighter_manager" version = "0.1.0" -source = "git+https://git.nubo.sh/hulthe/lighter.git#0c8f09e825c655e4907fe30221ff4a08ffa274f1" dependencies = [ - "anyhow", + "async-trait", "clap", + "eyre", "lighter_lib", "log", "mqtt-protocol", @@ -815,9 +841,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -842,9 +868,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "mime" @@ -873,9 +899,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi", @@ -910,15 +936,15 @@ dependencies = [ "log", "memchr", "mime", - "spin 0.9.8", + "spin", "version_check", ] [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -950,9 +976,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "os_str_bytes" -version = "6.5.1" +version = "6.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d5d9eb14b174ee9aa2ef96dc2b94637a2d4b6e7cb873c7e171f0c20c6cf3eac" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" [[package]] name = "parking_lot" @@ -966,9 +992,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", @@ -1000,7 +1026,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.39", ] [[package]] @@ -1063,9 +1089,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -1117,18 +1143,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.9.5" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -1138,9 +1164,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -1149,17 +1175,17 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", @@ -1181,6 +1207,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-rustls", "tower-service", @@ -1194,17 +1221,16 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.20" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" dependencies = [ "cc", + "getrandom", "libc", - "once_cell", - "spin 0.5.2", + "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys", ] [[package]] @@ -1226,9 +1252,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" dependencies = [ "log", "ring", @@ -1238,18 +1264,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", ] [[package]] name = "rustls-webpki" -version = "0.101.6" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", "untrusted", @@ -1275,9 +1301,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", "untrusted", @@ -1329,29 +1355,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.188" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.39", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1401,15 +1427,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.1" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -1417,20 +1443,14 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -1456,15 +1476,36 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.37" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags", + "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 = "termcolor" version = "1.3.0" @@ -1482,22 +1523,22 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.49" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.39", ] [[package]] @@ -1517,9 +1558,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -1529,20 +1570,21 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.4", + "socket2 0.5.5", "tokio-macros", + "tracing", "windows-sys", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.39", ] [[package]] @@ -1580,9 +1622,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -1609,11 +1651,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-core", @@ -1621,9 +1662,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] @@ -1691,9 +1732,9 @@ dependencies = [ [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" @@ -1714,9 +1755,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "uuid" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" dependencies = [ "getrandom", ] @@ -1794,15 +1835,15 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.36" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ "cfg-if", "js-sys", @@ -1828,7 +1869,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1841,9 +1882,9 @@ checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" -version = "0.3.63" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" +checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", @@ -1887,10 +1928,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ "windows-targets", ] diff --git a/Cargo.toml b/Cargo.toml index 6ad074d..874dc3d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,7 @@ lto = true opt-level = 's' # Issue with const-generics incremental = false + +[patch."https://git.nubo.sh/hulthe/lighter.git"] +lighter_manager = { path = "../lighter/manager" } +lighter_lib = { path = "../lighter/lib" } diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 6e9a1a3..b4e24df 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -19,6 +19,7 @@ toml = "0.5.9" serde = { version = "1.0.138", features = ["derive"] } futures = "0.3.21" chrono = { version = "0.4.20", features = ["serde"] } +rand = "0.8.5" [dependencies.common] path = "../common" diff --git a/backend/src/main.rs b/backend/src/main.rs index 5541a2f..18cc284 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,6 +1,7 @@ mod collector; mod persistence; mod tasks; +mod util; use clap::Parser; use collector::CollectorConfig; diff --git a/backend/src/tasks/lights.rs b/backend/src/tasks/lights.rs index d1df757..b35ab5e 100644 --- a/backend/src/tasks/lights.rs +++ b/backend/src/tasks/lights.rs @@ -1,22 +1,22 @@ use std::collections::HashMap; -use chrono::{DateTime, Datelike, Local, NaiveTime, Weekday}; -use common::{ClientMessage, ServerMessage}; -use lighter_lib::{BulbColor, BulbId}; +use common::{BulbPrefs, ClientMessage, ScriptId, ServerMessage}; +use lighter_lib::BulbId; use lighter_manager::manager::{BulbCommand, BulbManager, BulbSelector}; +use lighter_manager::provider::mqtt::BulbsMqtt; use serde::{Deserialize, Serialize}; -use std::time::Duration; use tokio::select; -use tokio::sync::{broadcast, mpsc}; -use tokio::task::{spawn, JoinHandle}; -use tokio::time::sleep; use crate::persistence::PersistenceFile; -use crate::{ClientRequest, State}; +use crate::State; + +use self::scripts::{LightScript, Party, Waker}; + +pub mod scripts; #[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize)] struct LightsState { - wake_schedule: HashMap>, + script_prefs: HashMap>, } pub async fn lights_task(state: &State) { @@ -29,36 +29,43 @@ pub async fn lights_task(state: &State) { .await .expect("Failed to open lights config"); - let (cmd, bulb_states) = BulbManager::launch(config.bulbs.clone(), config.mqtt.clone()) + let provider = BulbsMqtt::new(config.bulbs.clone(), config.mqtt.clone()); + let manager = BulbManager::launch(config.bulbs.clone(), provider) .await .expect("Failed to launch bulb manager"); - let mut wake_tasks: HashMap<(BulbId, Weekday), JoinHandle<()>> = lights_state - .get() - .wake_schedule - .iter() - .flat_map(|(bulb, schedule)| schedule.iter().map(move |(day, time)| (bulb, day, time))) - .map(|(bulb, day, time)| { - let handle = spawn(wake_task( - state.client_message.clone(), - cmd.clone(), - bulb.clone(), - *day, - *time, - )); + let mut scripts: HashMap> = Default::default(); + scripts.insert( + "waker".to_string(), + Box::new(Waker::create(manager.clone())), + ); + scripts.insert( + "party".to_string(), + Box::new(Party::create(manager.clone())), + ); - ((bulb.clone(), *day), handle) - }) - .collect(); + for (script, prefs) in &lights_state.get().script_prefs { + let Some(script) = scripts.get_mut(script) else { + continue; + }; + + for (bulb, prefs) in prefs { + for (name, value) in &prefs.kvs { + script.set_param(bulb, name, value.clone()) + } + } + } loop { - let notify = bulb_states.notify_on_change(); select! { - _ = notify => { - let lights_state = lights_state.get(); - for (id, mode) in bulb_states.bulbs().await.clone().into_iter() { - let wake_schedule = lights_state.wake_schedule.get(&id).cloned().unwrap_or_default(); - let msg = ServerMessage::BulbState { id, mode, wake_schedule }; + _ = manager.notify_on_change() => { + for (id, mode) in manager.bulbs().await.clone().into_iter() { + let prefs = scripts.iter_mut() + .map(|(script, prefs)| + (script.clone(), prefs.get_params(&id))) + .collect(); + + let msg = ServerMessage::BulbState { id, mode, prefs }; if let Err(e) = server_message.send(msg) { error!("broadcast channel error: {e}"); return; @@ -73,58 +80,42 @@ pub async fn lights_task(state: &State) { match request.message { ClientMessage::SetBulbColor { id, color } => { - if let Err(e) = cmd.send(BulbCommand::SetColor(BulbSelector::Id(id), color)).await { - error!("bulb manager error: {e}"); - } + manager.send_command(BulbCommand::SetColor(BulbSelector::Id(id), color)).await; } ClientMessage::SetBulbPower { id, power } => { - if let Err(e) = cmd.send(BulbCommand::SetPower(BulbSelector::Id(id), power)).await { - error!("bulb manager error: {e}"); - } + manager.send_command(BulbCommand::SetPower(BulbSelector::Id(id), power)).await; } ClientMessage::GetBulbs => { if let Err(e) = request.response.send(ServerMessage::BulbMap(config.bulb_map.clone())).await { error!("GetBulbs response channel error: {e}"); return; } - let lights_state = lights_state.get(); - for (id, mode) in bulb_states.bulbs().await.clone().into_iter() { - let wake_schedule = lights_state.wake_schedule.get(&id).cloned().unwrap_or_default(); - let msg = ServerMessage::BulbState { id, mode, wake_schedule }; + for (id, mode) in manager.bulbs().await.clone().into_iter() { + let prefs = scripts.iter_mut() + .map(|(script, prefs)| + (script.clone(), prefs.get_params(&id))) + .collect(); + + let msg = ServerMessage::BulbState { id, mode, prefs }; if let Err(e) = request.response.send(msg).await { error!("GetBulbs response channel error: {e}"); return; } } } - ClientMessage::SetBulbWakeTime { id, day, time } => { - if let Err(e) = lights_state.update(|lights_state| { - let schedule = lights_state.wake_schedule.entry(id.clone()).or_default(); - if let Some(time) = time { - schedule.insert(day, time); - } - else { - schedule.remove(&day); - } - }).await { - error!("Failed to save wake schedule: {e}"); + ClientMessage::SetBulbPref { bulb, script, name, value } => { + let Some(s) = scripts.get_mut(&script) else { + continue; }; + s.set_param(&bulb, &name, value.clone()); - if let Some(time) = time { - let handle = spawn(wake_task( - state.client_message.clone(), - cmd.clone(), - id.clone(), - day, - time, - )); - - if let Some(old_handle) = wake_tasks.insert((id, day), handle) { - old_handle.abort(); - } - } else if let Some(old_handle) = wake_tasks.remove(&(id, day)) { - old_handle.abort(); - } + // TODO handle error + lights_state.update(move |state| { + state.script_prefs + .entry(script).or_default() + .entry(bulb).or_default() + .kvs.insert(name, value); + }).await.expect("failed to persist lights state"); } _ => {} } @@ -132,112 +123,3 @@ pub async fn lights_task(state: &State) { } } } - -async fn wake_task( - client_messages: broadcast::Sender, - cmd: mpsc::Sender, - id: BulbId, - day: Weekday, - time: NaiveTime, -) { - let mut alarm = next_alarm(Local::now(), day, time); - - loop { - info!("sleeping until {alarm}"); - sleep((alarm - Local::now()).to_std().unwrap()).await; - - // slowly turn up brightness of bulb - for brightness in (1..=75).map(|i| (i as f32) * 0.01) { - select! { - // abort if the client pokes the bulb - _ = wait_for_bulb_command(&id, client_messages.subscribe()) => break, - _ = sleep(Duration::from_secs(12)) => {} - }; - - if cmd - .send(BulbCommand::SetColor( - BulbSelector::Id(id.clone()), - BulbColor::Kelvin { - t: 0.0, - b: brightness, - }, - )) - .await - .is_err() - { - return; - }; - } - - alarm = next_alarm(Local::now(), day, time); - } -} - -/// Get the next alarm, from a weekday+time schedule. -fn next_alarm(now: DateTime, day: Weekday, time: NaiveTime) -> DateTime { - let day_of_alarm = day.num_days_from_monday() as i64; - let day_now = now.weekday().num_days_from_monday() as i64; - - let alarm = now + chrono::Duration::days(day_of_alarm - day_now); - let mut alarm = alarm - .date_naive() - .and_time(time) - .and_local_timezone(Local) - .unwrap(); - - if alarm <= now { - alarm += chrono::Duration::weeks(1); - } - - alarm -} - -/// Wait until we receive a client request that mutates the given bulb -async fn wait_for_bulb_command( - bulb_id: &BulbId, - mut client_messages: broadcast::Receiver, -) { - loop { - match client_messages.recv().await { - Err(_) => return, - Ok(request) => match request.message { - ClientMessage::SetBulbColor { id, .. } - | ClientMessage::SetBulbPower { id, .. } - | ClientMessage::SetBulbWakeTime { id, .. } - if &id == bulb_id => - { - break - } - _ => continue, - }, - } - } -} - -#[cfg(test)] -mod test { - use chrono::{offset::TimeZone, Local, NaiveTime, Weekday}; - - use super::next_alarm; - - #[test] - fn test_alarm_date() { - const FMT: &str = "%Y-%m-%d %H:%M"; - let now = Local.datetime_from_str("2022-10-18 15:30", FMT).unwrap(); - let test_values = [ - (Weekday::Tue, (16, 30), "2022-10-18 16:30"), - (Weekday::Tue, (14, 30), "2022-10-25 14:30"), - (Weekday::Wed, (15, 30), "2022-10-19 15:30"), - (Weekday::Mon, (15, 30), "2022-10-24 15:30"), - ]; - - for (day, (hour, min), expected) in test_values { - let expected = Local.datetime_from_str(expected, FMT).unwrap(); - - assert_eq!( - next_alarm(now, day, NaiveTime::from_hms(hour, min, 0)), - expected - ); - } - } -} diff --git a/backend/src/tasks/lights/scripts/mod.rs b/backend/src/tasks/lights/scripts/mod.rs new file mode 100644 index 0000000..e7c730d --- /dev/null +++ b/backend/src/tasks/lights/scripts/mod.rs @@ -0,0 +1,12 @@ +use common::{BulbPrefs, Param}; +use lighter_lib::BulbId; + +mod party; +mod waker; +pub use party::Party; +pub use waker::Waker; + +pub trait LightScript { + fn get_params(&mut self, bulb: &BulbId) -> BulbPrefs; + fn set_param(&mut self, bulb: &BulbId, name: &str, value: Param); +} diff --git a/backend/src/tasks/lights/scripts/party.rs b/backend/src/tasks/lights/scripts/party.rs new file mode 100644 index 0000000..124ad3c --- /dev/null +++ b/backend/src/tasks/lights/scripts/party.rs @@ -0,0 +1,79 @@ +use std::{collections::HashMap, time::Duration}; + +use common::{BulbPrefs, Param}; +use lighter_lib::{BulbColor, BulbId}; +use lighter_manager::manager::{BulbCommand, BulbManager, BulbSelector}; +use rand::random; +use tokio::{spawn, time::sleep}; + +use crate::util::DeadMansHandle; + +use super::LightScript; + +pub struct Party { + manager: BulbManager, + party_tasks: HashMap, +} + +impl Party { + pub fn create(manager: BulbManager) -> Self { + Party { + manager, + party_tasks: Default::default(), + } + } +} + +impl LightScript for Party { + fn get_params(&mut self, bulb: &BulbId) -> BulbPrefs { + let enabled = self.party_tasks.get(bulb).is_some(); + + BulbPrefs { + kvs: [("Party".to_string(), Param::Toggle(enabled))] + .into_iter() + .collect(), + } + } + + fn set_param(&mut self, bulb: &BulbId, name: &str, param: Param) { + if name != "Party" { + error!("invalit param name"); + return; + } + + // TODO: should be toggle + let Param::Toggle(enabled) = param else { + error!("invalit param kind"); + return; + }; + + if !enabled { + self.party_tasks.remove(bulb); + } else { + let task = spawn(party_task(self.manager.clone(), bulb.clone())); + self.party_tasks + .insert(bulb.clone(), DeadMansHandle(task.abort_handle())); + } + } +} + +async fn party_task(manager: BulbManager, id: BulbId) { + manager + .until_interrupted(id.clone(), async { + let mut h: f32 = random(); + loop { + sleep(Duration::from_millis(50)).await; + + h += 0.01; + if h > 1.0 { + h = 0.0; + } + + let color = BulbColor::HSB { h, s: 1.0, b: 1.0 }; + manager + .send_command(BulbCommand::SetColor(BulbSelector::Id(id.clone()), color)) + .await; + } + }) + .await; +} diff --git a/backend/src/tasks/lights/scripts/waker.rs b/backend/src/tasks/lights/scripts/waker.rs new file mode 100644 index 0000000..e15b668 --- /dev/null +++ b/backend/src/tasks/lights/scripts/waker.rs @@ -0,0 +1,188 @@ +use std::{collections::HashMap, time::Duration}; + +use chrono::{DateTime, Datelike, Local, NaiveTime, Weekday}; +use common::{BulbPrefs, Param}; +use lighter_lib::{BulbColor, BulbId}; +use lighter_manager::manager::{BulbCommand, BulbManager, BulbSelector}; +use tokio::{spawn, time::sleep}; + +use crate::util::DeadMansHandle; + +use super::LightScript; + +pub struct Waker { + manager: BulbManager, + wake_times: HashMap>, + wake_tasks: HashMap<(BulbId, Weekday), DeadMansHandle>, +} + +impl Waker { + pub fn create(manager: BulbManager) -> Self { + Waker { + manager, + wake_times: Default::default(), + wake_tasks: Default::default(), + } + } +} + +const TIME_FMT: &str = "%H:%M"; + +impl LightScript for Waker { + fn get_params(&mut self, bulb: &BulbId) -> BulbPrefs { + let settings = self.wake_times.entry(bulb.clone()).or_default(); + + let kvs = DAYS_OF_WEEK + .iter() + .map(|day| { + let time = match settings.get(day) { + Some(time) => time.format(TIME_FMT).to_string(), + None => String::new(), + }; + (format!("{day:?}"), Param::String(time)) + }) + .collect(); + + BulbPrefs { kvs } + } + + fn set_param(&mut self, bulb: &BulbId, name: &str, time: super::Param) { + let settings = self.wake_times.entry(bulb.clone()).or_default(); + + let Param::String(time) = time else { + error!("invalit param kind"); + return; + }; + + let time = NaiveTime::parse_from_str(&time, TIME_FMT) + .map(Some) + .unwrap_or(None); + + let weekday = match name { + "Mon" => Weekday::Mon, + "Tue" => Weekday::Tue, + "Wed" => Weekday::Wed, + "Thu" => Weekday::Thu, + "Fri" => Weekday::Fri, + "Sat" => Weekday::Sat, + "Sun" => Weekday::Sun, + _ => { + error!("invalit param name"); + return; + } + }; + + let Some(time) = time else { + settings.remove(&weekday); + self.wake_tasks.remove(&(bulb.clone(), weekday)); + return; + }; + + settings.insert(weekday, time); + let task = spawn(wake_task(self.manager.clone(), bulb.clone(), weekday, time)); + self.wake_tasks + .insert((bulb.clone(), weekday), DeadMansHandle(task.abort_handle())); + } +} + +const DAYS_OF_WEEK: &[Weekday] = &[ + Weekday::Mon, + Weekday::Tue, + Weekday::Wed, + Weekday::Thu, + Weekday::Fri, + Weekday::Sat, + Weekday::Sun, +]; + +async fn wake_task(manager: BulbManager, id: BulbId, day: Weekday, time: NaiveTime) { + let mut alarm = next_alarm(Local::now(), day, time); + + loop { + info!("waking lamp {id:?} at {alarm}"); + sleep((alarm - Local::now()).to_std().unwrap()).await; + + if let Some(bulb) = manager.bulbs().await.get(&id) { + // don't wake the bulb if it's already turned on + if bulb.power { + continue; + } + } else { + warn!("bulb {id:?} does not exist"); + return; + }; + + info!("waking lamp {id:?}"); + let r = manager + .until_interrupted(id.clone(), async { + // slowly turn up brightness of bulb + for brightness in (1..=75).map(|i| (i as f32) * 0.01) { + //sleep(Duration::from_secs(12)).await; + sleep(Duration::from_millis(500)).await; + + manager + .send_command(BulbCommand::SetColor( + BulbSelector::Id(id.clone()), + BulbColor::Kelvin { + t: 0.0, + b: brightness, + }, + )) + .await + } + }) + .await; + + if r.is_none() { + info!("interrupted waking lamp {id:?}"); + } + + alarm = next_alarm(Local::now(), day, time); + } +} + +/// Get the next alarm, from a weekday+time schedule. +fn next_alarm(now: DateTime, day: Weekday, time: NaiveTime) -> DateTime { + let day_of_alarm = day.num_days_from_monday() as i64; + let day_now = now.weekday().num_days_from_monday() as i64; + + let alarm = now + chrono::Duration::days(day_of_alarm - day_now); + let mut alarm = alarm + .date_naive() + .and_time(time) + .and_local_timezone(Local) + .unwrap(); + + if alarm <= now { + alarm += chrono::Duration::weeks(1); + } + + alarm +} + +#[cfg(test)] +mod test { + use super::next_alarm; + use chrono::{offset::TimeZone, Local, NaiveTime, Weekday}; + + #[test] + fn test_alarm_date() { + const FMT: &str = "%Y-%m-%d %H:%M"; + let now = Local.datetime_from_str("2022-10-18 15:30", FMT).unwrap(); + let test_values = [ + (Weekday::Tue, (16, 30), "2022-10-18 16:30"), + (Weekday::Tue, (14, 30), "2022-10-25 14:30"), + (Weekday::Wed, (15, 30), "2022-10-19 15:30"), + (Weekday::Mon, (15, 30), "2022-10-24 15:30"), + ]; + + for (day, (hour, min), expected) in test_values { + let expected = Local.datetime_from_str(expected, FMT).unwrap(); + + assert_eq!( + next_alarm(now, day, NaiveTime::from_hms(hour, min, 0)), + expected + ); + } + } +} diff --git a/backend/src/util.rs b/backend/src/util.rs new file mode 100644 index 0000000..e61f665 --- /dev/null +++ b/backend/src/util.rs @@ -0,0 +1,15 @@ +use tokio::task::AbortHandle; + +pub struct DeadMansHandle(pub AbortHandle); + +impl From for DeadMansHandle { + fn from(abort: AbortHandle) -> Self { + DeadMansHandle(abort) + } +} + +impl Drop for DeadMansHandle { + fn drop(&mut self) { + self.0.abort(); + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index ea4200b..8c6beeb 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,6 +1,5 @@ -use std::collections::HashMap; +use std::collections::BTreeMap; -use chrono::{NaiveTime, Weekday}; use lighter_lib::{BulbColor, BulbId, BulbMode}; use serde::{Deserialize, Serialize}; @@ -15,7 +14,7 @@ pub enum ServerMessage { BulbState { id: BulbId, mode: BulbMode, - wake_schedule: HashMap, + prefs: BTreeMap, }, BulbMap(BulbMap), @@ -35,10 +34,11 @@ pub enum ClientMessage { id: BulbId, power: bool, }, - SetBulbWakeTime { - id: BulbId, - day: Weekday, - time: Option, + SetBulbPref { + bulb: BulbId, + script: ScriptId, + name: String, + value: Param, }, } @@ -78,3 +78,16 @@ impl BulbGroupShape { } } } + +pub type ScriptId = String; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum Param { + String(String), + Toggle(bool), +} + +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct BulbPrefs { + pub kvs: BTreeMap, +} diff --git a/frontend/src/page/lights.rs b/frontend/src/page/lights.rs index cbacb75..ee2fc71 100644 --- a/frontend/src/page/lights.rs +++ b/frontend/src/page/lights.rs @@ -1,13 +1,13 @@ use crate::components::color_picker::{ColorPicker, ColorPickerMsg}; use crate::css::C; -use chrono::{NaiveTime, Weekday}; -use common::{BulbGroup, BulbGroupShape, BulbMap, ClientMessage, ServerMessage}; +use common::{BulbGroup, BulbGroupShape, BulbMap, BulbPrefs, ClientMessage, Param, ServerMessage}; use lighter_lib::{BulbId, BulbMode}; -use seed::{attrs, button, div, h2, input, table, td, tr, C}; +use seed::{attrs, button, div, empty, h2, input, table, td, tr, C}; use seed::{prelude::*, IF}; use seed_router::Page; -use std::collections::{BTreeMap, HashMap, HashSet}; +use std::collections::{BTreeMap, HashSet}; use std::fmt::Write; +use std::iter::repeat; /// /lights page #[derive(Default)] @@ -28,7 +28,7 @@ pub struct Model { #[derive(Default, Clone)] struct BulbState { mode: BulbMode, - wake_schedule: HashMap, + prefs: BTreeMap, } #[derive(Debug)] @@ -39,7 +39,13 @@ pub enum Msg { DeselectGroups, ColorPicker(ColorPickerMsg), SetBulbPower(bool), - LightTime(String, Weekday), + + /// Set a script parameter value for all selected bulbs. + SetParam { + script: String, + name: String, + value: Param, + }, } impl Page for Model { @@ -58,11 +64,11 @@ impl Page for Model { ServerMessage::BulbState { id, mode: new_mode, - wake_schedule, + prefs, } => { *self.bulb_states.entry(id).or_default() = BulbState { mode: new_mode, - wake_schedule, + prefs, }; //color_picker.set_color(mode.color); @@ -124,26 +130,25 @@ impl Page for Model { orders.notify(message); }); } - Msg::LightTime(time, day) => { - if time.is_empty() { - self.for_selected_bulbs(|id, _| { - let message = ClientMessage::SetBulbWakeTime { - id: id.clone(), - day, - time: None, - }; - orders.notify(message); - }); - } else if let Ok(time) = NaiveTime::parse_from_str(&time, "%H:%M") { - self.for_selected_bulbs(|id, _| { - let message = ClientMessage::SetBulbWakeTime { - id: id.clone(), - day, - time: Some(time), - }; - orders.notify(message); - }); - } + Msg::SetParam { + script, + name, + value, + } => { + self.for_selected_bulbs(|id, bulb| { + bulb.prefs + .get_mut(&script) + .unwrap() //TOD + .kvs + .insert(name.clone(), value.clone()); + let message = ClientMessage::SetBulbPref { + bulb: id.clone(), + script: script.clone(), + name: name.clone(), + value: value.clone(), + }; + orders.notify(message); + }); } } } @@ -230,20 +235,44 @@ impl Page for Model { .and_then(|group| group.bulbs.first()) .and_then(|id| self.bulb_states.get(id)); - let calendar_day = |day: Weekday| { - let time = selected_bulb - .and_then(|b| b.wake_schedule.get(&day)) - .map(|t| t.to_string()) - .unwrap_or_default(); - tr![ - C![C.calendar_day], - td![day.to_string()], - td![input![ - C![C.calendar_time_input], - attrs! {At::Placeholder => time}, - input_ev(Ev::Input, move |input| Msg::LightTime(input, day)) - ]], - ] + let script_param = |script: &str, name: &str, value: &Param| { + let name = name.to_string(); + let script = script.to_string(); + + match value { + Param::String(value) => tr![ + C![C.pref_line], + td![&name], + td![input![ + C![C.pref_input], + attrs! {At::Placeholder => &script}, + attrs! {At::Value => value}, + input_ev(Ev::Input, move |input| Msg::SetParam { + script, + name, + value: Param::String(input), + }) + ]] + ], + &Param::Toggle(value) => { + tr![ + C![C.pref_line], + button![ + if value { + C![C.pref_button_enabled] + } else { + C![C.pref_button] + }, + &name, + input_ev(Ev::Click, move |_| Msg::SetParam { + script, + name, + value: Param::Toggle(!value), + }) + ] + ] + } + } }; div![ @@ -278,30 +307,35 @@ impl Page for Model { ], ], div![ - C![C.calendar_box], + C![C.prefs_box], IF!(selected_bulb.is_none() => C![C.cross_out]), - h2!["Wake Schedule"], - table![ - calendar_day(Weekday::Mon), - calendar_day(Weekday::Tue), - calendar_day(Weekday::Wed), - calendar_day(Weekday::Thu), - calendar_day(Weekday::Fri), - calendar_day(Weekday::Sat), - calendar_day(Weekday::Sun), - ], + h2!["Settings"], + if let Some(selected_bulb) = selected_bulb { + table![selected_bulb + .prefs + .iter() + .flat_map(|(script, prefs)| repeat(script).zip(prefs.kvs.iter())) + .map(|(script, (name, value))| script_param(script, name, value))] + } else { + empty![] + }, ], ] } } impl Model { - fn for_selected_bulbs(&self, mut f: impl FnMut(&BulbId, &BulbState)) { - self.selected_groups - .iter() - .filter_map(|&index| self.bulb_map.groups.get(index)) - .flat_map(|group| group.bulbs.iter()) - .filter_map(|id| self.bulb_states.get(id).map(|bulb| (id, bulb))) - .for_each(|(id, bulb)| f(id, bulb)); + fn for_selected_bulbs(&mut self, mut f: impl FnMut(&BulbId, &mut BulbState)) { + for &index in &self.selected_groups { + let Some(group) = self.bulb_map.groups.get(index) else { + continue; + }; + + for id in group.bulbs.iter() { + if let Some(bulb) = self.bulb_states.get_mut(id) { + f(id, bulb); + } + } + } } } diff --git a/frontend/static/styles/common.scss b/frontend/static/styles/common.scss index fef7729..e4be215 100644 --- a/frontend/static/styles/common.scss +++ b/frontend/static/styles/common.scss @@ -304,14 +304,16 @@ body { transition: margin 0.1s ease-out; } -.calendar_day { + + +.pref_line { display: flex; flex-direction: row; justify-content: space-between; margin-top: .3em; } -.calendar_time_input { +.pref_input { background: #453f4b; border: solid 0.35em #5b3f63; border-radius: .3em; @@ -323,17 +325,49 @@ body { margin-left: .5em; } -.calendar_box { +.pref_button {} +.pref_button, .pref_button_enabled { + position: relative; + width: 100%; + + font-size: large; + font-weight: bold; + color: white; + text-shadow: 0.1rem 0.1rem 0.3rem black; + + padding: 1rem; + border: solid 0.35em #5b3f63; + border-radius: 0.3em; + background: transparent; + overflow: hidden; +} +.pref_button_enabled::before { + content: ""; + z-index: -1; + width: 20rem; + height: 20rem; + background-size: 100% 100%; + background-image: url(/images/hsb.png); + position: absolute; + transform: translate(-8.5rem, -2.5rem); + animation: infinite linear 3s button_rainbow; +} +@keyframes button_rainbow { + from { transform: translate(-8.5rem, -2.5rem) rotate( 0deg); } + to { transform: translate(-8.5rem, -2.5rem) rotate(360deg); } +} + +.prefs_box { display: flex; justify-content: center; } -.calendar_box > h2 { +.prefs_box > h2 { writing-mode: sideways-lr; margin-top: auto; margin-bottom: auto; } -.calendar_box > * { +.prefs_box > * { flex-shrink: 1; }