feat: dynamic count of machines, use config

This commit is contained in:
2026-08-25 00:00:07 +02:00
parent 59c6fb43a0
commit b0250e08bf
7 changed files with 438 additions and 102 deletions
+1
View File
@@ -1 +1,2 @@
/target /target
config.toml
Generated
+285 -27
View File
@@ -32,6 +32,23 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "arraydeque"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
[[package]]
name = "async-trait"
version = "0.1.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.4",
]
[[package]] [[package]]
name = "atomic" name = "atomic"
version = "0.6.1" version = "0.6.1"
@@ -102,6 +119,9 @@ name = "bitflags"
version = "2.13.1" version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
dependencies = [
"serde_core",
]
[[package]] [[package]]
name = "block-buffer" name = "block-buffer"
@@ -147,9 +167,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.4.3" version = "1.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@@ -203,6 +223,55 @@ dependencies = [
"static_assertions", "static_assertions",
] ]
[[package]]
name = "config"
version = "0.15.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b85f248a4de22d204ceabc6299d89d2c70fbd7f09fea53c06c852369652d8139"
dependencies = [
"async-trait",
"convert_case 0.6.0",
"json5",
"pathdiff",
"ron",
"rust-ini",
"serde-untagged",
"serde_core",
"serde_json",
"toml",
"winnow",
"yaml-rust2",
]
[[package]]
name = "const-random"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
dependencies = [
"const-random-macro",
]
[[package]]
name = "const-random-macro"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
"getrandom 0.2.17",
"once_cell",
"tiny-keccak",
]
[[package]]
name = "convert_case"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
dependencies = [
"unicode-segmentation",
]
[[package]] [[package]]
name = "convert_case" name = "convert_case"
version = "0.10.0" version = "0.10.0"
@@ -280,6 +349,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "crunchy"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.7" version = "0.1.7"
@@ -302,9 +377,9 @@ dependencies = [
[[package]] [[package]]
name = "darling" name = "darling"
version = "0.24.0" version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88490bf1b990d87eaaa7ac8aa887f629a08e7359765b4911faf63c3763347d23" checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"darling_macro", "darling_macro",
@@ -312,26 +387,26 @@ dependencies = [
[[package]] [[package]]
name = "darling_core" name = "darling_core"
version = "0.24.0" version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "084e274f91c482280130e1e34e0b8d6e66776a060d7b6de7b84289ca778868c4" checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff"
dependencies = [ dependencies = [
"ident_case", "ident_case",
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 3.0.3", "syn 3.0.4",
] ]
[[package]] [[package]]
name = "darling_macro" name = "darling_macro"
version = "0.24.0" version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68f5792fa0d41cd2325ce0ffa64f0a340eaebd4971a3a0c5e1ffd2cc488a355e" checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 3.0.3", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -364,7 +439,7 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [ dependencies = [
"convert_case", "convert_case 0.10.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustc_version", "rustc_version",
@@ -381,6 +456,15 @@ dependencies = [
"crypto-common", "crypto-common",
] ]
[[package]]
name = "dlv-list"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
dependencies = [
"const-random",
]
[[package]] [[package]]
name = "document-features" name = "document-features"
version = "0.2.12" version = "0.2.12"
@@ -398,9 +482,18 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]] [[package]]
name = "either" name = "either"
version = "1.17.0" version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "equivalent" name = "equivalent"
@@ -408,6 +501,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "erased-serde"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec"
dependencies = [
"serde",
"serde_core",
"typeid",
]
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.14" version = "0.3.14"
@@ -557,7 +661,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.3", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -634,6 +738,12 @@ dependencies = [
"rand_core 0.10.1", "rand_core 0.10.1",
] ]
[[package]]
name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.16.1" version = "0.16.1"
@@ -656,6 +766,15 @@ dependencies = [
"foldhash", "foldhash",
] ]
[[package]]
name = "hashlink"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f"
dependencies = [
"hashbrown 0.16.1",
]
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@@ -693,7 +812,7 @@ dependencies = [
"indoc", "indoc",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.3", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -732,6 +851,17 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "json5"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
dependencies = [
"pest",
"pest_derive",
"serde",
]
[[package]] [[package]]
name = "kasuari" name = "kasuari"
version = "0.4.12" version = "0.4.12"
@@ -799,9 +929,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.33" version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]] [[package]]
name = "lru" name = "lru"
@@ -827,6 +957,7 @@ name = "machinetui"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"config",
"crossterm", "crossterm",
"futures", "futures",
"rand 0.10.2", "rand 0.10.2",
@@ -956,6 +1087,16 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "ordered-multimap"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
dependencies = [
"dlv-list",
"hashbrown 0.14.5",
]
[[package]] [[package]]
name = "palette" name = "palette"
version = "0.7.7" version = "0.7.7"
@@ -1012,6 +1153,12 @@ dependencies = [
"windows-link", "windows-link",
] ]
[[package]]
name = "pathdiff"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.9.0" version = "2.9.0"
@@ -1345,6 +1492,20 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "ron"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81116b9531d61eabc41aeb228e4b6b2435bcca3233b98cf3b3077d4e6e9debb3"
dependencies = [
"bitflags 2.13.1",
"once_cell",
"serde",
"serde_derive",
"typeid",
"unicode-ident",
]
[[package]] [[package]]
name = "rumqttc" name = "rumqttc"
version = "0.25.1" version = "0.25.1"
@@ -1366,6 +1527,16 @@ dependencies = [
"tokio-util", "tokio-util",
] ]
[[package]]
name = "rust-ini"
version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7"
dependencies = [
"cfg-if",
"ordered-multimap",
]
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.1" version = "0.4.1"
@@ -1398,7 +1569,7 @@ dependencies = [
"log", "log",
"once_cell", "once_cell",
"rustls-pki-types", "rustls-pki-types",
"rustls-webpki 0.103.14", "rustls-webpki 0.103.15",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@@ -1446,9 +1617,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.103.14" version = "0.103.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2"
dependencies = [ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"ring", "ring",
@@ -1522,6 +1693,18 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "serde-untagged"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058"
dependencies = [
"erased-serde",
"serde",
"serde_core",
"typeid",
]
[[package]] [[package]]
name = "serde_core" name = "serde_core"
version = "1.0.229" version = "1.0.229"
@@ -1539,7 +1722,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.3", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -1555,6 +1738,15 @@ dependencies = [
"zmij", "zmij",
] ]
[[package]]
name = "serde_spanned"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
dependencies = [
"serde_core",
]
[[package]] [[package]]
name = "sha2" name = "sha2"
version = "0.10.9" version = "0.10.9"
@@ -1703,9 +1895,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "3.0.3" version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1825,7 +2017,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.3", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -1860,6 +2052,15 @@ dependencies = [
"time-core", "time-core",
] ]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.53.1" version = "1.53.1"
@@ -1885,7 +2086,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.3", "syn 3.0.4",
] ]
[[package]] [[package]]
@@ -1923,6 +2124,43 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "toml"
version = "1.1.4+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3aace63f4bbcdfc2c965b059de67119c89c4017a70d633be6c104910f67056f5"
dependencies = [
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_parser",
"winnow",
]
[[package]]
name = "toml_datetime"
version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [
"serde_core",
]
[[package]]
name = "toml_parser"
version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
dependencies = [
"winnow",
]
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.20.1" version = "1.20.1"
@@ -1978,9 +2216,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.24.1" version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc"
dependencies = [ dependencies = [
"atomic", "atomic",
"getrandom 0.4.3", "getrandom 0.4.3",
@@ -2245,12 +2483,32 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "wit-bindgen" name = "wit-bindgen"
version = "0.57.1" version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "yaml-rust2"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b36710ce3a279cfce8465dbab826f161675a262950b922cb2c3663852dfe9eb0"
dependencies = [
"arraydeque",
"encoding_rs",
"hashlink",
]
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.9.0" version = "1.9.0"
+1
View File
@@ -5,6 +5,7 @@ edition = "2024"
[dependencies] [dependencies]
anyhow = "1.0.104" anyhow = "1.0.104"
config = "0.15.25"
crossterm = { version = "0.29.0", features = ["event-stream"] } crossterm = { version = "0.29.0", features = ["event-stream"] }
futures = "0.3.34" futures = "0.3.34"
rand = "0.10.2" rand = "0.10.2"
+35
View File
@@ -0,0 +1,35 @@
use anyhow::Result;
use rumqttc::MqttOptions;
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct ConfigData {
pub mqtt: MqttConfig,
#[serde(rename = "machine", default)]
pub machines: Vec<MachineConfig>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MqttConfig {
pub device_id: String,
pub host: String,
pub port: u16,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MachineConfig {
pub name: String,
pub topic: String,
}
pub fn load() -> Result<ConfigData> {
let source = config::File::with_name("config").format(config::FileFormat::Toml);
let config = config::Config::builder().add_source(source).build()?;
Ok(config.try_deserialize()?)
}
impl MqttConfig {
pub fn as_options(&self) -> MqttOptions {
MqttOptions::new(&self.device_id, &self.host, self.port)
}
}
+18 -26
View File
@@ -1,11 +1,11 @@
use std::time::Duration; use std::time::Duration;
use anyhow::Result; use anyhow::Result;
use rumqttc::{AsyncClient, Event, MqttOptions, Packet, Publish, QoS}; use rumqttc::{AsyncClient, Event, Packet, Publish, QoS};
use serde::Deserialize; use serde::Deserialize;
use time::{OffsetDateTime, PrimitiveDateTime}; use time::{OffsetDateTime, PrimitiveDateTime};
use crate::{data, events}; use crate::{config::ConfigData, data, events};
time::serde::format_description!( time::serde::format_description!(
tasmota_time, tasmota_time,
@@ -13,11 +13,8 @@ time::serde::format_description!(
"[year]-[month]-[day]T[hour]:[minute]:[second]" "[year]-[month]-[day]T[hour]:[minute]:[second]"
); );
const MACHINE1_TOPIC: &str = "tele/tasmota_F68B94/SENSOR";
const MACHINE2_TOPIC: &str = "tele/tasmota_F81A3C/SENSOR";
pub struct MachineData { pub struct MachineData {
pub name: String, pub topic: String,
pub voltage: f32, pub voltage: f32,
pub current: f32, pub current: f32,
pub last_updated: OffsetDateTime, pub last_updated: OffsetDateTime,
@@ -40,12 +37,12 @@ pub struct TasmotaEnergyData {
current: f32, current: f32,
} }
fn publish_to_machine_data(name: String, publish: Publish) -> Result<data::MachineData> { fn publish_to_machine_data(topic: String, publish: Publish) -> Result<data::MachineData> {
let data = publish.payload.to_vec(); let data = publish.payload.to_vec();
let data: TasmotaData = serde_json::from_slice(&data)?; let data: TasmotaData = serde_json::from_slice(&data)?;
Ok(data::MachineData { Ok(data::MachineData {
name, topic,
voltage: data.energy.voltage, voltage: data.energy.voltage,
current: data.energy.current, current: data.energy.current,
last_updated: data.time.assume_utc(), last_updated: data.time.assume_utc(),
@@ -65,36 +62,31 @@ fn start_timestamp_task(event_sender: events::EventSender) {
}); });
} }
async fn start_data_fetch_task(event_sender: events::EventSender) -> Result<()> { async fn start_data_fetch_task(
let options = MqttOptions::new("machinetui", "192.168.178.251", 1883); config: &ConfigData,
event_sender: events::EventSender,
) -> Result<()> {
let options = config.mqtt.as_options();
let (client, mut event_loop) = AsyncClient::new(options, 10); let (client, mut event_loop) = AsyncClient::new(options, 10);
client.subscribe(MACHINE1_TOPIC, QoS::AtLeastOnce).await?; for machine in &config.machines {
client.subscribe(MACHINE2_TOPIC, QoS::AtLeastOnce).await?; client.subscribe(&machine.topic, QoS::AtLeastOnce).await?;
}
tokio::spawn(async move { tokio::spawn(async move {
// Keep client alive
let _client = client; let _client = client;
loop { loop {
match event_loop.poll().await { match event_loop.poll().await {
Ok(Event::Incoming(Packet::Publish(publish))) => match publish.topic.as_str() { Ok(Event::Incoming(Packet::Publish(publish))) => match publish.topic.as_str() {
MACHINE1_TOPIC => { topic => {
let machine_data = let machine_data = publish_to_machine_data(topic.into(), publish).unwrap();
publish_to_machine_data("machine 1".into(), publish).unwrap();
event_sender event_sender
.send(events::Event::MachineUpdate(machine_data)) .send(events::Event::MachineUpdate(machine_data))
.await .await
.unwrap(); .unwrap();
} }
MACHINE2_TOPIC => {
let machine_data =
publish_to_machine_data("machine 2".into(), publish).unwrap();
event_sender
.send(events::Event::MachineUpdate(machine_data))
.await
.unwrap();
}
_ => {}
}, },
Ok(_) => {} Ok(_) => {}
Err(_err) => {} Err(_err) => {}
@@ -105,8 +97,8 @@ async fn start_data_fetch_task(event_sender: events::EventSender) -> Result<()>
Ok(()) Ok(())
} }
pub async fn start(event_sender: events::EventSender) -> Result<()> { pub async fn start(config: &ConfigData, event_sender: events::EventSender) -> Result<()> {
start_timestamp_task(event_sender.clone()); start_timestamp_task(event_sender.clone());
start_data_fetch_task(event_sender).await?; start_data_fetch_task(config, event_sender).await?;
Ok(()) Ok(())
} }
+9 -4
View File
@@ -1,16 +1,21 @@
use anyhow::Result; mod config;
mod data; mod data;
mod events; mod events;
mod interaction; mod interaction;
mod tui; mod tui;
use anyhow::Result;
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let config = config::load()?;
let (event_sender, event_receiver) = events::channel(); let (event_sender, event_receiver) = events::channel();
data::start(event_sender.clone()).await?; data::start(&config, event_sender.clone()).await?;
tokio::try_join!(tui::start(event_receiver), interaction::start(event_sender))?; tokio::try_join!(
tui::start(&config, event_receiver),
interaction::start(event_sender)
)?;
Ok(()) Ok(())
} }
+89 -45
View File
@@ -1,71 +1,96 @@
use std::collections::{HashMap, VecDeque};
use anyhow::Result; use anyhow::Result;
use ratatui::{ use ratatui::{
prelude::*, prelude::*,
symbols::border, symbols::border,
widgets::{Block, Padding, Paragraph}, widgets::{Axis, Block, Chart, Dataset, GraphType, Padding, Paragraph},
}; };
use time::{OffsetDateTime, format_description::BorrowedFormatItem, macros::format_description}; use time::{OffsetDateTime, format_description::BorrowedFormatItem, macros::format_description};
use crate::{data, events}; use crate::{
config::ConfigData,
data::{self},
events,
};
const TIME_FORMAT: &'static [BorrowedFormatItem] = format_description!("[hour]:[minute]:[second]"); const TIME_FORMAT: &'static [BorrowedFormatItem] = format_description!("[hour]:[minute]:[second]");
const HISTORY_LEN: usize = 120;
pub struct App { pub struct App<'a> {
pub timestamp: OffsetDateTime, pub timestamp: OffsetDateTime,
pub machines: Vec<data::MachineData>, pub machines: HashMap<String, data::MachineData>,
pub history: HashMap<String, VecDeque<(f64, f64)>>,
config: &'a ConfigData,
} }
impl App { impl<'a> App<'a> {
pub fn new() -> Self { pub fn new(config: &'a ConfigData) -> Self {
Self { Self {
timestamp: OffsetDateTime::now_local().unwrap(), timestamp: OffsetDateTime::now_local().unwrap(),
machines: vec![ machines: HashMap::new(),
data::MachineData { history: HashMap::new(),
name: "machine 1".into(), config,
voltage: 0f32,
current: 0f32,
last_updated: OffsetDateTime::now_utc(),
},
data::MachineData {
name: "machine 2".into(),
voltage: 0f32,
current: 0f32,
last_updated: OffsetDateTime::now_utc(),
},
],
} }
} }
} }
impl Widget for &App { impl<'a> Widget for &App<'a> {
fn render(self, area: Rect, buf: &mut Buffer) fn render(self, area: Rect, buf: &mut Buffer)
where where
Self: Sized, Self: Sized,
{ {
let [headline_area, machines_area, _] = Layout::vertical([ let [headline_area, machines_area, chart_area] = Layout::vertical([
Constraint::Length(3), Constraint::Length(3),
Constraint::Length(6), Constraint::Length(6),
Constraint::Min(0), Constraint::Min(0),
]) ])
.areas(area); .areas(area);
let [machine1_area, machine2_area] =
Layout::horizontal([Constraint::Fill(1), Constraint::Fill(1)]).areas(machines_area);
Headline { Headline {
timestamp: &self.timestamp, timestamp: &self.timestamp,
} }
.render(headline_area, buf); .render(headline_area, buf);
MachineData { let machine_area_const = vec![Constraint::Fill(1); self.config.machines.len()];
data: &self.machines[0], let machine_areas = Layout::horizontal(machine_area_const).split(machines_area);
} let machine_zip = self
.render(machine1_area, buf); .config
.machines
.iter()
.zip(machine_areas.iter().copied());
MachineData { for (machine_config, machine_area) in machine_zip {
data: &self.machines[1], let machine_data = self.machines.get(&machine_config.topic);
MachineData {
name: &machine_config.name,
data: machine_data,
}
.render(machine_area, buf);
} }
.render(machine2_area, buf);
let datasets: Vec<Dataset<'_>> = self
.history
.iter()
.map(|(name, data)| {
let data = data.as_slices().0;
Dataset::default()
.name(Line::from(name.as_str()))
.marker(symbols::Marker::Braille)
.graph_type(GraphType::Line)
.style(Color::Cyan)
.data(data)
})
.collect();
let chart = Chart::new(datasets)
.block(Block::bordered().title("Current usage (A)"))
.x_axis(Axis::default().bounds([0.0, HISTORY_LEN as f64]))
.y_axis(Axis::default().bounds([0.0, 10f64]));
chart.render(chart_area, buf);
} }
} }
@@ -102,7 +127,8 @@ impl<'a> Widget for &Headline<'a> {
} }
pub struct MachineData<'a> { pub struct MachineData<'a> {
data: &'a data::MachineData, name: &'a str,
data: Option<&'a data::MachineData>,
} }
impl<'a> Widget for &MachineData<'a> { impl<'a> Widget for &MachineData<'a> {
@@ -117,6 +143,20 @@ impl<'a> Widget for &MachineData<'a> {
let block_area = block.inner(area); let block_area = block.inner(area);
block.render(area, buf); block.render(area, buf);
let machine = match self.data {
Some(data) => data,
None => {
// Render no data
let area_const = [Constraint::Length(1), Constraint::Min(0)];
let [name_area, no_data_area] = Layout::vertical(area_const).areas(block_area);
let text = format!("Name: {}", self.name).bold().fg(Color::Blue);
Paragraph::new(text).render(name_area, buf);
Paragraph::new("no data").render(no_data_area, buf);
return;
}
};
let layout_const = [ let layout_const = [
Constraint::Length(1), Constraint::Length(1),
Constraint::Length(1), Constraint::Length(1),
@@ -127,18 +167,18 @@ impl<'a> Widget for &MachineData<'a> {
Layout::vertical(layout_const).areas(block_area); Layout::vertical(layout_const).areas(block_area);
let format = format_description!("[year]-[month]-[day] [hour]:[month]:[second]"); let format = format_description!("[year]-[month]-[day] [hour]:[month]:[second]");
let last_updated = self.data.last_updated.format(format).unwrap(); let last_updated = machine.last_updated.format(format).unwrap();
let text = format!("Name: {}", self.data.name).bold().fg(Color::Blue); let text = format!("Name: {}", self.name).bold().fg(Color::Blue);
Paragraph::new(text).render(name_area, buf); Paragraph::new(text).render(name_area, buf);
let text = Line::from(vec![ let text = Line::from(vec![
"Voltage: ".bold(), "Voltage: ".bold(),
format!("{:.0} V", self.data.voltage).into(), format!("{:.0} V", machine.voltage).into(),
]); ]);
Paragraph::new(text).render(voltage_area, buf); Paragraph::new(text).render(voltage_area, buf);
let text = Line::from(vec![ let text = Line::from(vec![
"Current: ".bold(), "Current: ".bold(),
format!("{:.03} A", self.data.current).into(), format!("{:.03} A", machine.current).into(),
]); ]);
Paragraph::new(text).render(current_area, buf); Paragraph::new(text).render(current_area, buf);
let text = Line::from(vec!["Last updated: ".bold(), last_updated.into()]); let text = Line::from(vec!["Last updated: ".bold(), last_updated.into()]);
@@ -146,12 +186,12 @@ impl<'a> Widget for &MachineData<'a> {
} }
} }
pub async fn start(event_receiver: events::EventReceiver) -> Result<()> { pub async fn start(config: &ConfigData, event_receiver: events::EventReceiver) -> Result<()> {
let mut event_receiver = event_receiver; let mut event_receiver = event_receiver;
let mut terminal = ratatui::init(); let mut terminal = ratatui::init();
let mut running = true; let mut running = true;
let mut app = App::new(); let mut app = App::new(config);
while running { while running {
// render tui // render tui
@@ -163,12 +203,16 @@ pub async fn start(event_receiver: events::EventReceiver) -> Result<()> {
match event_receiver.recv().await { match event_receiver.recv().await {
Some(events::Event::Timestamp(t)) => app.timestamp = t, Some(events::Event::Timestamp(t)) => app.timestamp = t,
Some(events::Event::MachineUpdate(data)) => { Some(events::Event::MachineUpdate(data)) => {
for machine in &mut app.machines { let name = data.topic.clone();
if machine.name == data.name { let current = data.current;
machine.voltage = data.voltage;
machine.current = data.current; app.machines.insert(name.clone(), data);
machine.last_updated = data.last_updated;
} let history = app.history.entry(name).or_default();
let x = history.len() as f64;
history.push_back((x, current as f64));
if history.len() > HISTORY_LEN {
history.pop_front();
} }
} }
Some(events::Event::Exit) => running = false, Some(events::Event::Exit) => running = false,