From 77397c028bc2d296031754466e5669ce76fdf6c8 Mon Sep 17 00:00:00 2001 From: Sachymetsu Date: Tue, 21 Jul 2026 09:27:03 +0200 Subject: [PATCH] Ergot BLE transport/stack --- .cargo/config.toml | 2 +- Cargo.lock | 217 +++++++++++++++++++++++---------------------- Cargo.toml | 6 +- src/ble.rs | 81 ++++++++++++----- src/client.rs | 173 ++++++++++++++++++++++++++++-------- src/commands.rs | 149 +++++++++++++++++++++++++++++++ src/locks.rs | 2 + src/main.rs | 5 +- src/packet_pool.rs | 13 +-- src/timer.rs | 71 ++++++++++----- 10 files changed, 523 insertions(+), 196 deletions(-) create mode 100644 src/commands.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 06360c6..552d8e3 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,5 @@ [target.'cfg(all(target_arch = "arm", target_os = "none"))'] -runner = "probe-rs run --chip RP235x" +runner = "probe-rs run --chip RP235x --preverify" [build] target = "thumbv8m.main-none-eabihf" diff --git a/Cargo.lock b/Cargo.lock index f051004..be532a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -75,12 +75,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "base64ct" version = "1.8.3" @@ -165,9 +159,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.25.1" +version = "1.25.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" [[package]] name = "byteorder" @@ -177,9 +171,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.2.67" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" dependencies = [ "find-msvc-tools", "shlex", @@ -241,9 +235,9 @@ checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "cordyceps" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" +checksum = "5b9ab7e0ca1d179628fa0172b2b97203c7fa0cd81be2448bd446fb9559ca9261" dependencies = [ "loom", "tracing", @@ -251,30 +245,44 @@ dependencies = [ [[package]] name = "cortex-m" -version = "0.7.7" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +checksum = "844b9697e922c99847eed515c6eb6d101e7ce62ff556fcaec243798291427ee8" dependencies = [ "bare-metal", "bitfield", + "cortex-m-macros", + "critical-section", "embedded-hal 0.2.7", + "embedded-hal 1.0.0", "volatile-register", ] +[[package]] +name = "cortex-m-macros" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1922be58519ad40368fc4ca595a2cefa51a7abf947be3b0c90586dc7dbd0e2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "cortex-m-rt" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +checksum = "9c439b23bf18154d8a634543caf1ee73cceb723f2902f7ea243634159a00fd47" dependencies = [ "cortex-m-rt-macros", ] [[package]] name = "cortex-m-rt-macros" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +checksum = "061a75f3e7f3f01d649668d68e02d0ef92c7041a9c97b8fe6bc354ddbf40822d" dependencies = [ "proc-macro2", "quote", @@ -515,9 +523,9 @@ dependencies = [ [[package]] name = "either" -version = "1.16.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] name = "embassy-embedded-hal" @@ -839,9 +847,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "fixed" @@ -869,9 +877,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "futures" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -883,9 +891,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -893,9 +901,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-intrusive" @@ -909,38 +917,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] [[package]] name = "futures-sink" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-core", "futures-macro", @@ -1023,9 +1031,9 @@ dependencies = [ [[package]] name = "hybrid-array" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" dependencies = [ "ctutils", "typenum", @@ -1068,23 +1076,34 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.32" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "961d16382652bfdd8c6f68b223b26a8c93e0d475c672f414411db31c6c5c900e" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" dependencies = [ "defmt 1.1.1", + "jiff-core", "jiff-static", "portable-atomic", "portable-atomic-util", "serde_core", ] +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt 1.1.1", +] + [[package]] name = "jiff-static" -version = "0.2.32" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0879bd39df99c4c5e2c6615ccc026391a423dde10532c573e6086eb94a802cc" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" dependencies = [ + "jiff-core", "proc-macro2", "quote", "syn 2.0.119", @@ -1101,9 +1120,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -1159,9 +1178,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "litrs" @@ -1180,9 +1199,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "loom" @@ -1435,9 +1454,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" dependencies = [ "critical-section", ] @@ -1494,9 +1513,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -1512,9 +1531,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -1560,9 +1579,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -1583,15 +1602,14 @@ checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" [[package]] name = "rlsf" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1646a59a9734b8b7a0ac51689388a60fe1625d4b956348e9de07591a1478457a" +checksum = "07393724337be2ee43a9d86164df4505746874a3fa65913374bc6d6a92314362" dependencies = [ "cfg-if", "const-default", "libc", "rustversion", - "svgbobdoc", ] [[package]] @@ -1641,6 +1659,7 @@ name = "sachy-water" version = "0.1.0" dependencies = [ "aead", + "bt-hci", "cortex-m", "cortex-m-rt", "critical-section", @@ -1662,6 +1681,7 @@ dependencies = [ "rand_core 0.10.1", "rlsf", "sachy-fmt", + "serde", "static_cell", "trouble-host", "water-pod-common", @@ -1706,9 +1726,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -1716,22 +1736,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] [[package]] @@ -1757,7 +1777,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" dependencies = [ "digest 0.11.3", - "keccak 0.2.0", + "keccak 0.2.2", ] [[package]] @@ -1848,24 +1868,11 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "svgbobdoc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" -dependencies = [ - "base64", - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-width", -] - [[package]] name = "syn" -version = "1.0.109" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -1874,9 +1881,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.119" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" dependencies = [ "proc-macro2", "quote", @@ -1903,22 +1910,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.4", ] [[package]] @@ -2036,9 +2043,9 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "uuid" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc" [[package]] name = "valuable" @@ -2086,7 +2093,7 @@ dependencies = [ [[package]] name = "water-pod-common" version = "0.1.0" -source = "git+https://tangled.org/sachy.dev/water-pod#f528cadeb83149cce65fc820ef696126b9a69de4" +source = "git+https://tangled.org/sachy.dev/water-pod#d5fcfdc21be721a952463e0313421fd7f282f3fb" dependencies = [ "aead", "defmt 1.1.1", @@ -2100,7 +2107,7 @@ dependencies = [ [[package]] name = "wharrgarbl" version = "0.1.0" -source = "git+https://tangled.org/sachy.dev/wharrgarbl#375ccf6cec94d16380468f2a052634b20924c70f" +source = "git+https://tangled.org/sachy.dev/wharrgarbl#a6885382a9c8463e783402abf14a63bbbf7b7547" dependencies = [ "aead", "ctutils", @@ -2115,12 +2122,12 @@ dependencies = [ [[package]] name = "wharrgarbl-neko" version = "0.1.0" -source = "git+https://tangled.org/sachy.dev/wharrgarbl#375ccf6cec94d16380468f2a052634b20924c70f" +source = "git+https://tangled.org/sachy.dev/wharrgarbl#a6885382a9c8463e783402abf14a63bbbf7b7547" dependencies = [ "aead", "ctutils", "hybrid-array", - "keccak 0.2.0", + "keccak 0.2.2", "wharrgarbl-utils", "zerocopy", "zeroize", @@ -2129,7 +2136,7 @@ dependencies = [ [[package]] name = "wharrgarbl-utils" version = "0.1.0" -source = "git+https://tangled.org/sachy.dev/wharrgarbl#375ccf6cec94d16380468f2a052634b20924c70f" +source = "git+https://tangled.org/sachy.dev/wharrgarbl#a6885382a9c8463e783402abf14a63bbbf7b7547" dependencies = [ "aead", "ctutils", @@ -2171,18 +2178,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.54" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.54" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 8b829f2..bf117de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ critical-section = "1.2.0" static_cell = "2.1" embassy-embedded-hal = { version = "0.6.0" } embassy-sync = { version = "0.8" } -embassy-executor = { version = "0.10.0", features = ["platform-cortex-m"] } +embassy-executor = { version = "0.10.0", features = ["platform-cortex-m", "nightly"] } embassy-time = { version = "0.5.1" } sachy-fmt = { git = "https://tangled.org/sachy.dev/sachy-embed-core", package = "sachy-fmt" } water-pod-common = { git = "https://tangled.org/sachy.dev/water-pod", package = "water-pod-common", default-features = false } @@ -52,12 +52,14 @@ embassy-rp = { version = "0.10.0", features = [ embassy-futures = { version = "0.1.2" } cyw43 = { version = "0.7.0", features = ["bluetooth"] } cyw43-pio = { version = "0.10.0" } -trouble-host = { version = "0.6.0", default-features = false, features = ["peripheral"] } +trouble-host = { version = "0.6.0", default-features = false, features = ["peripheral", "l2cap-rx-queue-size-32", "l2cap-tx-queue-size-32"] } +bt-hci = { version = "0.8" } jiff = { version = "0.2.32", default-features = false, features = ["alloc"] } postcard = { version = "1", default-features = false } embedded-io = "0.7.1" wharrgarbl = { git = "https://tangled.org/sachy.dev/wharrgarbl", package = "wharrgarbl" } rand_core = { version = "0.10", default-features = false } +serde = { version = "1", default-features = false } aead = { version = "0.6.0" } [profile.dev] diff --git a/src/ble.rs b/src/ble.rs index 15ac511..4d18308 100644 --- a/src/ble.rs +++ b/src/ble.rs @@ -2,34 +2,47 @@ use cyw43::{Control, aligned_bytes, bluetooth::BtDriver}; use cyw43_pio::PioSpi; use embassy_executor::Spawner; use embassy_rp::{gpio::Output, peripherals::PIO0}; +use embassy_sync::signal::Signal; use embassy_time::{Duration, WithTimeout}; -use sachy_fmt::{error, info, unwrap}; +use sachy_fmt::{error, info, unwrap, warn}; use static_cell::StaticCell; use trouble_host::{ - Address, Host, HostResources, Stack, + Host, HostResources, Stack, advertise::{ AdStructure, Advertisement, AdvertisementParameters, BR_EDR_NOT_SUPPORTED, LE_GENERAL_DISCOVERABLE, }, + connection::Connection, l2cap::L2capChannel, prelude::ExternalController, }; +use water_pod_common::SPSM_L2CAP_WATER; -use crate::{client::PodClient, packet_pool::AllocPacketPool, timer::TimerActor}; +use crate::{client::PodClient, locks::NetLock, packet_pool::AllocPacketPool}; pub type BleController = ExternalController, 10>; pub type BleStack = Stack<'static, BleController, AllocPacketPool>; type BleHost = HostResources; type BleRunner = trouble_host::prelude::Runner<'static, BleController, AllocPacketPool>; -const SPSM_L2CAP_WATER: u16 = 0x0089; - /// Max number of connections const CONNECTIONS_MAX: usize = 1; /// Max number of L2CAP channels. const L2CAP_CHANNELS_MAX: usize = 3; // Signal + att + CoC +static BLE_WAKE: Signal = Signal::new(); +static BLE_DONE: Signal = Signal::new(); + +pub struct BleActor; + +impl BleActor { + pub fn advertise() -> impl Future { + BLE_WAKE.signal(()); + BLE_DONE.wait() + } +} + #[embassy_executor::task] pub async fn setup(spawner: Spawner, pwr: Output<'static>, spi: PioSpi<'static, PIO0, 0>) { let (fw, clm, btfw, nvram) = { @@ -57,15 +70,11 @@ pub async fn setup(spawner: Spawner, pwr: Output<'static>, spi: PioSpi<'static, #[embassy_executor::task] async fn ble_run(spawner: Spawner, controller: BleController, mut control: Control<'static>) -> ! { - let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]); - info!("Our address = {:?}", address); - static RESOURCES: StaticCell = StaticCell::new(); static STACK: StaticCell = StaticCell::new(); let resources = RESOURCES.init_with(HostResources::new); - let stack = - STACK.init_with(|| trouble_host::new(controller, resources).set_random_address(address)); + let stack = STACK.init_with(|| trouble_host::new(controller, resources)); let Host { mut peripheral, @@ -89,28 +98,28 @@ async fn ble_run(spawner: Spawner, controller: BleController, mut control: Contr &mut scan_data[..], )); - let config = AdvertisementParameters { + let params = AdvertisementParameters { timeout: Some(Duration::from_secs(30)), interval_min: Duration::from_millis(100), interval_max: Duration::from_millis(250), - tx_power: trouble_host::advertise::TxPower::Plus10dBm, ..Default::default() }; let l2cap_config = AllocPacketPool::l2cap_config(); loop { - TimerActor::wait_for_wake().await; + BLE_WAKE.wait().await; control.gpio_set(0, true).await; info!("Advertising, waiting for connection..."); + let advertiser = unwrap!( peripheral .advertise( - &config, + ¶ms, Advertisement::ConnectableScannableUndirected { adv_data: &adv_data[..adv_data_len], scan_data: &scan_data[..scan_data_len], - }, + } ) .await ); @@ -120,22 +129,50 @@ async fn ble_run(spawner: Spawner, controller: BleController, mut control: Contr .with_timeout(Duration::from_secs(30)) .await { - match L2capChannel::accept(stack, &connection, &[SPSM_L2CAP_WATER], &l2cap_config).await + match L2capChannel::accept(stack, &connection, &[SPSM_L2CAP_WATER], &l2cap_config) + .with_timeout(Duration::from_secs(10)) + .await { - Ok(channel) => { - let mut client = PodClient::new(channel, stack); - - client.run().await; + Ok(Ok(channel)) => { + spawner.spawn(unwrap!(client_task(connection, channel, stack))); + } + Ok(Err(e)) => { + error!("Connection error: {}", e); + BLE_DONE.signal(()); + } + Err(_) => { + warn!("Timed out"); + BLE_DONE.signal(()); } - Err(e) => error!("Connection error: {}", e), } + } else { + info!("NAP TIME"); + BLE_DONE.signal(()); } control.gpio_set(0, false).await; - TimerActor::sleep().await; } } +#[embassy_executor::task] +async fn client_task( + connection: Connection<'static, AllocPacketPool>, + channel: L2capChannel<'static, AllocPacketPool>, + stack: &'static BleStack, +) { + info!("Connection accepted"); + + let runner = alloc::boxed::Box::pin(PodClient::new(channel, stack).run()); + + if let Err(e) = runner.await { + error!("Client failure: {}", e); + } + + connection.disconnect(); + drop(connection); + BLE_DONE.signal(()); +} + #[embassy_executor::task] async fn ble_runner_task(mut runner: BleRunner) { loop { diff --git a/src/client.rs b/src/client.rs index 2714136..ebf2401 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,10 +1,14 @@ -use core::convert::Infallible; +use core::{cell::Cell, convert::Infallible}; +use alloc::vec::Vec; use embassy_rp::clocks::RoscRng; use rand_core::{TryCryptoRng, TryRng}; -use sachy_fmt::{error, info}; +use sachy_fmt::info; use trouble_host::l2cap::L2capChannel; -use water_pod_common::{Request, Response, TimeSyncMs}; +use water_pod_common::{ + Accumulator, AccumulatorResult, Encrypted as PodEncrypted, Pairing, PodDevice, Request, + Response, TimeSyncMs, +}; use wharrgarbl::{ Neko128, NekoClientHandshake128, transport::{AeadTransport, RecvState, SendState}, @@ -12,6 +16,7 @@ use wharrgarbl::{ use crate::{ ble::BleStack, + commands::{TIMERS_HASH, load_timers}, error::{PicoError, PicoResult}, packet_pool::{AllocPacketPool, DATA_MTU}, timer::TimerActor, @@ -48,62 +53,154 @@ impl PodClient { Self { channel, stack } } - async fn sync<'a>( - &mut self, - send: &mut SendState<'a, Neko128>, - recv: &mut RecvState<'a, Neko128>, - ) -> PicoResult<()> { - let now0 = TimerActor::now().await; + async fn pair(&mut self, buf: &mut [u8]) -> PicoResult> { + let mut client = NekoClientHandshake128::new(None); - info!("Starting at {}", now0); + info!("Requesting pairing"); + let req = Request::request_pairing(&mut client, &mut WrappedRng)?; - let req = Request::TimeSync(TimeSyncMs::new(now0)); - let req = req.to_bytes(send)?; + self.send(&req).await.ok(); - self.channel.send(self.stack, &req).await?; + let paired: Pairing = self.recv(buf).await?; - let mut payload = alloc::vec![0u8; DATA_MTU]; - let read = self.channel.receive(self.stack, &mut payload).await?; - payload.truncate(read); + let aead = Request::finish_pairing(client, paired)?; - if let Response::TimeSync(sync) = Response::from_bytes(payload, recv)? { - let now3 = TimerActor::now().await; - let time = sync.calc_synced_time(now3)?; + Ok(aead) + } - info!("SYNC CALC'D: {}", time); - TimerActor::set_time(time).await; - } else { - return Err(PicoError::ClientReqResMismatch); - } + pub async fn run(mut self) -> PicoResult<()> { + let mut buf = alloc::vec![0u8; DATA_MTU]; + + let aead = self.pair(&mut buf).await?; + info!("PAIRED"); + + let mut client = PodEncryptedClient::new(self, &aead, buf); + + client.update().await?; + client.sync().await?; + client.get_timers().await?; Ok(()) } - pub async fn pair(&mut self) -> PicoResult> { - let mut client = NekoClientHandshake128::new(None); + async fn send(&mut self, data: &[u8]) -> PicoResult<()> { + for chunk in data.chunks(DATA_MTU) { + self.channel.send(self.stack, chunk).await?; + } + Ok(()) + } + + async fn recv(&mut self, raw_buf: &mut [u8]) -> PicoResult + where + T: for<'de> serde::Deserialize<'de>, + { + let mut cobs_acc = Accumulator::new(); + + loop { + let ct = self.channel.receive(self.stack, raw_buf).await?; + + match cobs_acc.accumulate(&raw_buf[..ct]) { + AccumulatorResult::Continue => continue, + AccumulatorResult::Error => break, + AccumulatorResult::Success(payload) => { + return Ok(payload); + } + }; + } + + Err(PicoError::MessageError) + } +} + +struct PodEncryptedClient<'a> { + client: PodClient, + buf: Vec, + send_state: SendState<'a, Neko128>, + recv_state: RecvState<'a, Neko128>, +} - let mut buf = alloc::vec::Vec::new(); +impl<'a> PodEncryptedClient<'a> { + fn new(client: PodClient, aead: &'a AeadTransport, buf: Vec) -> Self { + let (send_state, recv_state) = aead.split(); - Request::request_pairing(&mut client, &mut WrappedRng, &mut buf)?; + Self { + client, + buf, + send_state, + recv_state, + } + } +} - self.channel.send(self.stack, &buf).await.ok(); +impl PodEncryptedClient<'_> { + async fn update(&mut self) -> PicoResult<()> { + let req = PodEncrypted::new(Request::Update(PodDevice { + name: "Pod-01".into(), + pumps: 1, + battery: 100, + })) + .to_bytes(&mut self.send_state)?; - let paired = self.channel.receive(self.stack, &mut buf).await?; + self.client.send(&req).await?; - let aead = Request::finish_pairing(client, &buf[..paired])?; + let payload: PodEncrypted = self.client.recv(&mut self.buf).await?; - Ok(aead) + if matches!(payload.unseal(&mut self.recv_state)?, Response::Ack) { + Ok(()) + } else { + Err(PicoError::ClientReqResMismatch) + } } - pub async fn run(&mut self) { - if let Ok(aead) = self.pair().await { - let (mut send, mut recv) = aead.split(); + async fn sync(&mut self) -> PicoResult<()> { + let now0 = TimerActor::now().await; + + info!("Starting at {}", now0); + + let req = PodEncrypted::new(Request::TimeSync(TimeSyncMs::new(now0))) + .to_bytes(&mut self.send_state)?; + + self.client.send(&req).await?; - if let Err(e) = self.sync(&mut send, &mut recv).await { - error!("Sync Failure: {}", e); + let payload: PodEncrypted = self.client.recv(&mut self.buf).await?; + + if let Response::TimeSync(sync) = payload.unseal(&mut self.recv_state)? { + let now3 = TimerActor::now().await; + let time = sync.calc_synced_time(now3)?; + + info!("SYNC CALC'D: {}", time); + TimerActor::set_time(time).await; + } else { + return Err(PicoError::ClientReqResMismatch); + } + + Ok(()) + } + + async fn get_timers(&mut self) -> PicoResult<()> { + let hash = TIMERS_HASH.lock(Cell::get); + let req = + PodEncrypted::new(Request::FetchTimers { hash }).to_bytes(&mut self.send_state)?; + + self.client.send(&req).await?; + + let payload: PodEncrypted = self.client.recv(&mut self.buf).await?; + + if let Response::FetchTimers { + update, + timers, + hash, + } = payload.unseal(&mut self.recv_state)? + { + if let Some(timers) = timers { + info!("SAVING"); + TIMERS_HASH.lock(|stored| stored.set(hash)); + load_timers(timers, update).await; } } else { - error!("Failed to pair with remote host"); + return Err(PicoError::ClientReqResMismatch); } + + Ok(()) } } diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..8dafa16 --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,149 @@ +use alloc::{ + boxed::Box, + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, +}; +use core::cell::{Cell, RefCell}; + +use embassy_rp::{Peri, gpio::Output, peripherals::PIN_8}; +use embassy_sync::{blocking_mutex::Mutex, signal::Signal}; +use embassy_time::{Duration, Timer}; +use jiff::ToSpan; +use sachy_fmt::{info, unwrap}; +use water_pod_common::PumpTimer; + +use crate::{ble::BleActor, locks::CommandLock, timer::TimerActor}; + +type JiffQueue = BTreeMap>; + +pub static TIMERS_HASH: Mutex> = Mutex::new(Cell::new(0)); +static JIFF_QUEUE: Mutex> = + Mutex::new(RefCell::new(BTreeMap::new())); +static JIFF_UPDATED: Signal = Signal::new(); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum JiffTimer { + Update(i64), + Pump(PumpTimer), +} + +fn add_to_queue(timers_queue: &mut JiffQueue, act: jiff::civil::DateTime, timer: JiffTimer) { + timers_queue + .entry(act) + .and_modify(|set| { + set.insert(timer); + }) + .or_insert_with(|| BTreeSet::from([timer])); +} + +async fn arm_timer_alarm() { + let now = get_rounded_time().await.datetime(); + + let armed = JIFF_QUEUE.lock(|queue| { + queue + .borrow() + .first_key_value() + .and_then(|(key, _)| if key > &now { Some(*key) } else { None }) + }); + + if let Some(next_alarm) = armed { + TimerActor::set_alarm(next_alarm).await; + } else { + // action is in the past or is now, fire the activation. + info!("AWAKE"); + TimerActor::wake(); + } +} + +async fn get_rounded_time() -> jiff::Zoned { + let now = TimerActor::now().await; + + let rounder = jiff::ZonedRound::new() + .smallest(jiff::Unit::Minute) + .mode(jiff::RoundMode::Trunc); + + unwrap!(now.to_zoned(jiff::tz::TimeZone::UTC).round(rounder)) +} + +pub async fn load_timers(timers: Box<[PumpTimer]>, update: i64) { + let mut new_queue = BTreeMap::new(); + + let now = get_rounded_time().await; + let now_time = now.time(); + + let update_act = now.saturating_add(update.minutes()); + + add_to_queue( + &mut new_queue, + update_act.datetime(), + JiffTimer::Update(update), + ); + + for pump_timer in timers { + let act_time = jiff::civil::time(pump_timer.hour, pump_timer.minute, 0, 0); + + let act = if now_time < act_time { + act_time.to_datetime(now.date()) + } else { + act_time.to_datetime(now.date().saturating_add(1.days())) + }; + + add_to_queue(&mut new_queue, act, JiffTimer::Pump(pump_timer)); + } + + JIFF_UPDATED.signal(new_queue); +} + +#[embassy_executor::task] +pub async fn task(pin_08: Peri<'static, PIN_8>) { + let mut pumps = [Output::new(pin_08, embassy_rp::gpio::Level::Low)]; + + let now = get_rounded_time().await.datetime(); + JIFF_QUEUE.lock(|queue| { + let mut queue = queue.borrow_mut(); + add_to_queue(&mut queue, now, JiffTimer::Update(3)); + }); + TimerActor::wake(); + + loop { + TimerActor::wait_for_wake().await; + + let timer = JIFF_QUEUE.lock(|queue| queue.borrow_mut().pop_first()); + + if let Some((time, pump_timers)) = timer { + let mut new_queue = BTreeMap::new(); + + for timer in pump_timers { + let next_act = match timer { + JiffTimer::Pump(pump_timer) => { + if let Some(pump) = pumps.get_mut(pump_timer.pump as usize) { + info!("PUMPING {}", pump_timer.pump); + pump.set_high(); + Timer::after(Duration::from_secs(pump_timer.duration as u64)).await; + pump.set_low(); + } + + time.saturating_add(pump_timer.freq.minutes()) + } + JiffTimer::Update(duration) => { + BleActor::advertise().await; + time.saturating_add(duration.minutes()) + } + }; + + add_to_queue(&mut new_queue, next_act, timer); + } + + let updated = JIFF_UPDATED.try_take(); + JIFF_QUEUE.lock(|queue| { + let mut queue = queue.borrow_mut(); + if let Some(updates) = updated { + *queue = updates; + } else { + queue.append(&mut new_queue); + } + }); + + arm_timer_alarm().await; + } + } +} diff --git a/src/locks.rs b/src/locks.rs index 1715b94..c9931de 100644 --- a/src/locks.rs +++ b/src/locks.rs @@ -2,3 +2,5 @@ use embassy_rp::spinlock_mutex::SpinlockRawMutex; pub type AllocatorLock = SpinlockRawMutex<0>; pub type TimerLock = SpinlockRawMutex<1>; +pub type CommandLock = SpinlockRawMutex<2>; +pub type NetLock = SpinlockRawMutex<3>; diff --git a/src/main.rs b/src/main.rs index b7a8799..6acc569 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ #![feature(allocator_api)] +#![feature(impl_trait_in_assoc_type)] #![no_std] #![no_main] mod allocator; mod ble; mod client; +mod commands; mod error; mod locks; mod packet_pool; @@ -33,7 +35,7 @@ use {defmt_rtt as _, panic_probe as _}; bind_interrupts!(struct Irqs { PIO0_IRQ_0 => InterruptHandler; DMA_IRQ_0 => dma::InterruptHandler; - POWMAN_IRQ_TIMER => embassy_rp::aon_timer::InterruptHandler; + POWMAN_IRQ_TIMER => embassy_rp::aon_timer::InterruptHandler, crate::timer::TimerInterruptHandler; }); const HEAP_SIZE: usize = 256 * 1024; @@ -72,5 +74,6 @@ fn main() -> ! { CORE0.init_with(Executor::new).run(|spawner| { spawner.spawn(unwrap!(ble::setup(spawner, pwr, spi))); spawner.spawn(unwrap!(timer::run(p.POWMAN))); + spawner.spawn(unwrap!(commands::task(p.PIN_8))); }); } diff --git a/src/packet_pool.rs b/src/packet_pool.rs index 7196d06..9c271e5 100644 --- a/src/packet_pool.rs +++ b/src/packet_pool.rs @@ -4,8 +4,9 @@ use trouble_host::{ l2cap::{CreditFlowPolicy, L2capChannelConfig}, }; -pub const MTU: usize = 2510; -pub const DATA_MTU: usize = MTU - 6; +pub const MTU: usize = 251; +pub const DATA_MTU: usize = 251 - 6; +const MPS: u16 = 251 - 4; pub struct AllocPacketPool; #[repr(transparent)] @@ -15,9 +16,9 @@ impl AllocPacketPool { pub fn l2cap_config() -> L2capChannelConfig { L2capChannelConfig { mtu: Some(DATA_MTU as u16), - mps: Some(251 - 4), - flow_policy: CreditFlowPolicy::Every(50), - initial_credits: Some(200), + mps: Some(MPS), + flow_policy: CreditFlowPolicy::Every(10), + initial_credits: Some(50), } } } @@ -36,7 +37,7 @@ impl PacketPool for AllocPacketPool { } fn capacity() -> usize { - 32 + 8 } } diff --git a/src/timer.rs b/src/timer.rs index d3d0e3d..2e6e9f8 100644 --- a/src/timer.rs +++ b/src/timer.rs @@ -1,19 +1,21 @@ use embassy_rp::{ Peri, - aon_timer::{self, AonTimer}, + aon_timer::{self, AonTimer, DateTime}, + interrupt::typelevel::POWMAN_IRQ_TIMER, peripherals::POWMAN, }; use embassy_sync::{channel::Channel, signal::Signal}; -use embassy_time::{Duration, Ticker}; + use jiff::Timestamp; -use sachy_fmt::{info, unwrap}; +use sachy_fmt::{error, info, unwrap}; use crate::{Irqs, locks::TimerLock}; +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] #[allow(dead_code)] enum TimerMessage { - Sleep, Now, + SetAlarm { time: jiff::civil::DateTime }, Set { time: Timestamp }, } @@ -29,26 +31,31 @@ impl TimerActor { NOW.wait().await } - pub async fn set_time(time: Timestamp) { - TIMER.send(TimerMessage::Set { time }).await; + #[inline] + pub fn set_time(time: Timestamp) -> impl Future { + TIMER.send(TimerMessage::Set { time }) + } + + #[inline] + pub fn set_alarm(time: jiff::civil::DateTime) -> impl Future { + TIMER.send(TimerMessage::SetAlarm { time }) } - pub async fn wait_for_wake() { - WAKE.wait().await; + #[inline] + pub fn wait_for_wake() -> impl Future { + WAKE.wait() } - pub async fn sleep() { - TIMER.send(TimerMessage::Sleep).await; + pub fn wake() { + WAKE.signal(()); } } #[embassy_executor::task] pub async fn run(powman: Peri<'static, POWMAN>) { - let mut ticker = Ticker::every(Duration::from_secs(2 * 60)); - let config = aon_timer::Config { clock_source: aon_timer::ClockSource::Xosc, - clock_freq_khz: 12000, + clock_freq_khz: 12_000, alarm_wake_mode: aon_timer::AlarmWakeMode::WfiOnly, }; @@ -58,16 +65,8 @@ pub async fn run(powman: Peri<'static, POWMAN>) { timer.start(); - WAKE.signal(()); - loop { match TIMER.receive().await { - TimerMessage::Sleep => { - info!("I SLEEP"); - ticker.next().await; - info!("REAL SHIT"); - WAKE.signal(()); - } TimerMessage::Now => { NOW.signal(unwrap!(Timestamp::from_millisecond(timer.now() as i64))); } @@ -78,6 +77,36 @@ pub async fn run(powman: Peri<'static, POWMAN>) { timer.start(); timer.enable_alarm(); } + TimerMessage::SetAlarm { time } => { + info!("Setting alarm for: {}", time); + if let Err(e) = timer.set_alarm_at_datetime(DateTime { + year: time.year() as u16, + month: time.month() as u8, + day: time.day() as u8, + day_of_week: match time.weekday() { + jiff::civil::Weekday::Monday => aon_timer::DayOfWeek::Monday, + jiff::civil::Weekday::Tuesday => aon_timer::DayOfWeek::Tuesday, + jiff::civil::Weekday::Wednesday => aon_timer::DayOfWeek::Wednesday, + jiff::civil::Weekday::Thursday => aon_timer::DayOfWeek::Thursday, + jiff::civil::Weekday::Friday => aon_timer::DayOfWeek::Friday, + jiff::civil::Weekday::Saturday => aon_timer::DayOfWeek::Saturday, + jiff::civil::Weekday::Sunday => aon_timer::DayOfWeek::Sunday, + }, + hour: time.hour() as u8, + minute: time.minute() as u8, + second: 0, + }) { + error!("Error setting alarm: {}", e); + } + } }; } } + +pub struct TimerInterruptHandler; + +impl embassy_rp::interrupt::typelevel::Handler for TimerInterruptHandler { + unsafe fn on_interrupt() { + WAKE.signal(()); + } +} -- 2.51.2