diff --git a/Cargo.lock b/Cargo.lock index 39bdbf9a..bda59813 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,9 +22,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -37,15 +37,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "bit_field" @@ -84,9 +84,15 @@ checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" [[package]] name = "bitflags" -version = "2.10.0" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block-buffer" @@ -99,9 +105,9 @@ dependencies = [ [[package]] name = "built" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" dependencies = [ "chrono", "git2", @@ -109,15 +115,15 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "cc" -version = "1.2.53" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "jobserver", @@ -139,9 +145,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.43" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "num-traits", @@ -151,9 +157,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.54" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" dependencies = [ "clap_builder", "clap_derive", @@ -180,9 +186,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -192,18 +198,18 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.65" +version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "430b4dc2b5e3861848de79627b2bedc9f3342c7da5173a14eaa5d0f8dc18ae5d" +checksum = "97bf4965940c2382204c0ded6dd3dd48c0c4e872f1e76fb1bf94f45991a2cb6a" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" dependencies = [ "heck", "proc-macro2", @@ -213,15 +219,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "core-foundation-sys" @@ -284,41 +290,62 @@ dependencies = [ ] [[package]] -name = "digest" -version = "0.10.7" +name = "defmt" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "a6e524506490a1953d237cb87b1cfc1e46f88c18f10a22dfe0f507dc6bfc7f7f" dependencies = [ - "block-buffer", - "crypto-common", + "bitflags 1.3.2", + "defmt-macros", ] [[package]] -name = "displaydoc" -version = "0.2.5" +name = "defmt-macros" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "f0a27770e9c8f719a79d8b638281f4d828f77d8fd61e0bd94451b9b85e576a0b" dependencies = [ + "defmt-parser", + "proc-macro-error2", "proc-macro2", "quote", "syn", ] [[package]] -name = "dmidecode" +name = "defmt-parser" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ecc81f210f04aaadfeb5aa067b7d9095ea2af9c0e140ef56b8a3f3381ec4db" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dmidecode" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a898086945b8e1c1e12f0996773ae65442d84f8dcafd5ab88a4c80b1da3d3008" dependencies = [ - "bitflags", + "bitflags 2.13.0", "uuid", ] [[package]] name = "embed-resource" -version = "3.0.6" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" +checksum = "c31a88c8d26de40ed18fe748c547845aa39de1db3afd958f8cb91579f3644bcb" dependencies = [ "cc", "memchr", @@ -330,9 +357,9 @@ dependencies = [ [[package]] name = "env_filter" -version = "0.1.4" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" dependencies = [ "log", "regex", @@ -340,9 +367,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.8" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" dependencies = [ "anstream", "anstyle", @@ -359,9 +386,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fnv" @@ -369,15 +396,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - [[package]] name = "framework_lib" version = "0.6.4" @@ -401,8 +419,10 @@ dependencies = [ "num-traits", "nvml-wrapper", "plain", + "rand", "regex", "rusb", + "sha1", "sha2", "spin 0.10.0", "uefi", @@ -436,9 +456,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -451,9 +471,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -461,15 +481,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -478,15 +498,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -495,21 +515,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -519,7 +539,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -547,15 +566,14 @@ dependencies = [ [[package]] name = "git2" -version = "0.20.3" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2b37e2f62729cdada11f0e6b3b6fe383c69c29fc619e391223e12856af308c" +checksum = "ddddbf932745a6be37109b6112d3ee09696106f848449069d3a57bba937ab82e" dependencies = [ - "bitflags", + "bitflags 2.13.0", "libc", "libgit2-sys", "log", - "url", ] [[package]] @@ -569,9 +587,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -581,22 +599,22 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hidapi" -version = "2.6.4" +version = "2.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "565dd4c730b8f8b2c0fb36df6be12e5470ae10895ddcc4e9dcfbfb495de202b0" +checksum = "c78dadfc12f865bc3fcac3897e64533b930737ceb9ef245c8277de98d0b010e9" dependencies = [ "cc", "cfg-if", "libc", "pkg-config", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -616,119 +634,17 @@ dependencies = [ "cc", ] -[[package]] -name = "icu_collections" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" -dependencies = [ - "displaydoc", - "potential_utf", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" - -[[package]] -name = "icu_properties" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" - -[[package]] -name = "icu_provider" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", @@ -742,10 +658,11 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "ccfe6121cbe750cf81efa362d85c0bde7ea298ec43092d3a193baca59cdbd634" dependencies = [ + "defmt", "jiff-static", "log", "portable-atomic", @@ -755,9 +672,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "e165e897f662d428f3cd3828a919dbe067c2d42bb1031eede74ef9d27ecdedd2" dependencies = [ "proc-macro2", "quote", @@ -776,11 +693,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] @@ -795,15 +713,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.180" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libgit2-sys" -version = "0.18.3+1.9.2" +version = "0.18.5+1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" +checksum = "005d6ae6eac1912906073e069f7db60b1fa98e052a68227824afe3e3a1c59ca2" dependencies = [ "cc", "libc", @@ -835,9 +753,9 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.23" +version = "1.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d118bbf3771060e7311cc7bb0545b01d08a8b4a7de949198dec1fa0ca1c0f7" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" dependencies = [ "cc", "libc", @@ -845,12 +763,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "litemap" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" - [[package]] name = "lock_api" version = "0.4.14" @@ -862,15 +774,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "nix" @@ -878,7 +790,7 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cfg-if", "cfg_aliases", "libc", @@ -968,7 +880,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d5c6c0ef9702176a570f06ad94f3198bc29c524c8b498f1b9346e1b1bdcbb3a" dependencies = [ - "bitflags", + "bitflags 2.13.0", "libloading", "nvml-wrapper-sys", "static_assertions", @@ -978,18 +890,18 @@ dependencies = [ [[package]] name = "nvml-wrapper-sys" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd23dbe2eb8d8335d2bce0299e0a07d6a63c089243d626ca75b770a962ff49e6" +checksum = "6b4d594420fcda43b1c2c4bd44d48974aa3c7a9ab2cbf10dc18e35265767bf0b" dependencies = [ "libloading", ] [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -997,29 +909,17 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain" @@ -1029,33 +929,55 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" dependencies = [ "portable-atomic", ] [[package]] -name = "potential_utf" -version = "0.1.4" +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" dependencies = [ - "zerovec", + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -1082,9 +1004,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.43" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -1095,11 +1017,40 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom", +] + [[package]] name = "regex" -version = "1.12.2" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -1109,9 +1060,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1120,9 +1071,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "rusb" @@ -1157,9 +1108,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -1193,13 +1144,24 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sha2" version = "0.10.9" @@ -1213,21 +1175,15 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "spin" @@ -1244,12 +1200,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - [[package]] name = "static_assertions" version = "1.1.0" @@ -1270,26 +1220,15 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -1301,11 +1240,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -1321,30 +1260,20 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "tinystr" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" -dependencies = [ - "displaydoc", - "zerovec", -] - [[package]] name = "toml" -version = "0.9.11+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" dependencies = [ "indexmap", "serde_core", @@ -1357,33 +1286,33 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.5+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ "serde_core", ] [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.6+spec-1.1.0" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucs2" @@ -1400,7 +1329,7 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71fe9058b73ee2b6559524af9e33199c13b2485ddbf3ad1181b68051cdc50c17" dependencies = [ - "bitflags", + "bitflags 2.13.0", "cfg-if", "log", "ptr_meta", @@ -1427,7 +1356,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f64fe59e11af447d12fd60a403c74106eb104309f34b4c6dbce6e927d97da9d" dependencies = [ - "bitflags", + "bitflags 2.13.0", "uguid", ] @@ -1439,27 +1368,9 @@ checksum = "0c8352f8c05e47892e7eaf13b34abd76a7f4aeaf817b716e88789381927f199c" [[package]] name = "unicode-ident" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8_iter" -version = "1.0.4" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "utf8parse" @@ -1469,9 +1380,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" [[package]] name = "vcpkg" @@ -1507,18 +1418,18 @@ dependencies = [ [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -1529,9 +1440,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1539,9 +1450,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", @@ -1552,9 +1463,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] @@ -1682,22 +1593,13 @@ 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", + "windows-targets", ] [[package]] @@ -1709,35 +1611,20 @@ dependencies = [ "windows-link", ] -[[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_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "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", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] [[package]] @@ -1758,36 +1645,18 @@ 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_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_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" @@ -1800,48 +1669,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[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_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_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_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" @@ -1850,9 +1695,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.14" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" [[package]] name = "winreg" @@ -1866,9 +1711,9 @@ dependencies = [ [[package]] name = "winresource" -version = "0.1.29" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cdfa8da4b111045a5e47c7c839e6c5e11c942de1309bc624393ed5d87f89c6" +checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" dependencies = [ "toml", "version_check", @@ -1876,9 +1721,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.51.0" +version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" [[package]] name = "wmi" @@ -1890,7 +1735,7 @@ dependencies = [ "futures", "log", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows", "windows-core", ] @@ -1907,103 +1752,20 @@ dependencies = [ "syn", ] -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "yoke" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - [[package]] name = "zerocopy" -version = "0.8.47" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerotrie" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.2" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", diff --git a/EXAMPLES_ADVANCED.md b/EXAMPLES_ADVANCED.md index 98847228..0a457d5c 100644 --- a/EXAMPLES_ADVANCED.md +++ b/EXAMPLES_ADVANCED.md @@ -28,6 +28,50 @@ Keyboard backlight: 0% Keyboard backlight: 0% ``` +## Battery + +### Smart Battery + +Get smart battery information by talking to the battery directly through SMBUS. + +Allows unsealing the battery and reading privileged registers - this needs the +secret key and is not available to end-users. Implemented for Framework +internal analysis of RMA units. +End-users, please press enter to skip unseal key. + +``` +> sudo framework_tool --smartbattery +Enter unseal key in hex (e.g. 04143672), or press enter to skip: +Device Name: FRANEDA +Manuf Name: ATC +Serial Num: 0188 +Manuf Date: 2025-11-27 +Chemistry: LION +FW Version: Device=0x0045 FW=09.03 Build=0x4900 +Mode: 0x6001 +Temperature: 32.6C +Voltage: 17.719V + Cell 1: 4.430V + Cell 2: 4.429V + Cell 3: 4.431V + Cell 4: 4.428V +Current: 0.000A (avg 0.000A) +Charge: 98% (4776 / 4920 mAh) +Design: 4640mAh @ 15.640V +Bat Status: 0x00E0 [FC (Fully Charged), DSG (Discharging), INIT (Initialization)] +Cycle Count: 76 + +=== Battery Health Analysis === + +Status: HEALTHY + No issues detected. Battery is operating normally. + +Summary: + Cycle count: 76 + Capacity: 4920 / 4640 mAh (106%) + Current cell balance: 3mV spread +``` + ## PD ### Check PD state diff --git a/framework_lib/Cargo.toml b/framework_lib/Cargo.toml index 9a6a63c7..c3e10209 100644 --- a/framework_lib/Cargo.toml +++ b/framework_lib/Cargo.toml @@ -26,6 +26,7 @@ built = { version = "0.8", features = ["chrono", "git2"] } [dependencies] lazy_static = "1.4.0" dmidecode = { version = "1", default-features = false } +sha1 = { version = "0.10.6", default-features = false } sha2 = { version = "0.10.8", default-features = false, features = [ "force-soft" ] } regex = { version = "1.11.1", default-features = false } num = { version = "0.4", default-features = false } @@ -45,6 +46,7 @@ uefi-raw = "0.13" plain = "0.2.3" [target.'cfg(windows)'.dependencies] +rand = { version = "0.9", default-features = false, features = ["std", "std_rng", "thread_rng"] } wmi = "~0.17.3" env_logger = "0.11" clap = { version = "4.5", features = ["derive", "cargo"] } @@ -56,8 +58,9 @@ winreg = "0.55.0" nvml-wrapper = { version = "0.11.0", optional = true } [target.'cfg(unix)'.dependencies] +rand = { version = "0.9", default-features = false, features = ["std", "std_rng", "thread_rng"] } libc = "0.2.155" -nix = { version = "0.30", features = ["ioctl", "user"] } +nix = { version = "0.30", features = ["ioctl", "user", "term"] } env_logger = "0.11" clap = { version = "4.5", features = ["derive", "cargo"] } clap-num = { version = "1.2.0" } @@ -81,6 +84,7 @@ features = [ "Win32_Devices_Properties", "Win32_Storage_EnhancedStorage", "Win32_System_Threading", + "Win32_System_Console", "Win32_UI_Shell_PropertiesSystem" ] diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 6c8ffba0..76ac18f6 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -2,6 +2,7 @@ //! This way we can use it in the regular OS commandline tool on Linux and Windows, //! as well as on the UEFI shell tool. use std::io; +use std::path::PathBuf; use clap::builder::TypedValueParser; use clap::error::ErrorKind; @@ -53,6 +54,14 @@ struct ClapCli { #[arg(long)] power: bool, + /// Show detailed smart battery information, or load from dump file + #[arg(long, value_name = "FILE")] + smartbattery: Option>, + + /// Authenticate smart battery (requires unseal and auth keys) + #[arg(long)] + smartbattery_auth: bool, + /// Print thermal information (Temperatures and Fan speed) #[arg(long)] thermal: bool, @@ -495,6 +504,10 @@ pub fn parse(args: &[String]) -> Cli { device: args.device, compare_version: args.compare_version, power: args.power, + smartbattery: args + .smartbattery + .map(|opt| opt.map(|x| x.into_os_string().into_string().unwrap())), + smartbattery_auth: args.smartbattery_auth, thermal: args.thermal, sensors: args.sensors, fansetduty, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index f2b0c693..a56d917b 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -56,6 +56,8 @@ use crate::nvme; use crate::os_specific; use crate::parade_retimer; use crate::power; +#[cfg(not(feature = "uefi"))] +use crate::smart_battery::SmartBattery; use crate::smbios; use crate::smbios::ConfigDigit0; use crate::smbios::{get_smbios, is_framework}; @@ -186,6 +188,8 @@ pub struct Cli { pub device: Option, pub compare_version: Option, pub power: bool, + pub smartbattery: Option>, + pub smartbattery_auth: bool, pub thermal: bool, pub sensors: bool, pub fansetduty: Option<(Option, u32)>, @@ -1593,6 +1597,33 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { print_board_ids(&ec); } else if args.power { return power::get_and_print_power_info(&ec); + } else if let Some(smartbattery_arg) = &args.smartbattery { + #[cfg(not(feature = "uefi"))] + { + use crate::smart_battery::{display_battery_data, BatteryData}; + use std::path::Path; + + match smartbattery_arg { + Some(file_path) => { + // Load from file + match BatteryData::read_from_file(Path::new(file_path)) { + Ok(data) => display_battery_data(&data), + Err(e) => eprintln!("Failed to read battery dump: {}", e), + } + } + None => { + // Read from actual battery + let bat = SmartBattery::new(); + print_err(bat.dump_data(&ec)); + } + } + } + } else if args.smartbattery_auth { + #[cfg(not(feature = "uefi"))] + { + let bat = SmartBattery::new(); + print_err(bat.interactive_authenticate(&ec)); + } } else if args.thermal { power::print_thermal(&ec); } else if args.sensors { diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 0b2701c2..b3bd2274 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -40,6 +40,8 @@ pub fn parse(args: &[String]) -> Cli { device: None, compare_version: None, power: false, + smartbattery: None, + smartbattery_auth: false, thermal: false, sensors: false, fansetduty: None, diff --git a/framework_lib/src/lib.rs b/framework_lib/src/lib.rs index 5da49205..64d6cb5f 100644 --- a/framework_lib/src/lib.rs +++ b/framework_lib/src/lib.rs @@ -61,6 +61,8 @@ pub mod fw_uefi; mod os_specific; pub mod parade_retimer; pub mod power; +#[cfg(not(feature = "uefi"))] +pub mod smart_battery; pub mod smbios; mod util; diff --git a/framework_lib/src/smart_battery.rs b/framework_lib/src/smart_battery.rs new file mode 100644 index 00000000..dc1f2822 --- /dev/null +++ b/framework_lib/src/smart_battery.rs @@ -0,0 +1,1897 @@ +// Smart Battery System (SBS) protocol support +// Reference: https://www.ti.com/lit/ug/sluua43a/sluua43a.pdf +// Based on driver/battery/smart.c and include/battery_smart.h from EC codebase + +use alloc::string::String; +use alloc::vec::Vec; + +use rand::random; +use sha1::{Digest, Sha1}; +use std::fs::File; +use std::io::{self, BufRead, BufReader, Write}; +use std::path::Path; +use std::thread; +use std::time::Duration; + +use crate::chromium_ec::i2c_passthrough::*; +use crate::chromium_ec::{CrosEc, EcError, EcResult}; + +/// Raw battery data that can be dumped to a file and loaded later +#[derive(Default)] +pub struct BatteryData { + pub mode: u16, + pub serial_num: u16, + pub manufacture_date: u16, + pub temperature: u16, + pub voltage: u16, + pub cell_voltage1: u16, + pub cell_voltage2: u16, + pub cell_voltage3: u16, + pub cell_voltage4: u16, + pub cycle_count: u16, + pub device_name: String, + pub manufacturer_name: String, + pub current: u16, + pub avg_current: u16, + pub rel_state_of_charge: u16, + pub abs_state_of_charge: u16, + pub remaining_capacity: u16, + pub full_charge_capacity: u16, + pub charging_current: u16, + pub charging_voltage: u16, + pub battery_status: u16, + pub design_capacity: u16, + pub design_voltage: u16, + pub device_chemistry: String, + // Unsealed data (may be empty if not unsealed) + pub state_of_health: Vec, + pub operation_status: u32, + pub safety_alert: u32, + pub safety_status: u32, + pub pf_alert: u32, + pub pf_status: u32, + pub lifetime1: Vec, + pub lifetime2: Vec, + pub lifetime3: Vec, + pub lifetime4: Vec, + pub lifetime5: Vec, + pub firmware_version: Vec, // MAC 0x0002 response (up to 14 bytes) +} + +impl BatteryData { + /// Write raw data to a file in a simple text format + pub fn write_to_file(&self, path: &Path) -> io::Result<()> { + let mut file = File::create(path)?; + writeln!(file, "# Smart Battery Raw Data Dump")?; + writeln!(file, "# Format: key=hex_value or key=string")?; + writeln!(file)?; + writeln!(file, "mode={:04X}", self.mode)?; + writeln!(file, "serial_num={:04X}", self.serial_num)?; + writeln!(file, "manufacture_date={:04X}", self.manufacture_date)?; + writeln!(file, "temperature={:04X}", self.temperature)?; + writeln!(file, "voltage={:04X}", self.voltage)?; + writeln!(file, "cell_voltage1={:04X}", self.cell_voltage1)?; + writeln!(file, "cell_voltage2={:04X}", self.cell_voltage2)?; + writeln!(file, "cell_voltage3={:04X}", self.cell_voltage3)?; + writeln!(file, "cell_voltage4={:04X}", self.cell_voltage4)?; + writeln!(file, "cycle_count={:04X}", self.cycle_count)?; + writeln!(file, "device_name={}", self.device_name)?; + writeln!(file, "manufacturer_name={}", self.manufacturer_name)?; + writeln!(file, "current={:04X}", self.current)?; + writeln!(file, "avg_current={:04X}", self.avg_current)?; + writeln!(file, "rel_state_of_charge={:04X}", self.rel_state_of_charge)?; + writeln!(file, "abs_state_of_charge={:04X}", self.abs_state_of_charge)?; + writeln!(file, "remaining_capacity={:04X}", self.remaining_capacity)?; + writeln!( + file, + "full_charge_capacity={:04X}", + self.full_charge_capacity + )?; + writeln!(file, "charging_current={:04X}", self.charging_current)?; + writeln!(file, "charging_voltage={:04X}", self.charging_voltage)?; + writeln!(file, "battery_status={:04X}", self.battery_status)?; + writeln!(file, "design_capacity={:04X}", self.design_capacity)?; + writeln!(file, "design_voltage={:04X}", self.design_voltage)?; + writeln!(file, "device_chemistry={}", self.device_chemistry)?; + writeln!( + file, + "state_of_health={}", + hex_encode(&self.state_of_health) + )?; + writeln!(file, "operation_status={:08X}", self.operation_status)?; + writeln!(file, "safety_alert={:08X}", self.safety_alert)?; + writeln!(file, "safety_status={:08X}", self.safety_status)?; + writeln!(file, "pf_alert={:08X}", self.pf_alert)?; + writeln!(file, "pf_status={:08X}", self.pf_status)?; + writeln!(file, "lifetime1={}", hex_encode(&self.lifetime1))?; + writeln!(file, "lifetime2={}", hex_encode(&self.lifetime2))?; + writeln!(file, "lifetime3={}", hex_encode(&self.lifetime3))?; + writeln!(file, "lifetime4={}", hex_encode(&self.lifetime4))?; + writeln!(file, "lifetime5={}", hex_encode(&self.lifetime5))?; + writeln!( + file, + "firmware_version={}", + hex_encode(&self.firmware_version) + )?; + Ok(()) + } + + /// Read raw data from a dump file + pub fn read_from_file(path: &Path) -> io::Result { + let file = File::open(path)?; + let reader = BufReader::new(file); + let mut data = BatteryData::default(); + + for line in reader.lines() { + let line = line?; + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + if let Some((key, value)) = line.split_once('=') { + match key { + "mode" => data.mode = u16::from_str_radix(value, 16).unwrap_or(0), + "serial_num" => data.serial_num = u16::from_str_radix(value, 16).unwrap_or(0), + "manufacture_date" => { + data.manufacture_date = u16::from_str_radix(value, 16).unwrap_or(0) + } + "temperature" => data.temperature = u16::from_str_radix(value, 16).unwrap_or(0), + "voltage" => data.voltage = u16::from_str_radix(value, 16).unwrap_or(0), + "cell_voltage1" => { + data.cell_voltage1 = u16::from_str_radix(value, 16).unwrap_or(0) + } + "cell_voltage2" => { + data.cell_voltage2 = u16::from_str_radix(value, 16).unwrap_or(0) + } + "cell_voltage3" => { + data.cell_voltage3 = u16::from_str_radix(value, 16).unwrap_or(0) + } + "cell_voltage4" => { + data.cell_voltage4 = u16::from_str_radix(value, 16).unwrap_or(0) + } + "cycle_count" => data.cycle_count = u16::from_str_radix(value, 16).unwrap_or(0), + "device_name" => data.device_name = value.to_string(), + "manufacturer_name" => data.manufacturer_name = value.to_string(), + "current" => data.current = u16::from_str_radix(value, 16).unwrap_or(0), + "avg_current" => data.avg_current = u16::from_str_radix(value, 16).unwrap_or(0), + "rel_state_of_charge" => { + data.rel_state_of_charge = u16::from_str_radix(value, 16).unwrap_or(0) + } + "abs_state_of_charge" => { + data.abs_state_of_charge = u16::from_str_radix(value, 16).unwrap_or(0) + } + "remaining_capacity" => { + data.remaining_capacity = u16::from_str_radix(value, 16).unwrap_or(0) + } + "full_charge_capacity" => { + data.full_charge_capacity = u16::from_str_radix(value, 16).unwrap_or(0) + } + "charging_current" => { + data.charging_current = u16::from_str_radix(value, 16).unwrap_or(0) + } + "charging_voltage" => { + data.charging_voltage = u16::from_str_radix(value, 16).unwrap_or(0) + } + "battery_status" => { + data.battery_status = u16::from_str_radix(value, 16).unwrap_or(0) + } + "design_capacity" => { + data.design_capacity = u16::from_str_radix(value, 16).unwrap_or(0) + } + "design_voltage" => { + data.design_voltage = u16::from_str_radix(value, 16).unwrap_or(0) + } + "device_chemistry" => data.device_chemistry = value.to_string(), + "state_of_health" => data.state_of_health = hex_decode(value), + "operation_status" => { + data.operation_status = u32::from_str_radix(value, 16).unwrap_or(0) + } + "safety_alert" => { + data.safety_alert = u32::from_str_radix(value, 16).unwrap_or(0) + } + "safety_status" => { + data.safety_status = u32::from_str_radix(value, 16).unwrap_or(0) + } + "pf_alert" => data.pf_alert = u32::from_str_radix(value, 16).unwrap_or(0), + "pf_status" => data.pf_status = u32::from_str_radix(value, 16).unwrap_or(0), + "lifetime1" => data.lifetime1 = hex_decode(value), + "lifetime2" => data.lifetime2 = hex_decode(value), + "lifetime3" => data.lifetime3 = hex_decode(value), + "lifetime4" => data.lifetime4 = hex_decode(value), + "lifetime5" => data.lifetime5 = hex_decode(value), + "firmware_version" => data.firmware_version = hex_decode(value), + _ => {} // Ignore unknown keys for forward compatibility + } + } + } + Ok(data) + } +} + +fn hex_encode(data: &[u8]) -> String { + data.iter().map(|b| format!("{:02X}", b)).collect() +} + +fn hex_decode(s: &str) -> Vec { + (0..s.len()) + .step_by(2) + .filter_map(|i| u8::from_str_radix(&s[i..i + 2], 16).ok()) + .collect() +} + +#[repr(u16)] +enum SmartBatReg { + Mode = 0x03, + Temp = 0x08, + Voltage = 0x09, + Current = 0x0A, + AverageCurrent = 0x0B, + RelativeStateOfCharge = 0x0D, + AbsoluteStateOfCharge = 0x0E, + RemainingCapacity = 0x0F, + FullChargeCapacity = 0x10, + ChargingCurrent = 0x14, + ChargingVoltage = 0x15, + BatteryStatus = 0x16, + CycleCount = 0x17, + DesignCapacity = 0x18, + DesignVoltage = 0x19, + ManufactureDate = 0x1B, + SerialNum = 0x1C, + ManufacturerName = 0x20, + DeviceName = 0x21, + DeviceChemistry = 0x22, + Authenticate = 0x2F, + CellVoltage4 = 0x3C, + CellVoltage3 = 0x3D, + CellVoltage2 = 0x3E, + CellVoltage1 = 0x3F, +} + +/// ManufacturerAccess block registers +/// Needs unseal for access +/// On EC Console can read these with battmfgacc command if CONFIG_CMD_BATT_MFG_ACCESS +#[repr(u16)] +enum ManufReg { + SafetyAlert = 0x50, + SafetyStatus = 0x51, + PFAlert = 0x52, + PFStatus = 0x53, + OperationStatus = 0x54, + LifeTimeDataBlock1 = 0x60, + LifeTimeDataBlock2 = 0x61, + LifeTimeDataBlock3 = 0x62, + LifeTimeDataBlock4 = 0x63, + LifeTimeDataBlock5 = 0x64, + Soh = 0x77, +} + +/// Decode OperationStatus register bits (BQ40z50) +/// Based on TI bq40z50-R2 (SLUUA43A) and bq40z50-R3 (SLUUBU5A) datasheets +fn decode_operation_status(value: u32, is_r3: bool) -> Vec<&'static str> { + let mut flags = Vec::new(); + if value & (1 << 0) != 0 { + flags.push("PRES (System Present Low)"); + } + if value & (1 << 1) != 0 { + flags.push("DSG (Discharge FET On)"); + } + if value & (1 << 2) != 0 { + flags.push("CHG (Charge FET On)"); + } + if value & (1 << 3) != 0 { + flags.push("PCHG (Precharge FET On)"); + } + if value & (1 << 4) != 0 && is_r3 { + flags.push("ACTHR (Accumulated Charge Threshold)"); + } + if value & (1 << 5) != 0 { + flags.push("FUSE (Fuse Active)"); + } + if value & (1 << 6) != 0 && is_r3 { + flags.push("EMSHUT (Emergency FET Shutdown)"); + } + if value & (1 << 7) != 0 { + flags.push("BTP_INT (Battery Trip Point)"); + } + // Bits 8-9: Security mode (00=Reserved, 01=FullAccess, 10=Unsealed, 11=Sealed) + let sec_mode = (value >> 8) & 0x03; + match sec_mode { + 1 => flags.push("SEC=FullAccess"), + 2 => flags.push("SEC=Unsealed"), + 3 => flags.push("SEC=Sealed"), + _ => {} + } + if value & (1 << 10) != 0 { + flags.push("SDV (Shutdown Low Voltage)"); + } + if value & (1 << 11) != 0 { + flags.push("SS (Safety Status)"); + } + if value & (1 << 12) != 0 { + flags.push("PF (Permanent Failure)"); + } + if value & (1 << 13) != 0 { + flags.push("XDSG (Discharge Disabled)"); + } + if value & (1 << 14) != 0 { + flags.push("XCHG (Charge Disabled)"); + } + if value & (1 << 15) != 0 { + flags.push("SLEEP (Sleep Mode)"); + } + if value & (1 << 16) != 0 { + flags.push("SDM (Shutdown Command)"); + } + if value & (1 << 17) != 0 { + flags.push("LED (LED Display)"); + } + if value & (1 << 18) != 0 { + flags.push("AUTH (Authentication)"); + } + if value & (1 << 19) != 0 { + flags.push("AUTOCALM (Auto CC Offset)"); + } + if value & (1 << 20) != 0 { + flags.push("CAL (Calibration)"); + } + if value & (1 << 21) != 0 { + flags.push("CAL_OFFSET (Cal Offset)"); + } + if value & (1 << 22) != 0 { + flags.push("XL (400kHz Mode)"); + } + if value & (1 << 23) != 0 { + flags.push("SLEEPM (Sleep Cmd Active)"); + } + if value & (1 << 24) != 0 { + flags.push("INIT (Initialization)"); + } + if value & (1 << 25) != 0 { + flags.push("SMBLCAL (SMBus Cal)"); + } + if value & (1 << 26) != 0 { + flags.push("SLPAD (Sleep via Adapter)"); + } + if value & (1 << 27) != 0 { + flags.push("SLPCC (Sleep via CC)"); + } + if value & (1 << 28) != 0 { + flags.push("CB (Cell Balancing)"); + } + if value & (1 << 29) != 0 { + if is_r3 { + flags.push("DISCONN (System Disconnect)"); + } else { + flags.push("EMSHUT (Emergency Shutdown)"); + } + } + if value & (1 << 30) != 0 && is_r3 { + flags.push("PSSHUT (Power Saving Shutdown)"); + } + if value & (1 << 31) != 0 && is_r3 { + flags.push("IOSHUT (IO-Based Shutdown)"); + } + flags +} + +/// Decode SafetyAlert/SafetyStatus register bits (BQ40z50) +/// Based on TI bq40z50-R2 (SLUUA43A) and bq40z50-R3 (SLUUBU5A) datasheets +fn decode_safety_status(value: u32) -> Vec<&'static str> { + let mut flags = Vec::new(); + if value & (1 << 0) != 0 { + flags.push("CUV (Cell Under-Voltage)"); + } + if value & (1 << 1) != 0 { + flags.push("COV (Cell Over-Voltage)"); + } + if value & (1 << 2) != 0 { + flags.push("OCC1 (Over-Current Charge Tier1)"); + } + if value & (1 << 3) != 0 { + flags.push("OCC2 (Over-Current Charge Tier2)"); + } + if value & (1 << 4) != 0 { + flags.push("OCD1 (Over-Current Discharge Tier1)"); + } + if value & (1 << 5) != 0 { + flags.push("OCD2 (Over-Current Discharge Tier2)"); + } + if value & (1 << 6) != 0 { + flags.push("AOLD (Overload in Discharge)"); + } + if value & (1 << 7) != 0 { + flags.push("AOLDL (Overload in Discharge Latch)"); + } + if value & (1 << 8) != 0 { + flags.push("ASCC (Short-Circuit Charge)"); + } + if value & (1 << 9) != 0 { + flags.push("ASCCL (Short-Circuit Charge Latch)"); + } + if value & (1 << 10) != 0 { + flags.push("ASCD (Short-Circuit Discharge)"); + } + if value & (1 << 11) != 0 { + flags.push("ASCDL (Short-Circuit Discharge Latch)"); + } + if value & (1 << 12) != 0 { + flags.push("OTC (Over-Temp Charge)"); + } + if value & (1 << 13) != 0 { + flags.push("OTD (Over-Temp Discharge)"); + } + if value & (1 << 14) != 0 { + flags.push("CUVC (Cell Under-Voltage Compensated)"); + } + if value & (1 << 16) != 0 { + flags.push("OTF (Over-Temp FET)"); + } + if value & (1 << 18) != 0 { + flags.push("PTO (Precharge Timeout)"); + } + if value & (1 << 19) != 0 { + flags.push("PTOS (Precharge Timeout Suspend)"); + } + if value & (1 << 20) != 0 { + flags.push("CTO (Charge Timeout)"); + } + if value & (1 << 21) != 0 { + flags.push("CTOS (Charge Timeout Suspend)"); + } + if value & (1 << 22) != 0 { + flags.push("OC (Overcharge)"); + } + if value & (1 << 23) != 0 { + flags.push("CHGC (Overcharge Current)"); + } + if value & (1 << 24) != 0 { + flags.push("CHGV (Overcharge Voltage)"); + } + if value & (1 << 25) != 0 { + flags.push("PCHGC (Over Precharge Current)"); + } + if value & (1 << 26) != 0 { + flags.push("UTC (Under-Temp Charge)"); + } + if value & (1 << 27) != 0 { + flags.push("UTD (Under-Temp Discharge)"); + } + if value & (1 << 28) != 0 { + flags.push("COVL (Cell Over-Voltage Latch)"); + } + if value & (1 << 29) != 0 { + flags.push("OCDL (Over-Current Discharge Latch)"); + } + flags +} + +/// Decode PFAlert/PFStatus register bits (BQ40z50) +/// Based on TI bq40z50-R2 (SLUUA43A) and bq40z50-R3 (SLUUBU5A) datasheets +fn decode_pf_status(value: u32, is_r3: bool) -> Vec<&'static str> { + let mut flags = Vec::new(); + // Lower 16 bits (PFAlert 0x0052 / PFStatus 0x0053) + if value & (1 << 0) != 0 { + flags.push("SUV (Safety Cell Under-Voltage)"); + } + if value & (1 << 1) != 0 { + flags.push("SOV (Safety Cell Over-Voltage)"); + } + if value & (1 << 2) != 0 { + flags.push("SOCC (Safety Over-Current Charge)"); + } + if value & (1 << 3) != 0 { + flags.push("SOCD (Safety Over-Current Discharge)"); + } + if value & (1 << 4) != 0 { + flags.push("SOT (Safety Over-Temp Cell)"); + } + if value & (1 << 5) != 0 { + flags.push("COVL (Cell Over-Voltage Latch)"); + } + if value & (1 << 6) != 0 { + flags.push("SOTF (Safety Over-Temp FET)"); + } + if value & (1 << 7) != 0 { + flags.push("QIM (QMax Imbalance)"); + } + if value & (1 << 8) != 0 { + flags.push("CB (Cell Balancing Failure)"); + } + if value & (1 << 9) != 0 { + flags.push("IMP (Impedance Failure)"); + } + if value & (1 << 10) != 0 { + flags.push("CD (Coulomb Counter Failure)"); + } + if value & (1 << 11) != 0 { + flags.push("VIMR (Voltage Imbalance at Rest)"); + } + if value & (1 << 12) != 0 { + flags.push("VIMA (Voltage Imbalance Active)"); + } + if value & (1 << 13) != 0 { + flags.push("AOLDL (Overload in Discharge Latch)"); + } + if value & (1 << 14) != 0 { + flags.push("ASCCL (Short-Circuit Charge Latch)"); + } + if value & (1 << 15) != 0 { + flags.push("ASCDL (Short-Circuit Discharge Latch)"); + } + // Upper 16 bits + if value & (1 << 16) != 0 { + flags.push("CFETF (Charge FET Failure)"); + } + if value & (1 << 17) != 0 { + flags.push("DFETF (Discharge FET Failure)"); + } + if value & (1 << 18) != 0 { + flags.push("OCDL (Over-Current Discharge Latch)"); + } + if value & (1 << 19) != 0 { + flags.push("FUSE (Chemical Fuse Failure)"); + } + if value & (1 << 20) != 0 { + flags.push("AFER (AFE Register Failure)"); + } + if value & (1 << 21) != 0 { + flags.push("AFEC (AFE Communication Failure)"); + } + if value & (1 << 22) != 0 { + flags.push("2LVL (Second Level Safety)"); + } + if value & (1 << 23) != 0 { + flags.push("PTC (Open PTC Failure)"); + } + if value & (1 << 24) != 0 { + flags.push("IFC (Instruction Flash Checksum)"); + } + if value & (1 << 25) != 0 { + if is_r3 { + flags.push("FORCE (Manual PF)"); + } else { + flags.push("OPNCELL (Open Cell Voltage Connection)"); + } + } + if value & (1 << 26) != 0 { + flags.push("DFW (Data Flash Wearout)"); + } + if value & (1 << 28) != 0 { + flags.push("TS1 (Open Thermistor TS1)"); + } + if value & (1 << 29) != 0 { + flags.push("TS2 (Open Thermistor TS2)"); + } + if value & (1 << 30) != 0 { + flags.push("TS3 (Open Thermistor TS3)"); + } + if value & (1 << 31) != 0 { + flags.push("TS4 (Open Thermistor TS4)"); + } + flags +} + +/// Decode BatteryStatus register bits (SBS spec) +fn decode_battery_status(value: u16) -> Vec<&'static str> { + let mut flags = Vec::new(); + // Error code (bits 0-3) + match value & 0x0F { + 0 => {} + 1 => flags.push("EC=Busy"), + 3 => flags.push("EC=Unsupported"), + 4 => flags.push("EC=AccessDenied"), + 5 => flags.push("EC=Overflow"), + 7 => flags.push("EC=Unknown"), + _ => flags.push("EC=Reserved"), + } + if value & (1 << 4) != 0 { + flags.push("FD (Fully Discharged)"); + } + if value & (1 << 5) != 0 { + flags.push("FC (Fully Charged)"); + } + if value & (1 << 6) != 0 { + flags.push("DSG (Discharging)"); + } + if value & (1 << 7) != 0 { + flags.push("INIT (Initialization)"); + } + if value & (1 << 8) != 0 { + flags.push("RTA (Remaining Time Alarm)"); + } + if value & (1 << 9) != 0 { + flags.push("RCA (Remaining Capacity Alarm)"); + } + if value & (1 << 11) != 0 { + flags.push("TDA (Terminate Discharge Alarm)"); + } + if value & (1 << 12) != 0 { + flags.push("OTA (Over Temperature Alarm)"); + } + if value & (1 << 14) != 0 { + flags.push("TCA (Terminate Charge Alarm)"); + } + if value & (1 << 15) != 0 { + flags.push("OCA (Over-Charged Alarm)"); + } + flags +} + +/// Print status flags with hex value (for Safety/PF registers) +fn print_status_flags(label: &str, value: u32, flags: Vec<&str>) { + if value == 0 { + println!("{}: (OK)", label); + } else { + println!("{}: 0x{:08X}", label, value); + for flag in flags { + println!(" - {}", flag); + } + } +} + +/// Print operation status flags on multiple lines +fn print_operation_status(value: u32, is_r3: bool) { + let flags = decode_operation_status(value, is_r3); + println!("Operation Status: 0x{:08X}", value); + for flag in flags { + println!(" - {}", flag); + } +} + +/// Calculates the HMAC using TI's specific nested SHA-1 method. +/// Formula: SHA1( Key || SHA1( Key || Challenge ) ) +/// Note: TI batteries expect bytes in reversed order +fn calculate_ti_hmac(key: &[u8; 16], challenge: &[u8; 20]) -> [u8; 20] { + // Reverse challenge bytes as per TI spec + let mut challenge_rev = *challenge; + challenge_rev.reverse(); + + // 1. Inner Hash: SHA1( Key + Challenge_reversed ) + let mut inner_hasher = Sha1::new(); + inner_hasher.update(key); + inner_hasher.update(challenge_rev); + let inner_digest = inner_hasher.finalize(); + + // 2. Outer Hash: SHA1( Key + Inner_Digest ) + let mut outer_hasher = Sha1::new(); + outer_hasher.update(key); + outer_hasher.update(inner_digest); + let outer_digest = outer_hasher.finalize(); + + // Convert GenericArray to standard [u8; 20] and reverse for output + let mut result = [0u8; 20]; + result.copy_from_slice(&outer_digest); + result.reverse(); + result +} + +/// Reads a line from stdin without echoing (for sensitive input like keys) +#[cfg(unix)] +fn read_password() -> io::Result { + use nix::sys::termios::{self, LocalFlags, SetArg}; + use std::io::BufRead; + use std::os::fd::AsFd; + + let stdin = io::stdin(); + let fd = stdin.as_fd(); + + // Save original terminal settings + let original = termios::tcgetattr(fd)?; + + // Disable echo + let mut noecho = original.clone(); + noecho.local_flags.remove(LocalFlags::ECHO); + termios::tcsetattr(fd, SetArg::TCSANOW, &noecho)?; + + // Read the line + let mut input = String::new(); + let result = stdin.lock().read_line(&mut input); + + // Restore original settings + termios::tcsetattr(fd, SetArg::TCSANOW, &original)?; + + // Print newline since echo was disabled + println!(); + + result?; + Ok(input) +} + +/// Reads a line from stdin without echoing (for sensitive input like keys) +#[cfg(windows)] +fn read_password() -> io::Result { + use std::io::BufRead; + use windows::Win32::System::Console::{ + GetConsoleMode, GetStdHandle, SetConsoleMode, CONSOLE_MODE, ENABLE_ECHO_INPUT, + STD_INPUT_HANDLE, + }; + + unsafe { + let handle = GetStdHandle(STD_INPUT_HANDLE).map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("Failed to get stdin handle: {}", e), + ) + })?; + + let mut mode = CONSOLE_MODE::default(); + GetConsoleMode(handle, &mut mode).map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("Failed to get console mode: {}", e), + ) + })?; + + // Disable echo + let noecho = CONSOLE_MODE(mode.0 & !ENABLE_ECHO_INPUT.0); + SetConsoleMode(handle, noecho).map_err(|e| { + io::Error::new( + io::ErrorKind::Other, + format!("Failed to set console mode: {}", e), + ) + })?; + + // Read the line + let mut input = String::new(); + let result = io::stdin().lock().read_line(&mut input); + + // Restore original mode + let _ = SetConsoleMode(handle, mode); + + // Print newline since echo was disabled + println!(); + + result?; + Ok(input) + } +} + +pub struct SmartBattery { + i2c_port: u8, + i2c_addr: u16, +} + +impl Default for SmartBattery { + fn default() -> Self { + Self::new() + } +} + +impl SmartBattery { + pub fn new() -> Self { + SmartBattery { + // Same on all our Nuvoton ECs + i2c_port: 3, + // 0x0B 7-bit, 0x16 8-bit address + // Same for all our batteries, they use the same IC + i2c_addr: 0x16, + } + } + + fn unseal(&self, ec: &CrosEc, key1: u16, key2: u16) -> EcResult<()> { + i2c_write_block( + ec, + self.i2c_port, + self.i2c_addr >> 1, + 0x00, + &key2.to_le_bytes(), + )?; + i2c_write_block( + ec, + self.i2c_port, + self.i2c_addr >> 1, + 0x00, + &key1.to_le_bytes(), + )?; + Ok(()) + } + + fn seal(&self, ec: &CrosEc) -> EcResult<()> { + i2c_write_block(ec, self.i2c_port, self.i2c_addr >> 1, 0x00, &[0x30, 0x00])?; + Ok(()) + } + + fn read_i16(&self, ec: &CrosEc, addr: u16) -> EcResult { + let i2c_response = i2c_read(ec, self.i2c_port, self.i2c_addr >> 1, addr, 0x02)?; + i2c_response.is_successful()?; + Ok(u16::from_le_bytes([ + i2c_response.data[0], + i2c_response.data[1], + ])) + } + + /// Read a 32-bit value from a ManufacturerAccess block command (SMBus block format with length prefix) + fn read_i32(&self, ec: &CrosEc, addr: u16) -> EcResult { + // ManufacturerAccess block commands return data in SMBus block format: + // Byte 0: Length, Bytes 1-4: Data + let i2c_response = i2c_read(ec, self.i2c_port, self.i2c_addr >> 1, addr, 0x05)?; + i2c_response.is_successful()?; + let len = i2c_response.data[0]; + if len != 4 { + return Err(EcError::DeviceError(format!( + "Expected 4 bytes but got {} from register 0x{:02X}", + len, addr + ))); + } + Ok(u32::from_le_bytes([ + i2c_response.data[1], + i2c_response.data[2], + i2c_response.data[3], + i2c_response.data[4], + ])) + } + + fn read_string(&self, ec: &CrosEc, addr: u16) -> EcResult { + // SMBus strings are length-prefixed + let i2c_response = i2c_read(ec, self.i2c_port, self.i2c_addr >> 1, addr, 0x20)?; + i2c_response.is_successful()?; + // First byte is the returned string length + let str_bytes = &i2c_response.data[1..=(i2c_response.data[0] as usize)]; + Ok(String::from_utf8_lossy(str_bytes).to_string()) + } + + /// Read a block of bytes with expected length + fn read_bytes(&self, ec: &CrosEc, addr: u16, len: u16) -> EcResult> { + let i2c_response = i2c_read(ec, self.i2c_port, self.i2c_addr >> 1, addr, len + 1)?; + i2c_response.is_successful()?; + let actual_len = i2c_response.data[0]; + if actual_len != len as u8 { + return Err(EcError::DeviceError(format!( + "Expected {} bytes but got {} from register 0x{:02X}", + len, actual_len, addr + ))); + } + Ok(i2c_response.data[1..].to_vec()) + } + + /// Read a block of bytes, returning whatever length the device provides + fn read_block(&self, ec: &CrosEc, addr: u16, max_len: u16) -> EcResult> { + let i2c_response = i2c_read(ec, self.i2c_port, self.i2c_addr >> 1, addr, max_len + 1)?; + i2c_response.is_successful()?; + let actual_len = i2c_response.data[0] as usize; + Ok(i2c_response.data[1..=actual_len].to_vec()) + } + + /// Raw I2C read without SMBus block length prefix handling + fn read_raw(&self, ec: &CrosEc, addr: u16, len: u16) -> EcResult> { + let i2c_response = i2c_read(ec, self.i2c_port, self.i2c_addr >> 1, addr, len)?; + i2c_response.is_successful()?; + Ok(i2c_response.data.to_vec()) + } + + fn smbus_write_block(&self, ec: &CrosEc, reg: u8, data: &[u8]) -> EcResult<()> { + i2c_write_block(ec, self.i2c_port, self.i2c_addr >> 1, reg, data)?; + Ok(()) + } + + /// Read a ManufacturerAccess block command by writing the sub-command + /// to register 0x00 and reading the response from 0x44 + fn mac_read(&self, ec: &CrosEc, subcmd: u16, max_len: u16) -> EcResult> { + self.smbus_write_block(ec, 0x00, &subcmd.to_le_bytes())?; + self.read_block(ec, 0x44, max_len) + } + + /// Authenticate the battery using SHA-1 HMAC challenge-response + pub fn authenticate_battery(&self, ec: &CrosEc, auth_key: &[u8; 16]) -> EcResult { + // 1. Generate a random 20-byte challenge + let challenge: [u8; 20] = random(); + + println!("Step 1: Sending Challenge..."); + + // SMBus Block Write format: [Byte_Count, Data...] + // The register address is handled by smbus_write_block + let mut write_buf = Vec::new(); + write_buf.push(20); // Byte Count (0x14) + write_buf.extend_from_slice(&challenge); + + self.smbus_write_block(ec, SmartBatReg::Authenticate as u8, &write_buf)?; + + // 2. Wait for the gauge to calculate (Datasheet says 250ms) + println!("Step 2: Waiting 250ms..."); + thread::sleep(Duration::from_millis(250)); + + // 3. Calculate expected result locally while waiting + let expected_response = calculate_ti_hmac(auth_key, &challenge); + + // 4. Read Response from Authenticate register + // SMBus Block Read format: [Length] + [Data...] + println!("Step 3: Reading Response..."); + + // Read 21 bytes (1 byte length + 20 bytes signature) from Authenticate register + let raw_response = self.read_raw(ec, SmartBatReg::Authenticate as u16, 21)?; + + // 5. Parse and Compare + // First byte is the length, should be 20 + let response_len = raw_response[0] as usize; + if response_len != 20 { + return Err(EcError::DeviceError(format!( + "Expected 20-byte response, got {} bytes (first byte: 0x{:02X})", + response_len, raw_response[0] + ))); + } + + let device_response = &raw_response[1..21]; + + println!("Expected: {:02X?}", expected_response); + println!("Received: {:02X?}", device_response); + + if device_response == expected_response { + println!("SUCCESS: Battery is genuine."); + Ok(true) + } else { + println!("FAILURE: Signature mismatch."); + Ok(false) + } + } + + /// Interactive authentication - prompts for unseal key and authentication key + pub fn interactive_authenticate(&self, ec: &CrosEc) -> EcResult<()> { + // First, try to unseal the battery + println!("Some batteries require unsealing before authentication."); + print!("Unseal key in hex (e.g. 04143672), or enter to skip: "); + io::stdout() + .flush() + .map_err(|e| EcError::DeviceError(format!("Failed to flush stdout: {}", e)))?; + + let unseal_input = read_password() + .map_err(|e| EcError::DeviceError(format!("Failed to read key: {}", e)))?; + let unseal_input = unseal_input.trim(); + if !unseal_input.is_empty() { + let key: u32 = u32::from_str_radix(unseal_input, 16) + .map_err(|e| EcError::DeviceError(format!("Invalid unseal key: {}", e)))?; + println!("Unsealing battery..."); + self.unseal(ec, (key >> 16) as u16, key as u16)?; + // Wait a bit after unsealing + thread::sleep(Duration::from_millis(100)); + } + + // Now prompt for authentication key + print!("Auth key in hex (32 chars, e.g. 00112233...EEFF): "); + io::stdout() + .flush() + .map_err(|e| EcError::DeviceError(format!("Failed to flush stdout: {}", e)))?; + + let input_text = read_password() + .map_err(|e| EcError::DeviceError(format!("Failed to read key: {}", e)))?; + let input_text = input_text.trim(); + if input_text.is_empty() { + println!("No key provided, aborting."); + return Ok(()); + } + + if input_text.len() != 32 { + return Err(EcError::DeviceError(format!( + "Key must be 32 hex characters (16 bytes), got {} characters", + input_text.len() + ))); + } + + let mut auth_key = [0u8; 16]; + for i in 0..16 { + auth_key[i] = u8::from_str_radix(&input_text[i * 2..i * 2 + 2], 16).map_err(|e| { + EcError::DeviceError(format!("Invalid hex at position {}: {}", i * 2, e)) + })?; + } + + let result = self.authenticate_battery(ec, &auth_key)?; + + // Re-seal the battery if we unsealed it + if !unseal_input.is_empty() { + println!("Re-sealing battery..."); + self.seal(ec)?; + } + + match result { + true => println!("Authentication successful - battery is genuine."), + false => println!("Authentication failed - battery signature mismatch."), + } + + Ok(()) + } + + /// Print battery information interactively (prompts for unseal key) + pub fn dump_data(&self, ec: &CrosEc) -> EcResult<()> { + // Prompt for unseal key to access ManufacturerAccess data + print!("Enter unseal key in hex (e.g. 04143672), or press enter to skip: "); + io::stdout() + .flush() + .map_err(|e| EcError::DeviceError(format!("Failed to flush stdout: {}", e)))?; + let input_text = read_password() + .map_err(|e| EcError::DeviceError(format!("Failed to read key: {}", e)))?; + let input_text = input_text.trim(); + + let unseal_key = if input_text.is_empty() { + None + } else { + Some( + u32::from_str_radix(input_text, 16) + .map_err(|e| EcError::DeviceError(format!("Invalid key: {}", e)))?, + ) + }; + + let data = self.collect_data(ec, unseal_key)?; + display_battery_data(&data); + + Ok(()) + } + + /// Collect raw battery data into a struct (for dumping or analysis) + #[allow(clippy::field_reassign_with_default)] + pub fn collect_data(&self, ec: &CrosEc, unseal_key: Option) -> EcResult { + let mut data = BatteryData::default(); + + // Basic registers (no unseal required) + data.mode = self.read_i16(ec, SmartBatReg::Mode as u16)?; + data.serial_num = self.read_i16(ec, SmartBatReg::SerialNum as u16)?; + data.manufacture_date = self.read_i16(ec, SmartBatReg::ManufactureDate as u16)?; + data.temperature = self.read_i16(ec, SmartBatReg::Temp as u16)?; + data.voltage = self.read_i16(ec, SmartBatReg::Voltage as u16)?; + // Per datasheet: 0x3C=Cell4, 0x3D=Cell3, 0x3E=Cell2, 0x3F=Cell1 + data.cell_voltage1 = self.read_i16(ec, SmartBatReg::CellVoltage1 as u16)?; + data.cell_voltage2 = self.read_i16(ec, SmartBatReg::CellVoltage2 as u16)?; + data.cell_voltage3 = self.read_i16(ec, SmartBatReg::CellVoltage3 as u16)?; + data.cell_voltage4 = self.read_i16(ec, SmartBatReg::CellVoltage4 as u16)?; + + // Read firmware version (MAC 0x0002, available in SEALED mode) + data.firmware_version = self.mac_read(ec, 0x0002, 14).unwrap_or_default(); + data.cycle_count = self.read_i16(ec, SmartBatReg::CycleCount as u16)?; + data.device_name = self.read_string(ec, SmartBatReg::DeviceName as u16)?; + data.manufacturer_name = self.read_string(ec, SmartBatReg::ManufacturerName as u16)?; + data.current = self.read_i16(ec, SmartBatReg::Current as u16)?; + data.avg_current = self.read_i16(ec, SmartBatReg::AverageCurrent as u16)?; + data.rel_state_of_charge = self.read_i16(ec, SmartBatReg::RelativeStateOfCharge as u16)?; + data.abs_state_of_charge = self.read_i16(ec, SmartBatReg::AbsoluteStateOfCharge as u16)?; + data.remaining_capacity = self.read_i16(ec, SmartBatReg::RemainingCapacity as u16)?; + data.full_charge_capacity = self.read_i16(ec, SmartBatReg::FullChargeCapacity as u16)?; + data.charging_current = self.read_i16(ec, SmartBatReg::ChargingCurrent as u16)?; + data.charging_voltage = self.read_i16(ec, SmartBatReg::ChargingVoltage as u16)?; + data.battery_status = self.read_i16(ec, SmartBatReg::BatteryStatus as u16)?; + data.design_capacity = self.read_i16(ec, SmartBatReg::DesignCapacity as u16)?; + data.design_voltage = self.read_i16(ec, SmartBatReg::DesignVoltage as u16)?; + data.device_chemistry = self.read_string(ec, SmartBatReg::DeviceChemistry as u16)?; + + // ManufacturerAccess block reads (typically readable in SEALED mode) + data.state_of_health = self + .read_bytes(ec, ManufReg::Soh as u16, 4) + .unwrap_or_default(); + data.operation_status = self + .read_i32(ec, ManufReg::OperationStatus as u16) + .unwrap_or(0); + data.safety_alert = self.read_i32(ec, ManufReg::SafetyAlert as u16).unwrap_or(0); + data.safety_status = self + .read_i32(ec, ManufReg::SafetyStatus as u16) + .unwrap_or(0); + data.pf_alert = self.read_i32(ec, ManufReg::PFAlert as u16).unwrap_or(0); + data.pf_status = self.read_i32(ec, ManufReg::PFStatus as u16).unwrap_or(0); + data.lifetime1 = self + .read_bytes(ec, ManufReg::LifeTimeDataBlock1 as u16, 32) + .unwrap_or_default(); + data.lifetime2 = self + .read_bytes(ec, ManufReg::LifeTimeDataBlock2 as u16, 20) + .unwrap_or_default(); + data.lifetime3 = self + .read_block(ec, ManufReg::LifeTimeDataBlock3 as u16, 16) + .unwrap_or_default(); + data.lifetime4 = self + .read_block(ec, ManufReg::LifeTimeDataBlock4 as u16, 32) + .unwrap_or_default(); + data.lifetime5 = self + .read_block(ec, ManufReg::LifeTimeDataBlock5 as u16, 32) + .unwrap_or_default(); + + // Unseal for registers that need it (re-read with full access) + if let Some(key) = unseal_key { + self.unseal(ec, (key >> 16) as u16, key as u16)?; + + data.state_of_health = self.read_bytes(ec, ManufReg::Soh as u16, 4)?; + data.operation_status = self.read_i32(ec, ManufReg::OperationStatus as u16)?; + data.safety_alert = self.read_i32(ec, ManufReg::SafetyAlert as u16)?; + data.safety_status = self.read_i32(ec, ManufReg::SafetyStatus as u16)?; + data.pf_alert = self.read_i32(ec, ManufReg::PFAlert as u16)?; + data.pf_status = self.read_i32(ec, ManufReg::PFStatus as u16)?; + data.lifetime1 = self.read_bytes(ec, ManufReg::LifeTimeDataBlock1 as u16, 32)?; + data.lifetime2 = self.read_bytes(ec, ManufReg::LifeTimeDataBlock2 as u16, 20)?; + data.lifetime3 = self.read_block(ec, ManufReg::LifeTimeDataBlock3 as u16, 16)?; + data.lifetime4 = self.read_block(ec, ManufReg::LifeTimeDataBlock4 as u16, 32)?; + data.lifetime5 = self.read_block(ec, ManufReg::LifeTimeDataBlock5 as u16, 32)?; + + self.seal(ec)?; + } + + Ok(data) + } + + /// Dump battery data to a file + pub fn dump_to_file(&self, ec: &CrosEc, path: &Path) -> EcResult<()> { + // Prompt for unseal key + print!("Enter unseal key in hex (e.g. 04143672), or press enter to skip: "); + io::stdout() + .flush() + .map_err(|e| EcError::DeviceError(format!("Failed to flush stdout: {}", e)))?; + let input_text = read_password() + .map_err(|e| EcError::DeviceError(format!("Failed to read key: {}", e)))?; + let input_text = input_text.trim(); + + let unseal_key = if input_text.is_empty() { + None + } else { + Some( + u32::from_str_radix(input_text, 16) + .map_err(|e| EcError::DeviceError(format!("Invalid key: {}", e)))?, + ) + }; + + let data = self.collect_data(ec, unseal_key)?; + data.write_to_file(path) + .map_err(|e| EcError::DeviceError(format!("Failed to write file: {}", e)))?; + + println!("Battery data saved to {}", path.display()); + Ok(()) + } +} + +/// Determine if the battery is bq40z50-R3 based on available data. +/// R3 returns 4-byte LifeTime3 (1×u32), R2 returns 16-byte (8×u16). +/// Defaults to R3 when no lifetime data is available. +fn is_r3(data: &BatteryData) -> bool { + // TODO: Use firmware_version device number once R2/R3 values are confirmed + data.lifetime3.len() != 16 +} + +/// Display decoded battery data from a BatteryData struct +pub fn display_battery_data(data: &BatteryData) { + // Identity + println!("Device Name: {}", data.device_name); + println!("Manuf Name: {}", data.manufacturer_name); + println!("Serial Num: {:04X}", data.serial_num); + let day = data.manufacture_date & 0x1F; + let month = (data.manufacture_date >> 5) & 0x0F; + let year = (data.manufacture_date >> 9) + 1980; + println!("Manuf Date: {:04}-{:02}-{:02}", year, month, day); + if !data.device_chemistry.is_empty() { + println!("Chemistry: {}", data.device_chemistry); + } + if data.firmware_version.len() >= 6 { + // MAC 0x0002 response: [subcmd_echo(2), device_num(2), fw_ver(2), build(2), ...] + let fw = &data.firmware_version; + let device_num = u16::from_le_bytes([fw[2], fw[3]]); + let fw_major = fw[5]; + let fw_minor = fw[4]; + let build = if fw.len() >= 8 { + format!(" Build=0x{:04X}", u16::from_le_bytes([fw[6], fw[7]])) + } else { + String::new() + }; + println!( + "FW Version: Device=0x{:04X} FW={:02}.{:02}{}", + device_num, fw_major, fw_minor, build + ); + } else if !data.firmware_version.is_empty() { + println!("FW Version: {}", hex_encode(&data.firmware_version)); + } + println!("Mode: 0x{:04X}", data.mode); + + // Dynamic state + let temp_c = (data.temperature as f32 / 10.0) - 273.15; + println!("Temperature: {:.1}C", temp_c); + + println!( + "Voltage: {}.{:03}V", + data.voltage / 1000, + data.voltage % 1000 + ); + println!( + " Cell 1: {}.{:03}V", + data.cell_voltage1 / 1000, + data.cell_voltage1 % 1000 + ); + println!( + " Cell 2: {}.{:03}V", + data.cell_voltage2 / 1000, + data.cell_voltage2 % 1000 + ); + println!( + " Cell 3: {}.{:03}V", + data.cell_voltage3 / 1000, + data.cell_voltage3 % 1000 + ); + println!( + " Cell 4: {}.{:03}V", + data.cell_voltage4 / 1000, + data.cell_voltage4 % 1000 + ); + + // Current + let current_ma = data.current as i16; + let avg_current_ma = data.avg_current as i16; + println!( + "Current: {:.3}A (avg {:.3}A)", + current_ma as f32 / 1000.0, + avg_current_ma as f32 / 1000.0 + ); + + // Charge state - check CAPACITY_MODE (bit 15 of mode) + let capacity_10mwh = data.mode & 0x8000 != 0; + if capacity_10mwh { + println!( + "Charge: {}% ({:.2} / {:.2} Wh)", + data.rel_state_of_charge, + data.remaining_capacity as f32 / 100.0, + data.full_charge_capacity as f32 / 100.0 + ); + println!( + "Design: {:.2}Wh @ {}.{:03}V", + data.design_capacity as f32 / 100.0, + data.design_voltage / 1000, + data.design_voltage % 1000 + ); + } else { + println!( + "Charge: {}% ({} / {} mAh)", + data.rel_state_of_charge, data.remaining_capacity, data.full_charge_capacity + ); + println!( + "Design: {}mAh @ {}.{:03}V", + data.design_capacity, + data.design_voltage / 1000, + data.design_voltage % 1000 + ); + } + + // Charging info (only if actually charging — DSG bit clear) + let is_discharging = data.battery_status & (1 << 6) != 0; + if data.charging_current > 0 && !is_discharging { + println!( + "Charging: {:.3}A @ {}.{:03}V", + data.charging_current as f32 / 1000.0, + data.charging_voltage / 1000, + data.charging_voltage % 1000 + ); + } + + // Battery status + let status_flags = decode_battery_status(data.battery_status); + if status_flags.is_empty() { + println!("Bat Status: 0x{:04X}", data.battery_status); + } else { + println!( + "Bat Status: 0x{:04X} [{}]", + data.battery_status, + status_flags.join(", ") + ); + } + + println!("Cycle Count: {}", data.cycle_count); + + // Unsealed data + if !data.state_of_health.is_empty() { + let soh = &data.state_of_health; + println!( + "StateOfHealth: {}mAh, {}.{:02}Wh", + u16::from_le_bytes([soh[0], soh[1]]), + u16::from_le_bytes([soh[2], soh[3]]) / 100, + u16::from_le_bytes([soh[2], soh[3]]) % 100, + ); + let r3 = is_r3(data); + print_operation_status(data.operation_status, r3); + print_status_flags( + "Safety Alert", + data.safety_alert, + decode_safety_status(data.safety_alert), + ); + print_status_flags( + "Safety Status", + data.safety_status, + decode_safety_status(data.safety_status), + ); + print_status_flags( + "PF Alert", + data.pf_alert, + decode_pf_status(data.pf_alert, r3), + ); + print_status_flags( + "PF Status", + data.pf_status, + decode_pf_status(data.pf_status, r3), + ); + + if data.lifetime1.len() >= 32 { + let lt1 = &data.lifetime1; + println!("LifeTime1:"); + println!( + " Cell 1 Max Voltage: {}mV", + u16::from_le_bytes([lt1[0], lt1[1]]) + ); + println!( + " Min Voltage: {}mV", + u16::from_le_bytes([lt1[8], lt1[9]]) + ); + println!( + " Cell 2 Max Voltage: {}mV", + u16::from_le_bytes([lt1[2], lt1[3]]) + ); + println!( + " Min Voltage: {}mV", + u16::from_le_bytes([lt1[10], lt1[11]]) + ); + println!( + " Cell 3 Max Voltage: {}mV", + u16::from_le_bytes([lt1[4], lt1[5]]) + ); + println!( + " Min Voltage: {}mV", + u16::from_le_bytes([lt1[12], lt1[13]]) + ); + println!( + " Cell 4 Max Voltage: {}mV", + u16::from_le_bytes([lt1[6], lt1[7]]) + ); + println!( + " Min Voltage: {}mV", + u16::from_le_bytes([lt1[14], lt1[15]]) + ); + println!( + " Max Delta Cell Voltage: {}mV", + u16::from_le_bytes([lt1[16], lt1[17]]) + ); + println!( + " Max Charge Current: {:.2}A", + u16::from_le_bytes([lt1[18], lt1[19]]) as f32 / 1000.0 + ); + println!( + " Max Discharge Current: {:.2}A", + i16::from_le_bytes([lt1[20], lt1[21]]).unsigned_abs() as f32 / 1000.0 + ); + println!( + " Max Avg Dsg Current: {:.2}A", + i16::from_le_bytes([lt1[22], lt1[23]]).unsigned_abs() as f32 / 1000.0 + ); + println!( + " Max Avg Dsg Power: {:.1}W", + u16::from_le_bytes([lt1[24], lt1[25]]) as f32 / 1000.0 + ); + println!(" Max Temp Cell: {}C", lt1[26]); + println!(" Min Temp Cell: {}C", lt1[27]); + println!(" Max Delta Cell Temp: {}C", lt1[28]); + println!(" Max Temp Int Sensor: {}C", lt1[29]); + println!(" Min Temp Int Sensor: {}C", lt1[30]); + println!(" Max Temp FET: {}C", lt1[31]); + } + + if data.lifetime2.len() >= 20 { + let lt2 = &data.lifetime2; + println!("LifeTime2:"); + println!(" No. of Shutdowns: {}", lt2[0]); + println!(" No. of Partial Resets: {}", lt2[1]); + println!(" No. of Full Resets: {}", lt2[2]); + println!(" No. of WDT Resets: {}", lt2[3]); + let cb1 = u32::from_le_bytes([lt2[4], lt2[5], lt2[6], lt2[7]]); + let cb2 = u32::from_le_bytes([lt2[8], lt2[9], lt2[10], lt2[11]]); + let cb3 = u32::from_le_bytes([lt2[12], lt2[13], lt2[14], lt2[15]]); + let cb4 = u32::from_le_bytes([lt2[16], lt2[17], lt2[18], lt2[19]]); + println!( + " CB Time Cell 1: {}s ({:.1}h)", + cb1, + cb1 as f64 / 3600.0 + ); + println!( + " CB Time Cell 2: {}s ({:.1}h)", + cb2, + cb2 as f64 / 3600.0 + ); + println!( + " CB Time Cell 3: {}s ({:.1}h)", + cb3, + cb3 as f64 / 3600.0 + ); + println!( + " CB Time Cell 4: {}s ({:.1}h)", + cb4, + cb4 as f64 / 3600.0 + ); + } + + if !data.lifetime3.is_empty() { + println!("LifeTime3:"); + if data.lifetime3.len() == 4 { + // R3 format: 1×u32 Total FW Runtime in seconds + let runtime_s = u32::from_le_bytes([ + data.lifetime3[0], + data.lifetime3[1], + data.lifetime3[2], + data.lifetime3[3], + ]); + let hours = runtime_s as f64 / 3600.0; + println!( + " Total FW Runtime: {}s ({:.1}h / {:.1}d)", + runtime_s, + hours, + hours / 24.0 + ); + } else if data.lifetime3.len() == 16 { + // R2 format: 8×u16 (runtime + 7 temperature time bins) + let lt3 = &data.lifetime3; + println!( + " Total FW Runtime: {}h", + u16::from_le_bytes([lt3[0], lt3[1]]) + ); + println!( + " Time in Under Temp: {}h", + u16::from_le_bytes([lt3[2], lt3[3]]) + ); + println!( + " Time in Low Temp: {}h", + u16::from_le_bytes([lt3[4], lt3[5]]) + ); + println!( + " Time in Std Temp Low: {}h", + u16::from_le_bytes([lt3[6], lt3[7]]) + ); + println!( + " Time in Std Temp: {}h", + u16::from_le_bytes([lt3[8], lt3[9]]) + ); + println!( + " Time in Std Temp High: {}h", + u16::from_le_bytes([lt3[10], lt3[11]]) + ); + println!( + " Time in High Temp: {}h", + u16::from_le_bytes([lt3[12], lt3[13]]) + ); + println!( + " Time in Over Temp: {}h", + u16::from_le_bytes([lt3[14], lt3[15]]) + ); + } else { + println!(" Raw: {}", hex_encode(&data.lifetime3)); + } + } + + if !data.lifetime4.is_empty() { + println!("LifeTime4:"); + if data.lifetime4.len() >= 32 { + let lt4 = &data.lifetime4; + println!( + " Cell Over-Voltage: {} (last @ cycle {})", + u16::from_le_bytes([lt4[0], lt4[1]]), + u16::from_le_bytes([lt4[2], lt4[3]]) + ); + println!( + " Cell Under-Voltage: {} (last @ cycle {})", + u16::from_le_bytes([lt4[4], lt4[5]]), + u16::from_le_bytes([lt4[6], lt4[7]]) + ); + println!( + " Over-Current Discharge 1: {} (last @ cycle {})", + u16::from_le_bytes([lt4[8], lt4[9]]), + u16::from_le_bytes([lt4[10], lt4[11]]) + ); + println!( + " Over-Current Discharge 2: {} (last @ cycle {})", + u16::from_le_bytes([lt4[12], lt4[13]]), + u16::from_le_bytes([lt4[14], lt4[15]]) + ); + println!( + " Over-Current Charge 1: {} (last @ cycle {})", + u16::from_le_bytes([lt4[16], lt4[17]]), + u16::from_le_bytes([lt4[18], lt4[19]]) + ); + println!( + " Over-Current Charge 2: {} (last @ cycle {})", + u16::from_le_bytes([lt4[20], lt4[21]]), + u16::from_le_bytes([lt4[22], lt4[23]]) + ); + println!( + " Overload in Discharge: {} (last @ cycle {})", + u16::from_le_bytes([lt4[24], lt4[25]]), + u16::from_le_bytes([lt4[26], lt4[27]]) + ); + println!( + " Short-Circuit Discharge: {} (last @ cycle {})", + u16::from_le_bytes([lt4[28], lt4[29]]), + u16::from_le_bytes([lt4[30], lt4[31]]) + ); + } + } + + if !data.lifetime5.is_empty() { + println!("LifeTime5:"); + if data.lifetime5.len() >= 32 { + let lt5 = &data.lifetime5; + println!( + " Short-Circuit Charge: {} (last @ cycle {})", + u16::from_le_bytes([lt5[0], lt5[1]]), + u16::from_le_bytes([lt5[2], lt5[3]]) + ); + println!( + " Over-Temp Charge: {} (last @ cycle {})", + u16::from_le_bytes([lt5[4], lt5[5]]), + u16::from_le_bytes([lt5[6], lt5[7]]) + ); + println!( + " Over-Temp Discharge: {} (last @ cycle {})", + u16::from_le_bytes([lt5[8], lt5[9]]), + u16::from_le_bytes([lt5[10], lt5[11]]) + ); + println!( + " Over-Temp FET: {} (last @ cycle {})", + u16::from_le_bytes([lt5[12], lt5[13]]), + u16::from_le_bytes([lt5[14], lt5[15]]) + ); + println!( + " Valid Charge Terminations:{} (last @ cycle {})", + u16::from_le_bytes([lt5[16], lt5[17]]), + u16::from_le_bytes([lt5[18], lt5[19]]) + ); + println!( + " QMax Updates: {} (last @ cycle {})", + u16::from_le_bytes([lt5[20], lt5[21]]), + u16::from_le_bytes([lt5[22], lt5[23]]) + ); + println!( + " Resistance Updates: {} (last @ cycle {})", + u16::from_le_bytes([lt5[24], lt5[25]]), + u16::from_le_bytes([lt5[26], lt5[27]]) + ); + println!( + " Ra Disable: {} (last @ cycle {})", + u16::from_le_bytes([lt5[28], lt5[29]]), + u16::from_le_bytes([lt5[30], lt5[31]]) + ); + } + } + } + + // Print health analysis at the end + analyze_health(data); +} + +/// Analyze battery health and print a summary +pub fn analyze_health(data: &BatteryData) { + println!("\n=== Battery Health Analysis ===\n"); + + let mut issues: Vec<&str> = Vec::new(); + let mut warnings: Vec = Vec::new(); + + // Check Safety/PF status + if data.safety_status != 0 { + issues.push("Safety status flags active"); + } + if data.pf_status != 0 { + issues.push("Permanent failure flags active"); + } + + // Check battery status alarm flags + if data.battery_status & (1 << 15) != 0 { + issues.push("Over-Charged Alarm active"); + } + if data.battery_status & (1 << 12) != 0 { + issues.push("Over Temperature Alarm active"); + } + if data.battery_status & (1 << 14) != 0 { + warnings.push("Terminate Charge Alarm active".to_string()); + } + if data.battery_status & (1 << 11) != 0 { + warnings.push("Terminate Discharge Alarm active".to_string()); + } + if data.battery_status & (1 << 4) != 0 { + warnings.push("Battery is fully discharged".to_string()); + } + + // Check capacity retention + if data.design_capacity > 0 && data.full_charge_capacity > 0 { + let retention = (data.full_charge_capacity as f32 / data.design_capacity as f32) * 100.0; + if retention < 50.0 { + issues.push("Severe capacity degradation (<50%)"); + } else if retention < 70.0 { + warnings.push(format!( + "Significant capacity degradation: {:.0}%", + retention + )); + } else if retention < 80.0 { + warnings.push(format!("Moderate capacity degradation: {:.0}%", retention)); + } + } + + // Check cell voltage balance (current) + let cells = [ + data.cell_voltage1, + data.cell_voltage2, + data.cell_voltage3, + data.cell_voltage4, + ]; + let max_cell = cells.iter().max().unwrap_or(&0); + let min_cell = cells.iter().min().unwrap_or(&0); + let cell_delta = max_cell - min_cell; + if cell_delta > 100 { + warnings.push(format!( + "Cell imbalance: {}mV difference (>100mV)", + cell_delta + )); + } + + // Analyze lifetime data if available + if data.lifetime1.len() >= 32 { + let lt1 = &data.lifetime1; + let max_delta = u16::from_le_bytes([lt1[16], lt1[17]]); + if max_delta > 200 { + warnings.push(format!( + "Historical cell imbalance: {}mV max delta recorded", + max_delta + )); + } + + // Check temperature extremes + let max_temp = lt1[26]; + let min_temp = lt1[27]; + if max_temp > 55 { + warnings.push(format!("High temperature recorded: {}C max", max_temp)); + } + if min_temp < 5 { + warnings.push(format!("Low temperature recorded: {}C min", min_temp)); + } + } + + // Analyze safety events + if data.lifetime4.len() >= 32 { + let lt4 = &data.lifetime4; + let cov_events = u16::from_le_bytes([lt4[0], lt4[1]]); + let cuv_events = u16::from_le_bytes([lt4[4], lt4[5]]); + let ocd1_events = u16::from_le_bytes([lt4[8], lt4[9]]); + let ocd2_events = u16::from_le_bytes([lt4[12], lt4[13]]); + let occ1_events = u16::from_le_bytes([lt4[16], lt4[17]]); + let scd_events = u16::from_le_bytes([lt4[28], lt4[29]]); + + if cov_events > 0 { + warnings.push(format!("{} cell over-voltage events", cov_events)); + } + if cuv_events > 5 { + warnings.push(format!( + "{} cell under-voltage events (deep discharge)", + cuv_events + )); + } + if ocd1_events > 0 || ocd2_events > 0 { + warnings.push(format!( + "{} over-current discharge events", + ocd1_events + ocd2_events + )); + } + if occ1_events > 0 { + warnings.push(format!("{} over-current charge events", occ1_events)); + } + if scd_events > 0 { + issues.push("Short-circuit events detected"); + } + } + + // Analyze gauging health + if data.lifetime5.len() >= 32 { + let lt5 = &data.lifetime5; + let valid_terminations = u16::from_le_bytes([lt5[16], lt5[17]]); + let ra_updates = u16::from_le_bytes([lt5[24], lt5[25]]); + let ra_fails = u16::from_le_bytes([lt5[28], lt5[29]]); + + // Check charge termination ratio + if data.cycle_count > 10 && valid_terminations < (data.cycle_count * 8 / 10) { + warnings.push(format!( + "Low charge termination rate: {} terminations over {} cycles", + valid_terminations, data.cycle_count + )); + } + + // Check resistance update failures + if ra_updates > 0 { + let fail_rate = (ra_fails as f32 / ra_updates as f32) * 100.0; + if fail_rate > 20.0 { + warnings.push(format!( + "High resistance update fail rate: {:.1}%", + fail_rate + )); + } + } + } + + // Print results + if issues.is_empty() && warnings.is_empty() { + println!("Status: HEALTHY"); + println!(" No issues detected. Battery is operating normally."); + } else if issues.is_empty() { + println!("Status: GOOD (with notes)"); + for warning in &warnings { + println!(" Note: {}", warning); + } + } else { + println!("Status: NEEDS ATTENTION"); + for issue in &issues { + println!(" Issue: {}", issue); + } + for warning in &warnings { + println!(" Note: {}", warning); + } + } + + // Print summary stats + println!("\nSummary:"); + println!(" Cycle count: {}", data.cycle_count); + if data.design_capacity > 0 && data.full_charge_capacity > 0 { + let capacity_10mwh = data.mode & 0x8000 != 0; + let retention = (data.full_charge_capacity as f32 / data.design_capacity as f32) * 100.0; + if capacity_10mwh { + println!( + " Capacity: {:.2} / {:.2} Wh ({:.0}%)", + data.full_charge_capacity as f32 / 100.0, + data.design_capacity as f32 / 100.0, + retention + ); + } else { + println!( + " Capacity: {} / {} mAh ({:.0}%)", + data.full_charge_capacity, data.design_capacity, retention + ); + } + } + if !data.state_of_health.is_empty() { + let soh_mah = u16::from_le_bytes([data.state_of_health[0], data.state_of_health[1]]); + let soh_cwh = u16::from_le_bytes([data.state_of_health[2], data.state_of_health[3]]); + println!( + " State of Health: {}mAh ({}.{:02}Wh)", + soh_mah, + soh_cwh / 100, + soh_cwh % 100 + ); + } + println!(" Current cell balance: {}mV spread", cell_delta); + if data.lifetime2.len() >= 20 { + let lt2 = &data.lifetime2; + let cb_times: Vec = [ + u32::from_le_bytes([lt2[4], lt2[5], lt2[6], lt2[7]]), + u32::from_le_bytes([lt2[8], lt2[9], lt2[10], lt2[11]]), + u32::from_le_bytes([lt2[12], lt2[13], lt2[14], lt2[15]]), + u32::from_le_bytes([lt2[16], lt2[17], lt2[18], lt2[19]]), + ] + .iter() + .map(|&t| t as f64 / 3600.0) + .collect(); + println!( + " Cell balancing time: {:.1}h / {:.1}h / {:.1}h / {:.1}h", + cb_times[0], cb_times[1], cb_times[2], cb_times[3] + ); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + + fn test_bins_path(filename: &str) -> PathBuf { + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("test_bins"); + path.push(filename); + path + } + + #[test] + fn test_parse_battery_dump() { + let path = test_bins_path("battery_00E3.txt"); + if !path.exists() { + // Skip test if dump file doesn't exist yet + eprintln!("Skipping test: {:?} not found", path); + return; + } + + let data = BatteryData::read_from_file(&path).expect("Failed to read dump file"); + + // Basic info + assert_eq!(data.mode, 0x6001); + assert_eq!(data.serial_num, 0x00E3); + assert!(!data.device_name.is_empty()); + assert!(!data.manufacturer_name.is_empty()); + + // Verify temperature is reasonable (0-100C range in 0.1K units) + let temp_c = (data.temperature as f32 / 10.0) - 273.15; + assert!( + temp_c > 0.0 && temp_c < 100.0, + "Temperature {} out of range", + temp_c + ); + + // Verify voltage is reasonable (10-20V for 4-cell pack) + let voltage_v = data.voltage as f32 / 1000.0; + assert!( + voltage_v > 10.0 && voltage_v < 25.0, + "Voltage {} out of range", + voltage_v + ); + + // Verify cell voltages add up approximately to total + let cell_sum = + data.cell_voltage1 + data.cell_voltage2 + data.cell_voltage3 + data.cell_voltage4; + let diff = (cell_sum as i32 - data.voltage as i32).abs(); + assert!( + diff < 100, + "Cell voltages don't add up: {} vs {}", + cell_sum, + data.voltage + ); + } + + #[test] + fn test_parse_lifetime_data() { + let path = test_bins_path("battery_00E3.txt"); + if !path.exists() { + eprintln!("Skipping test: {:?} not found", path); + return; + } + + let data = BatteryData::read_from_file(&path).expect("Failed to read dump file"); + + // Check lifetime1 has expected length + if !data.lifetime1.is_empty() { + assert_eq!(data.lifetime1.len(), 32, "LifeTime1 should be 32 bytes"); + + // Check cell max voltages are reasonable (3.0-4.5V) + for i in 0..4 { + let max_v = u16::from_le_bytes([data.lifetime1[i * 2], data.lifetime1[i * 2 + 1]]); + assert!( + max_v > 3000 && max_v < 4600, + "Cell {} max voltage {} out of range", + i + 1, + max_v + ); + } + + // Check temperatures are reasonable + let max_temp = data.lifetime1[26]; + let min_temp = data.lifetime1[27]; + assert!(max_temp < 100, "Max temp {} too high", max_temp); + assert!( + min_temp < max_temp || min_temp == 0, + "Min temp {} > max temp {}", + min_temp, + max_temp + ); + } + + // Check lifetime2 has expected length + if !data.lifetime2.is_empty() { + assert_eq!(data.lifetime2.len(), 20, "LifeTime2 should be 20 bytes"); + } + } + + #[test] + fn test_hex_encode_decode_roundtrip() { + let original = vec![0x00, 0x11, 0x22, 0x33, 0xAA, 0xBB, 0xCC, 0xFF]; + let encoded = hex_encode(&original); + let decoded = hex_decode(&encoded); + assert_eq!(original, decoded); + } + + #[test] + fn test_manufacture_date_decode() { + // Test date decoding: Year*512 + Month*32 + Day + // 2024-10-06 = (2024-1980)*512 + 10*32 + 6 = 44*512 + 320 + 6 = 22528 + 326 = 22854 + let mfg_date: u16 = 22854; + let day = mfg_date & 0x1F; + let month = (mfg_date >> 5) & 0x0F; + let year = (mfg_date >> 9) + 1980; + assert_eq!(day, 6); + assert_eq!(month, 10); + assert_eq!(year, 2024); + } + + #[test] + fn test_temperature_conversion() { + // 35.5C in 0.1K units = (35.5 + 273.15) * 10 = 3086.5 ≈ 3087 + let temp_raw: u16 = 3087; + let temp_c = (temp_raw as f32 / 10.0) - 273.15; + assert!( + (temp_c - 35.55).abs() < 0.1, + "Temperature conversion failed: {}", + temp_c + ); + } +} diff --git a/framework_lib/test_bins/battery_00E3.txt b/framework_lib/test_bins/battery_00E3.txt new file mode 100644 index 00000000..418de8ae --- /dev/null +++ b/framework_lib/test_bins/battery_00E3.txt @@ -0,0 +1,26 @@ +# Smart Battery Raw Data Dump +# Format: key=hex_value or key=string + +mode=6001 +serial_num=00E3 +manufacture_date=5946 +temperature=0BE4 +voltage=4590 +cell_voltage1=1163 +cell_voltage2=1164 +cell_voltage3=1164 +cell_voltage4=1164 +cycle_count=0051 +device_name=FRANDBAT01 +manufacturer_name=NVT +state_of_health=A3153921 +operation_status=10000287 +safety_alert=00000000 +safety_status=00000000 +pf_alert=00000000 +pf_status=00000000 +lifetime1=7111731170117311E00A5B0A6B0AC60A61017915A7DFE5EA25E13B0F003E0C42 +lifetime2=0500050096950100CF670200AF740200B7940200 +lifetime3=27273C01 +lifetime4=0000000006004800000000000000000000000000000000000000000000000000 +lifetime5=000000000000000000000000000000005000510013005100860451007A005100 diff --git a/framework_tool/completions/bash/framework_tool b/framework_tool/completions/bash/framework_tool index 45cbd9bc..e542c17e 100755 --- a/framework_tool/completions/bash/framework_tool +++ b/framework_tool/completions/bash/framework_tool @@ -23,7 +23,7 @@ _framework_tool() { case "${cmd}" in framework_tool) - opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --info --meinfo --pd-info --pd-reset --pd-disable --pd-enable --dp-hdmi-info --dp-hdmi-update --audio-card-info --privacy --pd-bin --ec-bin --capsule --dump --h2o-capsule --dump-ec-flash --flash-full-ec --flash-ec --flash-ro-ec --flash-rw-ec --intrusion --inputdeck --inputdeck-mode --expansion-bay --charge-limit --charge-current-limit --charge-rate-limit --get-gpio --fp-led-level --fp-brightness --kblight --remap-key --rgbkbd --ps2-enable --tablet-mode --touchscreen-enable --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --uptimeinfo --s0ix-counter --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" + opts="-v -q -t -f -h --flash-gpu-descriptor --verbose --quiet --versions --version --features --esrt --device --compare-version --power --smartbattery --smartbattery-auth --thermal --sensors --fansetduty --fansetrpm --autofanctrl --pdports --pdports-chromebook --info --meinfo --pd-info --pd-reset --pd-disable --pd-enable --dp-hdmi-info --dp-hdmi-update --audio-card-info --privacy --pd-bin --ec-bin --capsule --dump --h2o-capsule --dump-ec-flash --flash-full-ec --flash-ec --flash-ro-ec --flash-rw-ec --intrusion --inputdeck --inputdeck-mode --expansion-bay --charge-limit --charge-current-limit --charge-rate-limit --get-gpio --fp-led-level --fp-brightness --kblight --remap-key --rgbkbd --ps2-enable --tablet-mode --touchscreen-enable --haptic-intensity --click-force --stylus-battery --console --reboot-ec --ec-hib-delay --uptimeinfo --s0ix-counter --hash --driver --pd-addrs --pd-ports --test --test-retimer --boardid --force --dry-run --flash-gpu-descriptor-file --dump-gpu-descriptor-file --validate-gpu-descriptor-file --nvidia --host-command --generate-completions --help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -41,6 +41,10 @@ _framework_tool() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --smartbattery) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --fansetduty) COMPREPLY=($(compgen -f "${cur}")) return 0 diff --git a/framework_tool/completions/fish/framework_tool.fish b/framework_tool/completions/fish/framework_tool.fish index 465f1cde..815f2b4e 100644 --- a/framework_tool/completions/fish/framework_tool.fish +++ b/framework_tool/completions/fish/framework_tool.fish @@ -8,6 +8,7 @@ rtm23\t'' ac-left\t'' ac-right\t''" complete -c framework_tool -l compare-version -r +complete -c framework_tool -l smartbattery -d 'Show detailed smart battery information, or load from dump file' -r -F complete -c framework_tool -l fansetduty -d 'Set fan duty cycle (0-100%)' -r complete -c framework_tool -l fansetrpm -d 'Set fan RPM (limited by EC fan table max RPM)' -r complete -c framework_tool -l autofanctrl -d 'Turn on automatic fan speed control' -r @@ -88,6 +89,7 @@ complete -c framework_tool -l version -d 'Show tool version information (Add -vv complete -c framework_tool -l features -d 'Show features support by the firmware' complete -c framework_tool -l esrt -d 'Display the UEFI ESRT table' complete -c framework_tool -l power -d 'Show current power status of battery and AC (Add -vv for more details)' +complete -c framework_tool -l smartbattery-auth -d 'Authenticate smart battery (requires unseal and auth keys)' complete -c framework_tool -l thermal -d 'Print thermal information (Temperatures and Fan speed)' complete -c framework_tool -l sensors -d 'Print sensor information (ALS, G-Sensor)' complete -c framework_tool -l pdports -d 'Show USB-C PD port state' diff --git a/framework_tool/completions/zsh/_framework_tool b/framework_tool/completions/zsh/_framework_tool index 324c6286..edc7b7eb 100644 --- a/framework_tool/completions/zsh/_framework_tool +++ b/framework_tool/completions/zsh/_framework_tool @@ -18,6 +18,7 @@ _framework_tool() { '--flash-gpu-descriptor=[]: :_default: :_default' \ '--device=[]:DEVICE:(bios ec pd0 pd1 rtm01 rtm23 ac-left ac-right)' \ '--compare-version=[]:COMPARE_VERSION:_default' \ +'--smartbattery=[Show detailed smart battery information, or load from dump file]::FILE:_files' \ '*--fansetduty=[Set fan duty cycle (0-100%)]::FANSETDUTY:_default' \ '*--fansetrpm=[Set fan RPM (limited by EC fan table max RPM)]::FANSETRPM:_default' \ '--autofanctrl=[Turn on automatic fan speed control]::AUTOFANCTRL:_default' \ @@ -72,6 +73,7 @@ _framework_tool() { '--features[Show features support by the firmware]' \ '--esrt[Display the UEFI ESRT table]' \ '--power[Show current power status of battery and AC (Add -vv for more details)]' \ +'--smartbattery-auth[Authenticate smart battery (requires unseal and auth keys)]' \ '--thermal[Print thermal information (Temperatures and Fan speed)]' \ '--sensors[Print sensor information (ALS, G-Sensor)]' \ '--pdports[Show USB-C PD port state]' \