diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..ca2b787 Binary files /dev/null and b/.DS_Store differ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..287871f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1500 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "clap" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "colored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "config_lib" +version = "0.1.0" +dependencies = [ + "env_logger", + "log", + "rand", + "toml_edit", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.9.1", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.9.1", + "core-foundation", + "libc", +] + +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.9.1", + "objc2", +] + +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "enigo" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71744ff36f35a4276e8827add8102d0e792378c574fd93cb4e1c8e0505f96b7c" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types-shared", + "libc", + "log", + "nom", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "windows", + "x11rb", + "xkbcommon", + "xkeysym", +] + +[[package]] +name = "env_filter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gethostname" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" +dependencies = [ + "libc", + "windows-targets 0.48.5", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inquire" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" +dependencies = [ + "bitflags 2.9.1", + "crossterm", + "dyn-clone", + "fuzzy-matcher", + "fxhash", + "newline-converter", + "once_cell", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memmap2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "menu_tui" +version = "0.1.0" +dependencies = [ + "clap", + "colored", + "config_lib", + "crossterm", + "enigo", + "env_logger", + "inquire", + "log", + "terminal-menu", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + +[[package]] +name = "newline-converter" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "nix" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" +dependencies = [ + "bitflags 1.3.2", + "cc", + "cfg-if", + "libc", + "memoffset", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "objc2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88c6597e14493ab2e44ce58f2fdecf095a51f12ca57bec060a11c57332520551" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" +dependencies = [ + "bitflags 2.9.1", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +dependencies = [ + "bitflags 2.9.1", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900831247d2fe1a09a683278e5384cfb8c80c79fe6b166f9d14bfdde0ea1b03c" +dependencies = [ + "bitflags 2.9.1", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "protocol_lib" +version = "0.1.1" +dependencies = [ + "crc", + "log", + "serialport", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rk_nku_menu" +version = "0.1.0" +dependencies = [ + "config_lib", + "enigo", + "env_logger", + "log", + "menu_tui", + "sysfs_gpio", +] + +[[package]] +name = "rk_nku_sync" +version = "0.1.0" +dependencies = [ + "clap", + "config_lib", + "env_logger", + "log", + "protocol_lib", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serialport" +version = "4.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb0bc984f6af6ef8bab54e6cf2071579ee75b9286aa9f2319a0d220c28b0a2b" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "core-foundation", + "core-foundation-sys", + "io-kit-sys", + "mach2", + "nix 0.26.4", + "scopeguard", + "unescaper", + "winapi", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sysfs_gpio" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8808c55bc926565c62ef7838bcaa8add51585236803e2bdfa1472e3a3ab5e17" +dependencies = [ + "nix 0.23.2", +] + +[[package]] +name = "terminal-menu" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66e7bfe79ce3db25763bc7cb3af8be872a5e74ed2844c5cfcbbacd95c4a4bc8a" +dependencies = [ + "crossterm", + "lazy_static", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1dee9dc43ac2aaf7d3b774e2fba5148212bf2bd9374f4e50152ebe9afd03d42" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" + +[[package]] +name = "unescaper" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c01d12e3a56a4432a8b436f293c25f4808bdf9e9f9f98f9260bba1f1bc5a1f26" +dependencies = [ + "thiserror", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.1", +] + +[[package]] +name = "x11rb" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" + +[[package]] +name = "xkbcommon" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" +dependencies = [ + "libc", + "memmap2", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..98fb026 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[workspace] +resolver = "3" +members = [ + "config_lib", + "menu_tui", + "protocol_lib", + "rk_nku_sync", + "rk_nku_menu", +] + +[workspace.package] +authors = ["MU LLC "] + +[workspace.dependencies] +inquire = "0.7.5" +configparser = "3.1.0" +thiserror = "2.0.12" +log = "0.4.27" +env_logger = "0.11.8" +clap = { version = "4.5.41", features = ["derive"] } +serialport = { version = "4.7.2", default-features = false } diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 0000000..be65704 --- /dev/null +++ b/Cross.toml @@ -0,0 +1,7 @@ +[build] +build-std = false +default-target = "aarch64-unknown-linux-gnu" + +[build.zig] +enable = true # Сборка с участием линковщика Zig +version = "2.31" # Версия glibc, установленная на RK3399-ROC-PC-PRO diff --git a/README.md b/README.md new file mode 100644 index 0000000..4def0b3 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# ADAMultitool [rk3399-branch] + +> Набор утилит для работы с интерфейсной платой по последовательному порту + +## Структура репозитория + +### 1. Утилиты + +#### 1.1. rk_nku_config + +> Утилита для загрузки синхронизации настроек с интерфейсной платой + +#### 1.2. rk_nku_menu + +> TUI меню для внесения изменений в настройки индикатора + +#### 1.3. rk_nku_configs + +> Набор конфигов-схем TOML: [настройки индикатора](rk_nku_configs/nku_scheme.toml) и [последовательного порта](rk_nku_configs/rk3399_scheme.toml) + +### 2. Библиотеки + +#### 2.1. config_lib + +> Реализация I/O операций с конфигами-схемами TOML + +#### 2.2. protocol_lib + +> API для клиентского устройства, используещего [протокол МЮ](protocol_lib/MU%20Protocol.md) + +#### 2.3. menu_tui + +> Стандартная реализация TUI меню diff --git a/config_lib/Cargo.toml b/config_lib/Cargo.toml new file mode 100644 index 0000000..0253944 --- /dev/null +++ b/config_lib/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "config_lib" +version = "0.1.0" +edition = "2024" +authors.workspace = true + +[dependencies] +rand = "0.9.1" +toml_edit = "0.23.2" +log = { workspace = true } +env_logger = { workspace = true } diff --git a/config_lib/examples/simple.rs b/config_lib/examples/simple.rs new file mode 100644 index 0000000..19b7a98 --- /dev/null +++ b/config_lib/examples/simple.rs @@ -0,0 +1,44 @@ +use config_lib::device_config::DeviceConfig; +use rand::rng; +use rand::seq::IndexedRandom; + +fn main() -> Result<(), String> { + let mut config = DeviceConfig::create_parameter_list("config_lib/examples/simple_config.toml")?; + + let parameters = config.get_parameters_names()?; + println!("Полученный список параметров: {:#?}", parameters); + + println!("Текущие значения параметров:\n"); + for parameter in ¶meters { + println!( + "{} = {};\nдиапазон допустимых значений: {:?}\n", + config.get_parameter_description(¶meter)?, + config.get_parameter_value(¶meter)?, + config.get_parameter_possible_values(¶meter)? + ); + } + + let mut rng = rng(); + + // Установка случайных значений для параметров в пределах допустимой области + for parameter in ¶meters { + let random_value = config + .get_parameter_possible_values(¶meter)? + .choose(&mut rng) + .unwrap() + .clone(); + config.set_parameter_value(¶meter, random_value)?; + } + + println!("Значения параметров после изменений:\n"); + for parameter in ¶meters { + println!( + "{} = {};\nдиапазон допустимых значений: {:?}\n", + config.get_parameter_description(¶meter)?, + config.get_parameter_value(¶meter)?, + config.get_parameter_possible_values(¶meter)? + ); + } + + Ok(()) +} diff --git a/config_lib/examples/simple_config.toml b/config_lib/examples/simple_config.toml new file mode 100644 index 0000000..c037d92 --- /dev/null +++ b/config_lib/examples/simple_config.toml @@ -0,0 +1,63 @@ +# [Имя таблицы] должно соответствовать названию соответствующего параметра в интерфейсной плате +# name - текстовое название параметра (Используется в меню) +# possible_values - список возможных значений параметра +# default - значение по умолчанию +# current - актуальное текущее значение параметра. Если не указано или некорректно, то используется значение по умолчанию + +[groupnumber] +name = "Номер в группе" +possible_values = [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", +] +default = "0" +current = "2:1234" + +[soundvolume] +name = "Громкость звука" +possible_values = ["0%", "25%", "50%", "75%", "100%"] +default = "50%" +current = "100%" + +[musicvolume] +name = "Громкость музыки" +possible_values = ["0%", "25%", "50%", "75%", "100%"] +default = "0%" +current = "0%" + +[loadcapacity] +name = "Грузоподъемность" +possible_values = [ + "СКРЫТО", + "240кг 3чел.", + "320кг 4чел.", + "400кг 5чел.", + "450кг 6чел.", + "525кг 7чел.", + "630кг 8чел.", + "800кг 10чел.", + "800кг 11чел.", + "1000кг 13чел.", + "1150кг 15чел.", + "1275кг 16чел.", + "1275кг 17чел.", + "1425кг 18чел.", + "1600кг 20чел.", + "1600кг 21чел.", +] +default = "СКРЫТО" +current = "СКРЫТО" diff --git a/config_lib/src/device_config.rs b/config_lib/src/device_config.rs new file mode 100644 index 0000000..8861a89 --- /dev/null +++ b/config_lib/src/device_config.rs @@ -0,0 +1,309 @@ +use log::debug; +use std::collections::HashMap; +use toml_edit::value; + +use crate::toml_parser::*; + +/// Структура, содержащая набор параметров Parameter +#[derive(Debug, Clone)] +pub struct DeviceConfig { + scheme: TomlScheme, + /// Набор параметров + /// key - имя параметра, value - структура, описывающая параметр + parameters: HashMap, +} + +impl DeviceConfig { + /// ## Заполнение струтуры DeviceConfig + /// * `path_to_scheme` - путь к файлу-cхеме + pub fn create_parameter_list(path_to_scheme: &str) -> Result { + let toml_config = TomlScheme::new(path_to_scheme).map_err(|e| e.to_string())?; + + let parameter_list = toml_config + .get_list_of_parameters() + .map_err(|e| e.to_string())?; + + debug!("Parameter list: {:#?}", parameter_list); + let mut parameter_map = HashMap::new(); + + for parameter in parameter_list { + let device_parameter = Parameter { + description: toml_config.get_string_value(¶meter, "name")?, + value: toml_config.get_string_value(¶meter, "current")?, + possible_values: toml_config.get_array_value(¶meter, "possible_values")?, + }; + parameter_map.insert(parameter, device_parameter); + } + + let device_config = DeviceConfig { + scheme: toml_config, + parameters: parameter_map, + }; + + Ok(device_config) + } + + /// # Сохранение текущих значений параметров в TOML-файл self.schema_path + pub fn save_parameters_values(&self) -> Result<(), String> { + let mut toml_config = + TomlScheme::new(&self.scheme.get_path_to_scheme_file()).map_err(|e| e.to_string())?; + for (parameter_name, parameter_object) in &self.parameters { + toml_config.set_parameter_value( + parameter_name, + "current", + value(parameter_object.get_value()), + )?; + } + Ok(()) + } + + /// # Получение списка ключей - имен параметров + pub fn get_parameters_names(&self) -> Result, String> { + if self.parameters.is_empty() { + return Err("Parameters list is empty".to_string()); + } + + return Ok(self.parameters.keys().map(|key| key.clone()).collect()); + } + + /// # Получение описания параметра соответствующего key + pub fn get_parameter_description(&self, key: &str) -> Result { + match self.parameters.get(key) { + Some(parameter) => Ok(parameter.get_description()), + None => Err(format!("Parameter {} not found", key)), + } + } + + /// # Получение списка возможных значений параметра соответствующего key + pub fn get_parameter_possible_values(&self, key: &str) -> Result, String> { + match self.parameters.get(key) { + Some(parameter) => Ok(parameter.get_possible_values()), + None => Err(format!("Parameter {} not found", key)), + } + } + + /// # Получение значения параметра соответствующего key + pub fn get_parameter_value(&self, key: &str) -> Result { + match self.parameters.get(key) { + Some(parameter) => Ok(parameter.get_value()), + None => Err(format!("Parameter {} not found", key)), + } + } + + /// # Установка значения параметра соответствующего key + pub fn set_parameter_value(&mut self, key: &str, value: String) -> Result<(), String> { + match self.parameters.get_mut(key) { + Some(parameter) => parameter.set_value(value), + None => Err(format!("Parameter {} not found", key)), + } + } + + /// ## Получение числового индекса соответствующего текущему значению параметра + /// Индекс соответствует положению текущего значения в списке возможных значений possible_values + pub fn get_parameter_index_using_value(&self, key: &str) -> Result { + let parameters_list = self.get_parameters_names()?; + if parameters_list.contains(&key.to_owned()) { + let parameters_possible_values_list = self.get_parameter_possible_values(key)?; + let parameter_current_value = self.get_parameter_value(key)?; + + if let Some(index) = parameters_possible_values_list + .iter() + .position(|value| *value == parameter_current_value) + { + return Ok(index as u8); + } + } + Err("Unable to get parameter value using index".to_string()) + } + + /// ## Присваивание параметру key значения, соответствующего index + /// Индекс соответствует положению значения в списке возможных значений possible_values + pub fn set_parameter_value_using_index(&mut self, key: &str, index: u8) -> Result<(), String> { + let parameters_list = self.get_parameters_names()?; + if parameters_list.contains(&key.to_owned()) { + let parameters_possible_values_list = self.get_parameter_possible_values(key)?; + if let Some(value) = parameters_possible_values_list.get(index as usize) { + return self.set_parameter_value(key, value.clone()); + } + } + Err("Unable to get parameter value using index".to_string()) + } +} + +/// Структура, описывающая параметр +#[derive(Debug, Clone)] +struct Parameter { + /// Описание параметра (для использования в меню) + description: String, + /// Текущее значение параметра + value: String, + /// Список допустимых значений параметра + possible_values: Vec, +} + +impl Parameter { + /// Получение имени параметра (для использования в меню) + fn get_description(&self) -> String { + self.description.clone() + } + + /// Получение текущего значения параметра + fn get_value(&self) -> String { + self.value.clone() + } + + /// Установка текущего значения параметра + fn set_value(&mut self, value: String) -> Result<(), String> { + if !self.possible_values.contains(&value) { + return Err(format!( + "Parameter {} cannot be set to {}!", + self.description, value + )); + } + self.value = value; + Ok(()) + } + + /// Получение списка возможных значений параметра + fn get_possible_values(&self) -> Vec { + self.possible_values.clone() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_io_operations() { + let mut device_config = + DeviceConfig::create_parameter_list("examples/simple_config.toml").unwrap(); + + let names = device_config.get_parameters_names().unwrap(); + + assert!(names.contains(&"groupnumber".to_string())); + + assert!( + device_config + .set_parameter_value("groupnumber", "19".to_string()) + .is_err() + ); + + assert!( + device_config + .set_parameter_value("groupnumber", "11".to_string()) + .is_ok() + ); + + assert_eq!( + device_config.get_parameter_value("groupnumber").unwrap(), + "11" + ); + } + + #[test] + fn test_parameter_saving() { + let mut device_config = + DeviceConfig::create_parameter_list("examples/simple_config.toml").unwrap(); + + let names = device_config.get_parameters_names().unwrap(); + + assert!(names.contains(&"soundvolume".to_string())); + + assert!( + device_config + .set_parameter_value("soundvolume", "100".to_string()) + .is_err() + ); + + assert!( + device_config + .set_parameter_value("soundvolume", "100%".to_string()) + .is_ok() + ); + + assert!(device_config.save_parameters_values().is_ok()); + + let device_config = + DeviceConfig::create_parameter_list("examples/simple_config.toml").unwrap(); + + assert_eq!( + device_config.get_parameter_value("soundvolume").unwrap(), + "100%" + ); + } + + #[test] + fn test_get_parameter_index_using_value() { + let mut device_config = + DeviceConfig::create_parameter_list("examples/simple_config.toml").unwrap(); + + for index in 0..device_config + .get_parameter_possible_values("groupnumber") + .unwrap() + .len() + { + device_config + .set_parameter_value("groupnumber", index.to_string()) + .unwrap(); + assert_eq!( + device_config + .get_parameter_index_using_value("groupnumber") + .unwrap(), + index as u8 + ); + } + + let capacity_values = device_config + .get_parameter_possible_values("loadcapacity") + .unwrap(); + + for index in 0..capacity_values.len() { + device_config + .set_parameter_value("loadcapacity", capacity_values[index].clone()) + .unwrap(); + assert_eq!( + device_config + .get_parameter_index_using_value("loadcapacity") + .unwrap(), + index as u8 + ); + } + } + + #[test] + fn test_set_parameter_value_using_index() { + let mut device_config = + DeviceConfig::create_parameter_list("examples/simple_config.toml").unwrap(); + + for index in 0..device_config + .get_parameter_possible_values("groupnumber") + .unwrap() + .len() + { + device_config + .set_parameter_value_using_index("groupnumber", index as u8) + .unwrap(); + assert_eq!( + device_config.get_parameter_value("groupnumber").unwrap(), + index.to_string() + ); + } + + for index in 0..device_config + .get_parameter_possible_values("loadcapacity") + .unwrap() + .len() + { + device_config + .set_parameter_value_using_index("loadcapacity", index as u8) + .unwrap(); + assert_eq!( + device_config.get_parameter_value("loadcapacity").unwrap(), + device_config + .get_parameter_possible_values("loadcapacity") + .unwrap()[index] + ); + } + } +} diff --git a/config_lib/src/lib.rs b/config_lib/src/lib.rs new file mode 100644 index 0000000..e184bc4 --- /dev/null +++ b/config_lib/src/lib.rs @@ -0,0 +1,2 @@ +pub mod device_config; +mod toml_parser; diff --git a/config_lib/src/toml_parser.rs b/config_lib/src/toml_parser.rs new file mode 100644 index 0000000..363b492 --- /dev/null +++ b/config_lib/src/toml_parser.rs @@ -0,0 +1,148 @@ +use toml_edit::{DocumentMut, Item, Value}; + +#[derive(Debug, Clone)] +/// Структура для работы с TOML-конфигом +pub(crate) struct TomlScheme { + path_to_scheme: String, + document: DocumentMut, + tables: Vec, +} + +impl TomlScheme { + pub(crate) fn new(path_to_scheme: &str) -> Result { + let toml_str = std::fs::read_to_string(path_to_scheme).map_err(|e| e.to_string())?; + let doc = toml_str.parse::().map_err(|e| e.to_string())?; + + let table = doc.as_table(); + + // Получение списка вложенных в файл таблиц-параметров + let sub_tables: Vec = table + .iter() + .filter_map(|(k, v)| { + if v.is_table() { + Some(k.to_string()) + } else { + None + } + }) + .collect(); + + if sub_tables.is_empty() { + return Err("Parameters list is empty".to_string()); + } + + Ok(TomlScheme { + path_to_scheme: path_to_scheme.to_string(), + document: doc, + tables: sub_tables, + }) + } + + /// Получение пути к конфиг-файлу + pub(crate) fn get_path_to_scheme_file(&self) -> String { + self.path_to_scheme.clone() + } + + /// Получение списка вложенных в файл параметров (TOML-таблицы) + pub(crate) fn get_list_of_parameters(&self) -> Result, String> { + Ok(self.tables.clone()) + } + + /// Получение значения для `key`(подпараметр) у параметра `parameter_name` + fn get_parameter_value(&self, parameter_name: &str, key: &str) -> Result { + if let Some(table) = self.document.get(parameter_name) { + if let Some(sub_table) = table.as_table() { + if let Some(value) = sub_table.get(key) { + if let Some(value) = value.as_value() { + return Ok(value.clone()); + } + } + } + } + + return Err(format!("Unable to get {} value", key)); + } + + /// Получение строкового значения для `key`(подпараметр) у параметра `parameter_name` + pub(crate) fn get_string_value( + &self, + parameter_name: &str, + key: &str, + ) -> Result { + let value = self.get_parameter_value(parameter_name, key)?; + + if let Some(value) = value.as_str() { + return Ok(value.to_string()); + } + Err(format!("Unable to get {} value as string", key)) + } + + /// Получение значения - массива строк для `key`(подпараметр) у параметра `parameter_name` + pub(crate) fn get_array_value( + &self, + parameter_name: &str, + key: &str, + ) -> Result, String> { + let value = self.get_parameter_value(parameter_name, key)?; + + if let Some(array) = value.as_array() { + return Ok(array + .iter() + .map(|x| x.as_str().expect("Unable to get array value").to_string()) + .collect()); + } + Err(format!("Unable to get {} value as array", key)) + } + + /// Установка значения `value` для `key`(подпараметр) у параметра `parameter_name` + pub(crate) fn set_parameter_value( + &mut self, + parameter_name: &str, + key: &str, + val: Item, + ) -> Result<(), String> { + if let Some(table) = self.document.get_mut(parameter_name) { + if let Some(sub_table) = table.as_table() { + if let Some(_) = sub_table.get(key) { + self.document[parameter_name][key] = val; + + let new_toml = self.document.to_string(); + + std::fs::write(&self.path_to_scheme, new_toml) + .expect("Failed to write TOML file"); + + return Ok(()); + } + } + } + + return Err(format!( + "Unable to set {}'s {} value to {:?}", + parameter_name, key, val + )); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use toml_edit::value; + #[test] + fn test_get_list_of_parameters() { + let toml_scheme = TomlScheme::new("examples/simple_config.toml").unwrap(); + let parameters = toml_scheme.get_list_of_parameters().unwrap(); + assert_eq!(parameters.len(), 4); + } + + #[test] + fn test_set_parameter_value() { + let mut toml_scheme = TomlScheme::new("examples/simple_config.toml").unwrap(); + toml_scheme + .set_parameter_value("groupnumber", "current", value("2:1234")) + .unwrap(); + let value = toml_scheme + .get_string_value("groupnumber", "current") + .unwrap(); + assert_eq!(value.as_str(), "2:1234"); + } +} diff --git a/docs/build'n'deploy/README.md b/docs/build'n'deploy/README.md new file mode 100644 index 0000000..c6b5583 --- /dev/null +++ b/docs/build'n'deploy/README.md @@ -0,0 +1,20 @@ +# Настройка рабочего окружения + +## Кросскомпиляция + +- [cargo-zigbuild](https://github.com/rust-cross/cargo-zigbuild) +- [cross-rs](https://github.com/cross-rs/cross) + +### Сборка + +> Важно: необходимо использовать версию линковщика, совпадающую с версией, установленной на целевом устройстве + +- cross-rs: `cross build --target=arm-unknown-linux-gnueabihf --bin --release` + +- zigbuild: `cargo zigbuild --target=arm-unknown-linux-gnueabihf.[версия линкера на целевом устройстве] --bin --release` + +> Для кросскомпиляции `serialport` (libudev не поддерживается на MacOS & WIN64) указать фича-флаг `default-features = false` + +## Загрузка на целевое устройство + +`scp -P [port_number] [path/to/bin] [hostname]@[ip_address]:/home/[user-name]` diff --git a/docs/vscode/Install.md b/docs/vscode/Install.md new file mode 100644 index 0000000..728d9ed --- /dev/null +++ b/docs/vscode/Install.md @@ -0,0 +1,5 @@ +# Установка необходимых расширений + +```bash +cat vs_code_extensions_list.txt | xargs -n 1 code --install-extension +``` diff --git a/docs/vscode/vs_code_extensions_list.txt b/docs/vscode/vs_code_extensions_list.txt new file mode 100644 index 0000000..95f6692 Binary files /dev/null and b/docs/vscode/vs_code_extensions_list.txt differ diff --git a/menu_tui/Cargo.toml b/menu_tui/Cargo.toml new file mode 100644 index 0000000..65d71a8 --- /dev/null +++ b/menu_tui/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "menu_tui" +version = "0.1.0" +edition = "2024" +authors.workspace = true + +[dependencies] +clap = { workspace = true } +config_lib = { path = "../config_lib" } +inquire = { workspace = true } +enigo = "0.5.0" +colored = "3.0.0" +terminal-menu = "3.0.0" +crossterm = "0.25.0" +log = { workspace = true } +env_logger = { workspace = true } diff --git a/menu_tui/examples/simple.rs b/menu_tui/examples/simple.rs new file mode 100644 index 0000000..08edc41 --- /dev/null +++ b/menu_tui/examples/simple.rs @@ -0,0 +1,27 @@ +use config_lib::device_config::DeviceConfig; +use menu_tui::menu_rendering::{MainMenuStates, show_main_dialog}; + +fn main() -> Result<(), Box> { + let mut device_config = + DeviceConfig::create_parameter_list("menu_tui/examples/simple_config.toml")?; + + loop { + match show_main_dialog(&mut device_config) { + Ok(MainMenuStates::ConfigurationState) => { + println!("Configuration state"); + continue; + } + Ok(MainMenuStates::ExitState) => { + println!("The user has chosen to exit..."); + break; + } + Err(e) => { + println!("Menu error: {}", e); + break; + } + } + + } + + return Ok(()); +} diff --git a/menu_tui/examples/simple_config.toml b/menu_tui/examples/simple_config.toml new file mode 100644 index 0000000..d497542 --- /dev/null +++ b/menu_tui/examples/simple_config.toml @@ -0,0 +1,64 @@ + +# [Имя таблицы] должно соответствовать названию соответствующего параметра в интерфейсной плате +# name - текстовое название параметра (Используется в меню) +# possible_values - список возможных значений параметра +# default - значение по умолчанию +# current - актуальное текущее значение параметра. Если не указано или некорректно, то используется значение по умолчанию + +[groupnumber] +name = "Номер в группе" +possible_values = [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", +] +default = "0" +current = "6" + +[soundvolume] +name = "Громкость звука" +possible_values = ["0%", "25%", "50%", "75%", "100%"] +default = "50%" +current = "50%" + +[musicvolume] +name = "Громкость музыки" +possible_values = ["0%", "25%", "50%", "75%", "100%"] +default = "0%" +current = "100%" + +[loadcapacity] +name = "Грузоподъемность" +possible_values = [ + "СКРЫТО", + "240кг 3чел.", + "320кг 4чел.", + "400кг 5чел.", + "450кг 6чел.", + "525кг 7чел.", + "630кг 8чел.", + "800кг 10чел.", + "800кг 11чел.", + "1000кг 13чел.", + "1150кг 15чел.", + "1275кг 16чел.", + "1275кг 17чел.", + "1425кг 18чел.", + "1600кг 20чел.", + "1600кг 21чел.", +] +default = "СКРЫТО" +current = "240кг 3чел." diff --git a/menu_tui/src/lib.rs b/menu_tui/src/lib.rs new file mode 100644 index 0000000..18d3e22 --- /dev/null +++ b/menu_tui/src/lib.rs @@ -0,0 +1 @@ +pub mod menu_rendering; diff --git a/menu_tui/src/menu_rendering.rs b/menu_tui/src/menu_rendering.rs new file mode 100644 index 0000000..0f006f3 --- /dev/null +++ b/menu_tui/src/menu_rendering.rs @@ -0,0 +1,136 @@ +//! # Модуль отображения меню +//! +//! ## Навигация +//! > Для навигации по меню используется клавиши стрелка вверх/вниз +//! Выбор эелемента в меню происходит клавишей Enter +//! `В случае отсутствия данных клавиш в системе, можно эмулировать их с помощью библиотеки enigo` +//! +//! ## Параметры меню +//! > Параметры меню подтягиваются из файла-схемы TOML с помощью крейта config_lib +use config_lib::device_config::DeviceConfig; +use crossterm::style::Color; +use inquire::Select; +use terminal_menu::{TerminalMenuItem, button, label, menu, mut_menu, run}; + +pub enum MainMenuStates { + ConfigurationState, + ExitState, +} + +/// Отображение главного меню +pub fn show_main_dialog(config: &mut DeviceConfig) -> Result { + // Header + let mut menu_structure = generate_menu_header("Меню настроек индикатора"); + + // Текущие значения + menu_structure.extend(generate_current_values(config)?); + + // Элементы меню + menu_structure.extend(generate_menu_items(config)?); + + let main_menu = menu(menu_structure); + + // Отрисовка и навигация по меню + run(&main_menu); + + // Получение названия выбранного пункта меню + let mut_menu_instance = mut_menu(&main_menu); + let selected_menu_item = mut_menu_instance.selected_item_name(); + + // Получение ключа параметра в HashMap, соответствующего названию пункта меню + let selected_parameter_name = config + .get_parameters_names()? + .into_iter() + .find_map(|par_name| { + // Получение ключа параметра в HashMap, в случае если описание параметра совпадает с названием пункта меню + if let Ok(par_desc) = config.get_parameter_description(&par_name) { + return if par_desc == selected_menu_item { + Some(par_name) + } else { + None + }; + } + None + }); + + if let Some(key) = selected_parameter_name { + show_configuration_dialog(config, &key)?; + return Ok(MainMenuStates::ConfigurationState); + } + + // Сохранение настроек в TOML-файл схему + config.save_parameters_values()?; + + return Ok(MainMenuStates::ExitState); +} + +/// Отображение диалога для выбора значения параметра +fn show_configuration_dialog(config: &mut DeviceConfig, par_name: &str) -> Result<(), String> { + let possible_values = config.get_parameter_possible_values(par_name)?; + let answer = Select::new("Выберите значение параметра", possible_values.clone()).prompt(); + + match answer { + Ok(selection) => { + config.set_parameter_value(par_name, selection)?; + Ok(()) + } + Err(e) => return Err(e.to_string()), + } +} + +/// Генерация структуры меню: Титул +fn generate_menu_header(title: &str) -> Vec { + vec![ + label("----------------------").colorize(Color::DarkGreen), + label(title).colorize(Color::DarkGreen), + label(format!( + "{} v{}", + env!("CARGO_CRATE_NAME"), + env!("CARGO_PKG_VERSION") + )) + .colorize(Color::DarkGreen), + label(format!("{}", env!("CARGO_PKG_AUTHORS"))).colorize(Color::DarkGreen), + label("-----------------------").colorize(Color::DarkGreen), + ] +} + +/// Генерация структуры меню: Текущие значения настраиваемых параметров +fn generate_current_values(config: &DeviceConfig) -> Result, String> { + let parameter_names = config.get_parameters_names()?; + + let mut menu_structure = vec![]; + + for parameter_name in ¶meter_names { + let parameter_value = config.get_parameter_value(¶meter_name)?; + let parameter_description = config.get_parameter_description(¶meter_name)?; + menu_structure.push( + label(format!("{}: {}", parameter_description, parameter_value)) + .colorize(Color::DarkYellow), + ); + } + + menu_structure.push(label("-----------------------").colorize(Color::DarkYellow)); + + Ok(menu_structure) +} + +/// Генерация структуры меню: Элементы меню +fn generate_menu_items(config: &DeviceConfig) -> Result, String> { + let parameter_names = config.get_parameters_names()?; + + let mut menu_structure = vec![]; + + menu_structure.push(label("-----------------------").colorize(Color::DarkGrey)); + + // Элементы меню; имя каждого параметра соответствует полю description + let mut menu_structure = menu_structure; + for parameter_name in ¶meter_names { + let parameter_description = config.get_parameter_description(¶meter_name)?; + menu_structure.push(button(parameter_description).colorize(Color::White)); + } + menu_structure.push(button("Выход").colorize(Color::White)); + + menu_structure.push(label("-----------------------").colorize(Color::DarkGrey)); + + Ok(menu_structure) +} diff --git a/protocol_lib/Cargo.toml b/protocol_lib/Cargo.toml new file mode 100644 index 0000000..b84293a --- /dev/null +++ b/protocol_lib/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "protocol_lib" +version = "0.1.1" +edition = "2024" +authors.workspace = true + +[dependencies] +log = { workspace = true } +crc = "3.3.0" +serialport = { workspace = true } diff --git a/protocol_lib/MU Protocol.md b/protocol_lib/MU Protocol.md new file mode 100644 index 0000000..893abdc --- /dev/null +++ b/protocol_lib/MU Protocol.md @@ -0,0 +1,93 @@ +# **MU_protocol — Протокол обмена данными между микроконтроллером и одноплатным компьютером** + +## **1. Общее описание** + +Протокол предназначен для передачи данных между **Python Server App** и **одноплатным компьютером** по UART. Протокол обеспечивает: + +- Синхронизацию пакетов +- Целостность данных (через CRC16) +- Гибкость в передаче данных разных типов (определяется через `opcode`) + +--- + +## **2. Структура пакета** + +Каждый пакет состоит из следующих полей: + +| Поле | Размер | Тип | Описание | +|------------|--------|----------|----------| +| `sync1` | 1 байт | uint8 | Стартовый байт синхронизации, значение `0xAA` | +| `size` | 1 байт | uint8 | Размер поля `data` в байтах (от 0 до 255) | +| `opcode` | 1 байт | uint8 | Код операции (например, 0xDA для передачи данных о состоянии лифта) | +| `data` | N байт | массив | Полезная нагрузка | +| `crc16` | 2 байта| uint16 | CRC-16 контрольная сумма | +| `sync2` | 1 байт | uint8 | Конечный байт синхронизации, значение `0xBB` | + +### **Общая длина пакета**: +``` +Длина = 1 (sync1) + 1 (size) + 1 (opcode) + N (data) + 2 (crc16) + 1 (sync2) = 6 + N байт +``` + +--- + +## **3. Поля пакета** + +### **3.1. `sync1` и `sync2`** +- Используются для синхронизации начала и конца пакета +- Значения: `0xAA` и `0xBB` соответственно + +### **3.2. `size`** +- Определяет длину поля `data` +- Допустимые значения: от 0 до 255 + +### **3.3. `opcode`** +- Код операции, определяющий тип данных в поле `data` +- Примеры: + - `0xDA` — данные о состоянии лифта + - `0xDB` — команда от компьютера к микроконтроллеру + - `0xDC` — подтверждение приёма + - `0xDD` — запрос состояния + - и т.д. + +### **3.4. `data`** +- Полезная нагрузка, формат которой зависит от `opcode` +- Для `opcode: 0xDA` — строка вида `#STM:L%d:R%d:A%d:S%d:M%d:E#\r\n` + +### **3.5. `crc16`** +- CRC-16 контрольная сумма, рассчитанная по следующим параметрам: + +| Параметр | Значение | +|------------|------------------| +| Название | CRC-16 CCITT | +| Полином | `0x1021` (`x^16 + x^12 + x^5 + 1`) | +| Начальное значение | `0xFFFF` | +| Реверс входных данных | Нет | +| Реверс результата | Нет | +| Выходное XOR (`XorOut`)| `0x0000` | +| Контрольное значение (`"123456789"`) | `0x29B1` | +| Максимальная длина блока | 4095 байт (32767 бит) | + +--- + +## **4. Пример пакета** + +### **4.1. Исходные данные** +```c +opcode = 0xDA +data = "#STM:L10:R3:A1:S4:M0:E#\r\n" +``` + +### **4.2. Шаги формирования пакета** + +1. Вычислить длину `data`: `len(data) = 24` +2. Установить `size = 24` +3. Вычислить CRC16 от последовательности: `opcode + data` +4. Сформировать пакет: + +``` +[0xAA] [0x18] [0xDA] [data bytes...] [CRC16_H] [CRC16_L] [0xBB] +``` + +--- + + diff --git a/protocol_lib/src/client.rs b/protocol_lib/src/client.rs new file mode 100644 index 0000000..6dc855f --- /dev/null +++ b/protocol_lib/src/client.rs @@ -0,0 +1,74 @@ +use log::{debug, error, warn}; + +use crate::mu_frame::MUFrame; +use std::time::Duration; + +/// Количество попыток установить соединение c MCU +const HANDSHAKE_ATTEMPTS: u8 = 5; + +pub struct HostClient { + serial_port: Box, +} + +impl HostClient { + /// Создание соединения host - интерфейсная плата over UART + pub fn connect( + port_name: &str, + baudrate: u32, + timeout: Duration, + ) -> Result { + let serial_port = serialport::new(port_name, baudrate) + .timeout(timeout) + .open() + .unwrap_or_else(|_| panic!("Unable to open serial port: {port_name}")); + + Self::try_handshake(serial_port) + } + + /// Попытка установить соединение с устройством + fn try_handshake(instance: Box) -> Result { + let mut attempts: u8 = 1; + + let mut client_connection = HostClient { + serial_port: instance, + }; + + // Цикл попыток установить соединение + 'handshake_loop: loop { + warn!("Handshake attempt: {attempts}"); + + let answer = client_connection.send_request("hello"); + + if let Ok(response) = answer { + warn!("Response from device: {response}"); + + if response.as_bytes() == b"Hi!\r\n" { + return Ok(client_connection); + } + } else { + error!("Handshake: {}", answer.clone().unwrap_err()); + } + + attempts += 1; + + if attempts > HANDSHAKE_ATTEMPTS { + break 'handshake_loop; + } + } + Err("Handshake failed!".to_string()) + } + + /// Отправка запроса на устройство, возврат полученного отклика + pub fn send_request(&mut self, request: &str) -> Result { + let mut frame = MUFrame::new(); + frame + .set_data(format!("{}{}", request, "\n").as_bytes().to_vec()) + .map_err(|e| e.to_string())?; + crate::send_proto_message(frame, &mut self.serial_port)?; + + let new_frame = + crate::recv_proto_message(&mut self.serial_port).map_err(|e| e.to_string())?; + + Ok(String::from_utf8(new_frame.get_data().to_vec()).map_err(|e| e.to_string())?) + } +} diff --git a/protocol_lib/src/lib.rs b/protocol_lib/src/lib.rs new file mode 100644 index 0000000..17692fa --- /dev/null +++ b/protocol_lib/src/lib.rs @@ -0,0 +1,84 @@ +pub mod client; +mod mu_frame; + +use crate::mu_frame::MUFrame; + +use log::trace; +use std::{ + io::{Read, Write}, + thread, +}; + +/// Задержка перед приемом отклика от интерфейсной платы +const ANSWER_DELAY_MS: u64 = 500; + +/// Отправка сообщения +fn send_proto_message(data: MUFrame, mut writer: Writer) -> Result<(), String> { + let bytes = data.serialize(); + writer.write_all(&bytes).map_err(|e| e.to_string())?; + + // Время для составления ответа + thread::sleep(std::time::Duration::from_millis(ANSWER_DELAY_MS)); + + Ok(()) +} + +/// Прием сообщения +fn recv_proto_message(mut reader: Reader) -> Result { + let mut raw_frame = Vec::new(); + let mut read_buffer = [0; 256]; + + // Чтение отклика от интерфейсной платы + reader.read(&mut read_buffer).map_err(|e| e.to_string())?; + + trace!("Received raw message: {read_buffer:?}"); + + // Сборка сообщения + let prefix = read_buffer[0]; + raw_frame.push(prefix); + + let payload_length = read_buffer[1]; + raw_frame.push(payload_length); + + let opcode = read_buffer[2]; + raw_frame.push(opcode); + + // 3[prefix, payload_length, opcode] + payload_length + let end_of_data_idx = 3 + payload_length as usize; + + let payload = &read_buffer[3..end_of_data_idx]; + raw_frame.extend_from_slice(payload); + + let crc_low = read_buffer[end_of_data_idx]; + raw_frame.push(crc_low); + let crc_high = read_buffer[1 + end_of_data_idx]; + raw_frame.push(crc_high); + + let postfix = read_buffer[2 + end_of_data_idx]; + raw_frame.push(postfix); + + let frame = MUFrame::deserialize(&raw_frame).map_err(|e| e.to_string())?; + + Ok(frame) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_send_and_recv() { + let mut frame_to_send = MUFrame::new(); + frame_to_send + .set_data(b"get server_info\n".to_vec()) + .unwrap(); + + let mut buf = Vec::new(); + + send_proto_message(frame_to_send.clone(), &mut buf).unwrap(); + + let received_frame = recv_proto_message(&buf[..]).unwrap(); + assert_eq!(received_frame.get_data(), frame_to_send.get_data()); + assert_eq!(received_frame, frame_to_send); + } +} diff --git a/protocol_lib/src/mu_frame.rs b/protocol_lib/src/mu_frame.rs new file mode 100644 index 0000000..e8e9eaa --- /dev/null +++ b/protocol_lib/src/mu_frame.rs @@ -0,0 +1,225 @@ +use std::fmt::Display; + +const SYNC1: u8 = 0xAA; +const SYNC2: u8 = 0xBB; +const MAX_DATA_SIZE: u8 = u8::MAX; +const CONSOLE_OPCODE: u8 = 0xC0; + +/// Пакет данных протокола "МЮ" +/// +/// +/// ## Пример +/// ```ignore +/// let mut frame_to_send = MUFrame::new(); +/// frame_to_send +/// .set_data(b"get server_info\n".to_vec())?; +/// let raw_bytes = frame_to_send.serialize(); +/// ``` +/// + +#[derive(Debug, PartialEq, Clone)] +pub struct MUFrame { + prefix: u8, + length: u8, + opcode: u8, + data: Vec, + crc_low: u8, + crc_high: u8, + suffix: u8, +} + +impl MUFrame { + pub fn new() -> Self { + Self { + prefix: SYNC1, + length: 0, + opcode: CONSOLE_OPCODE, + data: Vec::with_capacity(MAX_DATA_SIZE as usize), + crc_low: 0x00, + crc_high: 0x00, + suffix: SYNC2, + } + } + + pub(crate) fn get_data(&self) -> &Vec { + &self.data + } + + /// Загрузка данных в фрейм, вычисление CRC и длины + pub(crate) fn set_data(&mut self, data: Vec) -> Result<(), String> { + if data.len() > MAX_DATA_SIZE as usize || data.is_empty() { + return Err("Data too long".to_string()); + } + + if !data.is_ascii() { + return Err("Bad encoding".to_string()); + } + + self.length = data.len() as u8; + self.data = data; + let crc_value = self.calculate_src(); + self.crc_low = crc_value as u8; + self.crc_high = (crc_value >> 8) as u8; + + Ok(()) + } + + /// Десериализация данных из буфера + pub(crate) fn deserialize(data: &[u8]) -> Result { + let mut frame = Self::new(); + frame.prefix = data[0]; + frame.length = data[1]; + frame.opcode = data[2]; + + frame.data = data[3..3 + frame.length as usize].to_vec(); + + frame.crc_low = data[3 + frame.length as usize]; + frame.crc_high = data[4 + frame.length as usize]; + + frame.suffix = data[5 + frame.length as usize]; + + frame.invalidate_frame()?; + + Ok(frame) + } + + /// Сериализация данных + pub(crate) fn serialize(&self) -> Vec { + let mut result = Vec::with_capacity(5 + self.length as usize); + result.push(self.prefix); + result.push(self.length); + result.push(self.opcode); + result.extend(self.data.iter()); + + result.push(self.crc_low); + result.push(self.crc_high); + result.push(self.suffix); + result + } + + /// Проверка валидности фрейма + fn invalidate_frame(&self) -> Result<(), String> { + if !self.is_prefix_correct() { + return Err("Bad prefix".to_string()); + } + if !self.is_postfix_correct() { + return Err("Bad postfix".to_string()); + } + + let crc_value = (self.crc_high as u16) << 8 | self.crc_low as u16; + if !self.is_crc_valid(crc_value) { + return Err("Bad CRC".to_string()); + } + + if !self.data.is_ascii() { + return Err("Bad encoding".to_string()); + } + + Ok(()) + } + + /// Вычисление CRC + fn calculate_src(&self) -> u16 { + const CRC16_MU: crc::Crc = crc::Crc::::new(&crc::CRC_16_IBM_3740); + + let mut crc_data = Vec::with_capacity(self.length as usize + 1); + crc_data.push(self.opcode); + crc_data.extend(self.data.iter()); + + CRC16_MU.checksum(&crc_data) + } + + /// Проверка соответствия фактического CRC посылки с принятым + fn is_crc_valid(&self, crc: u16) -> bool { + let calculated_crc = self.calculate_src(); + calculated_crc == crc + } + + /// Проверка корректности префикса (SYNC1) + fn is_prefix_correct(&self) -> bool { + self.prefix == SYNC1 + } + + /// Проверка корректности постфикса (SYNC2) + fn is_postfix_correct(&self) -> bool { + self.suffix == SYNC2 + } +} + +impl Display for MUFrame { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "MU Message: \n 1.Opcode={}, \n 2.Data length={} \n 3.Payload={:?} \n 4.CRC={}", + self.opcode, + self.length, + self.data, + (self.crc_high as u16) << 8 | self.crc_low as u16 + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_check_crc_calculation() { + let mut frame = MUFrame::new(); + frame + .set_data(b"#STM:L16:R16:A0:S0:M0:E#\r\n\0".to_vec()) + .unwrap(); + assert!(frame.is_crc_valid(0x2D52)); + } + + #[test] + fn test_serialize() { + let mut frame = MUFrame::new(); + + frame + .set_data(b"#STM:L0:R16:A1:S0:M0:E#\r\n\0".to_vec()) + .unwrap(); + + let serialized_vec = frame.serialize(); + assert_eq!(serialized_vec.len(), 6 + frame.length as usize); + + assert_eq!( + serialized_vec, + vec![ + 0xAA, 0x1A, 0xC0, 0x23, 0x53, 0x54, 0x4D, 0x3A, 0x4C, 0x30, 0x3A, 0x52, 0x31, 0x36, + 0x3A, 0x41, 0x31, 0x3A, 0x53, 0x30, 0x3A, 0x4D, 0x30, 0x3A, 0x45, 0x23, 0x0D, 0x0A, + 0x00, 0x80, 0x77, 0xBB + ] + ); + } + + #[test] + fn test_deserialize() { + let serialized_vec = vec![ + 0xAA, 0x1A, 0xC0, 0x23, 0x53, 0x54, 0x4D, 0x3A, 0x4C, 0x30, 0x3A, 0x52, 0x31, 0x36, + 0x3A, 0x41, 0x31, 0x3A, 0x53, 0x30, 0x3A, 0x4D, 0x30, 0x3A, 0x45, 0x23, 0x0D, 0x0A, + 0x00, 0x80, 0x77, 0xBB, + ]; + let frame = MUFrame::deserialize(&serialized_vec).unwrap(); + assert!(frame.is_prefix_correct()); + assert!(frame.is_postfix_correct()); + assert!(frame.is_crc_valid(0x7780)); + assert_eq!(frame.opcode, CONSOLE_OPCODE); + assert_eq!(frame.data, b"#STM:L0:R16:A1:S0:M0:E#\r\n\0"); + } + + #[test] + fn test_frame_invalidation() { + let serialized_vec = vec![ + 0xAA, 0x1B, 0xC0, 0x23, 0x53, 0x54, 0x4D, 0x3A, 0x4C, 0x31, 0x36, 0x3A, 0x52, 0x31, + 0x36, 0x3A, 0x41, 0x31, 0x3A, 0x53, 0x32, 0x3A, 0x4D, 0x30, 0x3A, 0x45, 0x23, 0x0D, + 0x0A, 0x00, 0xBB, 0xB6, 0xBB, + ]; + let frame = MUFrame::deserialize(&serialized_vec).unwrap(); + assert!(frame.is_prefix_correct()); + assert!(frame.is_postfix_correct()); + assert!(frame.is_crc_valid(0xB6BB)); + assert_eq!(frame.opcode, CONSOLE_OPCODE); + assert_eq!(frame.data, b"#STM:L16:R16:A1:S2:M0:E#\r\n\0"); + frame.invalidate_frame().unwrap(); + } +} diff --git a/rk_nku_configs/nku_scheme.toml b/rk_nku_configs/nku_scheme.toml new file mode 100644 index 0000000..5d79403 --- /dev/null +++ b/rk_nku_configs/nku_scheme.toml @@ -0,0 +1,63 @@ +# [table name] должно соответствовать названию соответствующего параметра в ПО для интерфейсной платы +# name - текстовое название параметра (используется в TUI меню) +# possible_values - список возможных значений параметра +# default - значение по умолчанию (одно из possible_values) +# current - актуальное текущее значение параметра (одно из possible_values). Если не указано или некорректно, то используется значение по умолчанию + +[groupnumber] +name = "Номер в группе" +possible_values = [ + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + "11", + "12", + "13", + "14", + "15", +] +default = "0" +current = "6" + +[soundvolume] +name = "Громкость звука" +possible_values = ["0%", "25%", "50%", "75%", "100%"] +default = "50%" +current = "100%" + +[musicvolume] +name = "Громкость музыки" +possible_values = ["0%", "25%", "50%", "75%", "100%"] +default = "0%" +current = "100%" + +[loadcapacity] +name = "Грузоподъемность" +possible_values = [ + "СКРЫТО", + "240кг 3чел.", + "320кг 4чел.", + "400кг 5чел.", + "450кг 6чел.", + "525кг 7чел.", + "630кг 8чел.", + "800кг 10чел.", + "800кг 11чел.", + "1000кг 13чел.", + "1150кг 15чел.", + "1275кг 16чел.", + "1275кг 17чел.", + "1425кг 18чел.", + "1600кг 20чел.", + "1600кг 21чел.", +] +default = "СКРЫТО" +current = "240кг 3чел." diff --git a/rk_nku_configs/rk3399_scheme.toml b/rk_nku_configs/rk3399_scheme.toml new file mode 100644 index 0000000..a380e65 --- /dev/null +++ b/rk_nku_configs/rk3399_scheme.toml @@ -0,0 +1,17 @@ +# [table name] должно соответствовать названию соответствующего параметра в ПО для интерфейсной платы +# name - текстовое название параметра (используется в TUI меню) +# possible_values - список возможных значений параметра +# default - значение по умолчанию (одно из possible_values) +# current - актуальное текущее значение параметра (одно из possible_values). Если не указано или некорректно, то используется значение по умолчанию + +[device] +name = "Имя порта" +possible_values = ["/dev/ttyS4"] +default = "/dev/ttyS4" +current = "/dev/ttyS4" + +[baudrate] +name = "Скорость передачи данных" +possible_values = ["9600", "38400", "115200"] +default = "115200" +current = "115200" diff --git a/rk_nku_menu/Cargo.toml b/rk_nku_menu/Cargo.toml new file mode 100644 index 0000000..c7ca31f --- /dev/null +++ b/rk_nku_menu/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rk_nku_menu" +version = "0.1.0" +edition = "2024" +authors.workspace = true + +[dependencies] +enigo = "0.5.0" +sysfs_gpio = { version = "0.6.2" } +log = { workspace = true } +env_logger = { workspace = true } +config_lib = { path = "../config_lib" } +menu_tui = { path = "../menu_tui" } diff --git a/rk_nku_menu/src/main.rs b/rk_nku_menu/src/main.rs new file mode 100644 index 0000000..ee8962f --- /dev/null +++ b/rk_nku_menu/src/main.rs @@ -0,0 +1,133 @@ +use config_lib::device_config::DeviceConfig; +use enigo::{Direction::Click, Enigo, Key, Keyboard, Settings}; +use log::{debug, error}; +use menu_tui::menu_rendering::{MainMenuStates, show_main_dialog}; +use std::sync::atomic::AtomicBool; +use std::time::Duration; +use sysfs_gpio::{Direction, Edge, Pin}; + +/// ### Путь к файлу-схемы параметров устройства +const NKU_DEVICE_CONFIG_PATH: &str = "rk_nku_configs/nku_scheme.toml"; + +/// Расчёт номера пина в порядке SYSFS +/// ``` +/// bank = 0; // GPIO0_B5 => 0, bank ∈ [0,4] +/// group = 1; // GPIO0_B5 => 1, group ∈ {(A=0), (B=1), (C=2), (D=3)} +/// X = 5; // GPIO0_B5 => 5, X ∈ [0,7] +/// number = group * 8 + X = 1 * 8 + 5 = 13; +/// pin = bank * 32 + number = 0 * 32 + 13 = 13; +/// ``` + +/// SYSFS номер порта кнопки ввода +const S1_BTN: u8 = 124; //124 +/// SYSFS номер порта кнопки выбора +const S2_BTN: u8 = 125; //125 +/// Максимальное время бездействия [c], после достижения которого происходит выход из меню +const IDLE_TIMEOUT_SEC: u32 = 30; + +fn main() -> Result<(), Box> { + env_logger::init(); + let mut device_config = DeviceConfig::create_parameter_list(NKU_DEVICE_CONFIG_PATH)?; + + static BTN_PRESSED: AtomicBool = AtomicBool::new(false); + static STOP_FLAG: AtomicBool = AtomicBool::new(false); + + // Открытие потоков для обслуживания EXT прерываний + let down_irq_thread = std::thread::spawn(move || { + let input = Pin::new(S2_BTN as u64); + debug!("down_irq_thread launched!"); + input.with_exported(|| { + input.set_direction(Direction::In)?; + input.set_edge(Edge::RisingEdge)?; + let mut poller = input.get_poller()?; + + while !STOP_FLAG.load(std::sync::atomic::Ordering::SeqCst) { + if let Some(pin_value) = poller.poll(1000)? { + debug!("{}", format!("Pin value: {}", pin_value)); + if pin_value == 1 { + let mut enigo = Enigo::new(&Settings::default()).unwrap(); + enigo.key(Key::DownArrow, Click).unwrap(); + BTN_PRESSED.store(true, std::sync::atomic::Ordering::SeqCst); + std::thread::sleep(Duration::from_millis(500)); + debug!("DownArrow pressed"); + } + } + } + Ok(()) + }) + }); + + let return_irq_thread = std::thread::spawn(move || { + let input = Pin::new(S1_BTN as u64); + debug!("return_irq_thread launched!"); + input.with_exported(|| { + input.set_direction(Direction::In)?; + input.set_edge(Edge::RisingEdge)?; + let mut poller = input.get_poller()?; + + while !STOP_FLAG.load(std::sync::atomic::Ordering::SeqCst) { + if let Some(pin_value) = poller.poll(1000)? { + debug!("{}", format!("Pin value: {}", pin_value)); + if pin_value == 1 { + let mut enigo = Enigo::new(&Settings::default()).unwrap(); + enigo.key(Key::Return, Click).unwrap(); + BTN_PRESSED.store(true, std::sync::atomic::Ordering::SeqCst); + std::thread::sleep(Duration::from_millis(500)); + debug!("Return pressed"); + } + } + } + + Ok(()) + }) + }); + + // Поток TUI меню + std::thread::spawn(move || { + loop { + match show_main_dialog(&mut device_config) { + Ok(MainMenuStates::ConfigurationState) => { + debug!("Configuration state"); + continue; + } + Ok(MainMenuStates::ExitState) => { + debug!("The user has chosen to exit..."); + break; + } + Err(e) => { + error!("Menu error: {}", e); + break; + } + } + } + + STOP_FLAG.store(true, std::sync::atomic::Ordering::SeqCst); + debug!("The user has chosen to exit..."); + }); + + // Главный поток - проверка бездействия + let mut idle_counter: u32 = 0; + while !STOP_FLAG.load(std::sync::atomic::Ordering::SeqCst) { + if BTN_PRESSED.load(std::sync::atomic::Ordering::SeqCst) { + idle_counter = 0; + BTN_PRESSED.store(false, std::sync::atomic::Ordering::SeqCst); + } else { + idle_counter += 1; + if idle_counter > IDLE_TIMEOUT_SEC * 10 { + STOP_FLAG.store(true, std::sync::atomic::Ordering::SeqCst); + debug!("Idle timeout has been reached, exiting..."); + } + } + std::thread::sleep(Duration::from_millis(100)); + } + + if down_irq_thread.join().is_err() { + debug!("Unable to stop down_irq_thread"); + } + + if return_irq_thread.join().is_err() { + debug!("Unable to stop return_irq_thread"); + } + + return Ok(()); +} diff --git a/rk_nku_sync/Cargo.toml b/rk_nku_sync/Cargo.toml new file mode 100644 index 0000000..6f925f6 --- /dev/null +++ b/rk_nku_sync/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rk_nku_sync" +version = "0.1.0" +edition = "2024" +authors.workspace = true + +[dependencies] +protocol_lib = { path = "../protocol_lib" } +config_lib = { path = "../config_lib" } +clap = { workspace = true } +log = { workspace = true } +env_logger = { workspace = true } diff --git a/rk_nku_sync/src/main.rs b/rk_nku_sync/src/main.rs new file mode 100644 index 0000000..9a7c3b3 --- /dev/null +++ b/rk_nku_sync/src/main.rs @@ -0,0 +1,128 @@ +mod nku_client; +mod serial_config; + +use std::str::FromStr; + +use clap::Parser; +use log::{error, warn}; + +/// Количество попыток выполнить запрос +const REQUEST_ATTEMPTS: u8 = 5; + +#[derive(Parser)] +#[command(author = "Akimov Dmitry MU LLC", name = "nku_sync", version = "0.1.0", about, long_about = None)] +struct Args { + /// Тип команды: pull - запрос сохраненных в устройстве настроек, push - отправка новых настроек + #[arg(short = 'm', long = "mode")] + mode: CommandMode, +} + +fn main() -> Result<(), String> { + let args = Args::parse(); + + env_logger::init(); + warn!("rk_nku_sync> command mode: {:?}", args.mode); + + let mut nku_client = nku_client::NkuClient::new()?; + + match args.mode { + CommandMode::Pull => pull_parameters(&mut nku_client)?, + CommandMode::Push => push_parameters(&mut nku_client)?, + } + + Ok(()) +} + +/// Отправка настроек на устройство +fn push_parameters(client: &mut nku_client::NkuClient) -> Result<(), String> { + // Цикл попыток установить соединение + let mut attempts: u8 = 1; + 'push_request_loop: loop { + warn!("Push request attempt: {attempts}"); + if client.push_parameters_to_device().is_ok() { + break 'push_request_loop; + } + let result = client.push_parameters_to_device(); + + if result.is_ok() { + break 'push_request_loop; + } + + error!( + "{}", + format!("Failed to push parameters: {result:?}").as_str() + ); + + attempts += 1; + + if attempts > REQUEST_ATTEMPTS { + return Err("Push request failed!".to_string()); + } + } + + // Запуск стриминга данных от станции + attempts = 1; + 'start_streaming_loop: loop { + warn!("Start streaming attempt: {attempts}"); + + let result = client.start_elevator_data_streaming(nku_client::StreamingMode::OnChangeMode); + + if result.is_ok() { + break 'start_streaming_loop; + } + error!( + "{}", + format!("Failed to push parameters: {result:?}").as_str() + ); + attempts += 1; + + if attempts > REQUEST_ATTEMPTS { + return Err("Start streaming failed!".to_string()); + } + } + + Ok(()) +} + +/// Получение настроек, сохраненных в устройстве +fn pull_parameters(client: &mut nku_client::NkuClient) -> Result<(), String> { + let mut attempts: u8 = 1; + // Цикл попыток установить соединение + 'pull_request_loop: loop { + warn!("Pull request attempt: {attempts}"); + + let result = client.pull_parameters_from_device(); + if result.is_ok() { + break 'pull_request_loop; + } + error!( + "{}", + format!("Failed to push parameters: {result:?}").as_str() + ); + + attempts += 1; + + if attempts > REQUEST_ATTEMPTS { + return Err("Pull request failed!".to_string()); + } + } + + Ok(()) +} + +#[derive(Clone, Debug)] +enum CommandMode { + Pull, + Push, +} + +impl FromStr for CommandMode { + type Err = String; + fn from_str(s: &str) -> Result { + match s { + "pull" => Ok(CommandMode::Pull), + "push" => Ok(CommandMode::Push), + _ => Err(format!("Unknown command mode: {s}")), + } + } +} diff --git a/rk_nku_sync/src/nku_client.rs b/rk_nku_sync/src/nku_client.rs new file mode 100644 index 0000000..6a903ab --- /dev/null +++ b/rk_nku_sync/src/nku_client.rs @@ -0,0 +1,124 @@ +use crate::serial_config::SerialPortConfig; +use config_lib::device_config::DeviceConfig; +use log::{debug, info}; +use protocol_lib::client::HostClient; + +const BOARD_RESPONSE_TIMEOUT_MS: std::time::Duration = std::time::Duration::from_millis(5000); +const SERIAL_PORT_CONFIG_PATH: &str = "rk_nku_configs/rk3399_scheme.toml"; +const NKU_DEVICE_CONFIG_PATH: &str = "rk_nku_configs/nku_scheme.toml"; + +#[derive(Clone)] +/// Режимы стриминга данных от устройства +pub enum StreamingMode { + /// Без стриминга + __SilentMode = 0, + /// Отправка данных, в случае изменения их состояния (и наличия) + OnChangeMode = 1, + /// Отправка данных периодически, с заданным в настройках (periodicity) периодом + __PeriodicMode = 2, + /// Отправка данных по требованию + __OnDemandMode = 3, +} + +pub struct NkuClient { + nku_client: HostClient, + nku_config: DeviceConfig, +} + +impl NkuClient { + pub fn new() -> Result { + // Чтение параметров последовательного порта + let serial_config = SerialPortConfig::new(SERIAL_PORT_CONFIG_PATH)?; + + let nku_config = DeviceConfig::create_parameter_list(NKU_DEVICE_CONFIG_PATH)?; + debug!("Parameters list: {:#?}", nku_config.get_parameters_names()); + + let port_name = serial_config.get_serial_name(); + let baudrate = serial_config.get_serial_baudrate(); + + debug!("Serial config: {port_name}, {baudrate}"); + + let nku_client = HostClient::connect(&port_name, baudrate, BOARD_RESPONSE_TIMEOUT_MS)?; + + info!("Connection with IMv has been established!"); + + Ok(Self { + nku_client, + nku_config, + }) + } + + /// ### Запрос начала стриминга данных со станции управления + pub fn start_elevator_data_streaming(&mut self, mode: StreamingMode) -> Result { + let response_from_mcu = self + .nku_client + .send_request(format!("set mode {}", mode.clone() as u8).as_str())?; + if !response_from_mcu.contains(format!("mode: {}", mode as u8).as_str()) { + return Err("START STREAMING> invalid response".into()); + } + Ok(response_from_mcu) + } + + /// ### Запрос сохраненных в устройстве настроек + pub fn pull_parameters_from_device(&mut self) -> Result<(), String> { + let parameters_list = self.nku_config.get_parameters_names()?; + + for parameter in parameters_list { + let request_string = format!("get {parameter}"); + debug!("PULL> sending request: {request_string}"); + let response_from_mcu = self.nku_client.send_request(&request_string)?; + debug!("PULL> response from MCU: {response_from_mcu}"); + + let parameter_value = + NkuClient::extract_parameter_value(¶meter, response_from_mcu)?; + self.nku_config + .set_parameter_value_using_index(¶meter, parameter_value)?; + } + self.nku_config.save_parameters_values()?; + Ok(()) + } + + /// Извлечение значения параметра из отклика от MCU + fn extract_parameter_value( + parameter_name: &str, + response_from_mcu: String, + ) -> Result { + // Ожидается ответ в формате "parameter_name:value" + let tokens = response_from_mcu.split(':').collect::>().to_vec(); + + if tokens.len() != 2 { + return Err(format!( + "Failed to parse response from MCU: {response_from_mcu}" + )); + } + + if tokens[0] != parameter_name { + return Err(format!( + "Failed to parse response from MCU: {response_from_mcu}" + )); + } + + tokens[1].trim().parse::().map_err(|e| e.to_string()) + } + + /// Отправка новых настроек на устройство для последующего сохранения + pub fn push_parameters_to_device(&mut self) -> Result<(), String> { + let parameters_list = self.nku_config.get_parameters_names()?; + + for parameter in parameters_list { + let parameter_value = self + .nku_config + .get_parameter_index_using_value(¶meter)?; + let request_string = format!("set {parameter} {parameter_value}"); + debug!("PUSH> sending request: {request_string}"); + let response_from_mcu = self.nku_client.send_request(&request_string)?; + + debug!("PUSH> response from MCU: {response_from_mcu}"); + if !response_from_mcu.contains(format!("{parameter}: {parameter_value}").as_str()) { + return Err("PUSH> invalid response".into()); + } + } + + Ok(()) + } +} diff --git a/rk_nku_sync/src/serial_config.rs b/rk_nku_sync/src/serial_config.rs new file mode 100644 index 0000000..c40313e --- /dev/null +++ b/rk_nku_sync/src/serial_config.rs @@ -0,0 +1,38 @@ +use config_lib::device_config::DeviceConfig; +use log::debug; + +pub struct SerialPortConfig { + serial_name: String, + serial_baudrate: u32, +} + +impl SerialPortConfig { + /// Чтение параметров последовательного порта из файла-схемы TOML + pub fn new(path_to_scheme: &str) -> Result { + let nku_serial_parameters = DeviceConfig::create_parameter_list(path_to_scheme)?; + + let serial_name = nku_serial_parameters.get_parameter_value("device")?; + let serial_baudrate = nku_serial_parameters + .get_parameter_value("baudrate")? + .parse::() + .map_err(|e: std::num::ParseIntError| e.to_string())?; + + debug!("serial port_name: {}", serial_name); + debug!("serial baudrate: {}", serial_baudrate); + + Ok(SerialPortConfig { + serial_name, + serial_baudrate, + }) + } + + /// Чтение имени последовательного порта из конфига + pub fn get_serial_name(&self) -> String { + self.serial_name.clone() + } + + /// Чтение скорости последовательного порта из конфига + pub fn get_serial_baudrate(&self) -> u32 { + self.serial_baudrate + } +}