Repo for designs & driver for a TA7642 powered lightning detector

feat: Embassy driver

+890 -1
+21
.tangled/workflows/test.yml
··· 1 + when: 2 + - event: ["push", "pull_request"] 3 + branch: main 4 + 5 + engine: nixery 6 + 7 + dependencies: 8 + nixpkgs: 9 + - clang 10 + - cargo 11 + - rustfmt 12 + - protobuf 13 + - cargo-nextest 14 + 15 + steps: 16 + - name: Format check 17 + command: cargo fmt --all --check 18 + - name: Tests 19 + command: cargo nextest run --workspace --locked --no-fail-fast 20 + - name: Doc Tests 21 + command: cargo test --workspace --locked --doc --no-fail-fast
+255
Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 4 4 + 5 + [[package]] 6 + name = "byteorder" 7 + version = "1.5.0" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 10 + 11 + [[package]] 12 + name = "cfg-if" 13 + version = "1.0.4" 14 + source = "registry+https://github.com/rust-lang/crates.io-index" 15 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 16 + 17 + [[package]] 18 + name = "critical-section" 19 + version = "1.2.0" 20 + source = "registry+https://github.com/rust-lang/crates.io-index" 21 + checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 22 + 23 + [[package]] 24 + name = "document-features" 25 + version = "0.2.12" 26 + source = "registry+https://github.com/rust-lang/crates.io-index" 27 + checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" 28 + dependencies = [ 29 + "litrs", 30 + ] 31 + 32 + [[package]] 33 + name = "embassy-executor-timer-queue" 34 + version = "0.1.0" 35 + source = "registry+https://github.com/rust-lang/crates.io-index" 36 + checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" 37 + 38 + [[package]] 39 + name = "embassy-strike-driver" 40 + version = "0.1.0" 41 + dependencies = [ 42 + "critical-section", 43 + "embassy-sync", 44 + "embassy-time", 45 + "pollster", 46 + ] 47 + 48 + [[package]] 49 + name = "embassy-sync" 50 + version = "0.7.2" 51 + source = "registry+https://github.com/rust-lang/crates.io-index" 52 + checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" 53 + dependencies = [ 54 + "cfg-if", 55 + "critical-section", 56 + "embedded-io-async", 57 + "futures-core", 58 + "futures-sink", 59 + "heapless", 60 + ] 61 + 62 + [[package]] 63 + name = "embassy-time" 64 + version = "0.5.0" 65 + source = "registry+https://github.com/rust-lang/crates.io-index" 66 + checksum = "f4fa65b9284d974dad7a23bb72835c4ec85c0b540d86af7fc4098c88cff51d65" 67 + dependencies = [ 68 + "cfg-if", 69 + "critical-section", 70 + "document-features", 71 + "embassy-time-driver", 72 + "embassy-time-queue-utils", 73 + "embedded-hal 0.2.7", 74 + "embedded-hal 1.0.0", 75 + "embedded-hal-async", 76 + "futures-core", 77 + ] 78 + 79 + [[package]] 80 + name = "embassy-time-driver" 81 + version = "0.2.1" 82 + source = "registry+https://github.com/rust-lang/crates.io-index" 83 + checksum = "a0a244c7dc22c8d0289379c8d8830cae06bb93d8f990194d0de5efb3b5ae7ba6" 84 + dependencies = [ 85 + "document-features", 86 + ] 87 + 88 + [[package]] 89 + name = "embassy-time-queue-utils" 90 + version = "0.3.0" 91 + source = "registry+https://github.com/rust-lang/crates.io-index" 92 + checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" 93 + dependencies = [ 94 + "embassy-executor-timer-queue", 95 + "heapless", 96 + ] 97 + 98 + [[package]] 99 + name = "embedded-hal" 100 + version = "0.2.7" 101 + source = "registry+https://github.com/rust-lang/crates.io-index" 102 + checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" 103 + dependencies = [ 104 + "nb 0.1.3", 105 + "void", 106 + ] 107 + 108 + [[package]] 109 + name = "embedded-hal" 110 + version = "1.0.0" 111 + source = "registry+https://github.com/rust-lang/crates.io-index" 112 + checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" 113 + 114 + [[package]] 115 + name = "embedded-hal-async" 116 + version = "1.0.0" 117 + source = "registry+https://github.com/rust-lang/crates.io-index" 118 + checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" 119 + dependencies = [ 120 + "embedded-hal 1.0.0", 121 + ] 122 + 123 + [[package]] 124 + name = "embedded-io" 125 + version = "0.6.1" 126 + source = "registry+https://github.com/rust-lang/crates.io-index" 127 + checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" 128 + 129 + [[package]] 130 + name = "embedded-io-async" 131 + version = "0.6.1" 132 + source = "registry+https://github.com/rust-lang/crates.io-index" 133 + checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" 134 + dependencies = [ 135 + "embedded-io", 136 + ] 137 + 138 + [[package]] 139 + name = "futures-core" 140 + version = "0.3.31" 141 + source = "registry+https://github.com/rust-lang/crates.io-index" 142 + checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 143 + 144 + [[package]] 145 + name = "futures-sink" 146 + version = "0.3.31" 147 + source = "registry+https://github.com/rust-lang/crates.io-index" 148 + checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 149 + 150 + [[package]] 151 + name = "hash32" 152 + version = "0.3.1" 153 + source = "registry+https://github.com/rust-lang/crates.io-index" 154 + checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" 155 + dependencies = [ 156 + "byteorder", 157 + ] 158 + 159 + [[package]] 160 + name = "heapless" 161 + version = "0.8.0" 162 + source = "registry+https://github.com/rust-lang/crates.io-index" 163 + checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" 164 + dependencies = [ 165 + "hash32", 166 + "stable_deref_trait", 167 + ] 168 + 169 + [[package]] 170 + name = "litrs" 171 + version = "1.0.0" 172 + source = "registry+https://github.com/rust-lang/crates.io-index" 173 + checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" 174 + 175 + [[package]] 176 + name = "nb" 177 + version = "0.1.3" 178 + source = "registry+https://github.com/rust-lang/crates.io-index" 179 + checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" 180 + dependencies = [ 181 + "nb 1.1.0", 182 + ] 183 + 184 + [[package]] 185 + name = "nb" 186 + version = "1.1.0" 187 + source = "registry+https://github.com/rust-lang/crates.io-index" 188 + checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" 189 + 190 + [[package]] 191 + name = "pollster" 192 + version = "0.4.0" 193 + source = "registry+https://github.com/rust-lang/crates.io-index" 194 + checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" 195 + dependencies = [ 196 + "pollster-macro", 197 + ] 198 + 199 + [[package]] 200 + name = "pollster-macro" 201 + version = "0.4.0" 202 + source = "registry+https://github.com/rust-lang/crates.io-index" 203 + checksum = "ac5da421106a50887c5b51d20806867db377fbb86bacf478ee0500a912e0c113" 204 + dependencies = [ 205 + "proc-macro2", 206 + "quote", 207 + "syn", 208 + ] 209 + 210 + [[package]] 211 + name = "proc-macro2" 212 + version = "1.0.106" 213 + source = "registry+https://github.com/rust-lang/crates.io-index" 214 + checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" 215 + dependencies = [ 216 + "unicode-ident", 217 + ] 218 + 219 + [[package]] 220 + name = "quote" 221 + version = "1.0.44" 222 + source = "registry+https://github.com/rust-lang/crates.io-index" 223 + checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" 224 + dependencies = [ 225 + "proc-macro2", 226 + ] 227 + 228 + [[package]] 229 + name = "stable_deref_trait" 230 + version = "1.2.1" 231 + source = "registry+https://github.com/rust-lang/crates.io-index" 232 + checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 233 + 234 + [[package]] 235 + name = "syn" 236 + version = "2.0.114" 237 + source = "registry+https://github.com/rust-lang/crates.io-index" 238 + checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" 239 + dependencies = [ 240 + "proc-macro2", 241 + "quote", 242 + "unicode-ident", 243 + ] 244 + 245 + [[package]] 246 + name = "unicode-ident" 247 + version = "1.0.22" 248 + source = "registry+https://github.com/rust-lang/crates.io-index" 249 + checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 250 + 251 + [[package]] 252 + name = "void" 253 + version = "1.0.2" 254 + source = "registry+https://github.com/rust-lang/crates.io-index" 255 + checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
+16
Cargo.toml
··· 1 + [workspace] 2 + resolver = "3" 3 + members = ["embassy-strike-driver"] 4 + 5 + [workspace.package] 6 + authors = ["Sachy.dev <sachymetsu@tutamail.com>"] 7 + edition = "2024" 8 + repository = "https://tangled.org/sachy.dev/strike-sensor" 9 + version = "0.1.0" 10 + license = "AGPL-3.0-only" 11 + rust-version = "1.91.0" 12 + 13 + [workspace.dependencies] 14 + embassy-time = "0.5" 15 + embassy-sync = "0.7" 16 + defmt = "1"
+5 -1
README.md
··· 4 4 5 5 The sensor designs can be found in the [kicad](./kicad) folder. 6 6 7 + ## Driver 8 + 9 + Currently, the driver needs to be tested and coded according to how the new PCB/circuit behaves, so this will require a bit of work before considered "ready". 10 + 7 11 ## Licenses 8 12 9 - The sensor hardware designs are licensed under the [CERN Open Hardware Licence Version 2 - Strongly Reciprocal](./kicad/LICENSE-CERN-OHL-S) license. 13 + The sensor hardware designs are licensed under the [CERN Open Hardware Licence Version 2 - Strongly Reciprocal](./kicad/LICENSE-CERN-OHL-S) license. ⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁤⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁤⁤⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁤⁢⁢⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁤⁢⁤⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁤⁤⁤⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁤⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁢⁤⁤⁢⁤⁤⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁤⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁤⁤⁤⁢⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁤⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁤⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁢⁤⁤⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁤⁤⁤⁢⁢⁤⁤⁢⁤⁤⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁤⁤⁤⁢⁢⁤⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁢⁤⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁤⁢⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁤⁤⁤⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁤⁢⁤⁢⁢⁤⁢⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁤⁤⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁤⁢⁤⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁤⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁤⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁤⁢⁤⁤⁢⁤⁢⁤⁢⁢⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁤⁢⁤⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢
+17
embassy-strike-driver/Cargo.toml
··· 1 + [package] 2 + name = "embassy-strike-driver" 3 + authors.workspace = true 4 + edition.workspace = true 5 + repository.workspace = true 6 + version.workspace = true 7 + license.workspace = true 8 + rust-version.workspace = true 9 + 10 + [dependencies] 11 + embassy-time.workspace = true 12 + embassy-sync.workspace = true 13 + 14 + [dev-dependencies] 15 + embassy-time = { workspace = true, features = ["mock-driver", "generic-queue-8"] } 16 + critical-section = { version = "1.1", features = ["std"] } 17 + pollster = { version = "0.4", features = ["macro"] }
+576
embassy-strike-driver/src/lib.rs
··· 1 + //! A generalised Embassy driver for the Strike Sensor v1 2 + //! ⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁤⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁤⁤⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁤⁢⁢⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁤⁢⁤⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁤⁤⁤⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁤⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁢⁤⁤⁢⁤⁤⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁤⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁤⁤⁤⁢⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁤⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁤⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁢⁤⁤⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁤⁤⁤⁢⁢⁤⁤⁢⁤⁤⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁤⁤⁤⁢⁢⁤⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁢⁤⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁤⁢⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁤⁤⁤⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁤⁢⁤⁢⁢⁤⁢⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁤⁤⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁤⁢⁤⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁤⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁤⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁤⁢⁤⁤⁢⁤⁢⁤⁢⁢⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁤⁢⁤⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢ 3 + #![no_std] 4 + 5 + use core::cell::Cell; 6 + 7 + use embassy_sync::{ 8 + blocking_mutex::raw::NoopRawMutex, 9 + zerocopy_channel::{Channel, Receiver, Sender}, 10 + }; 11 + use embassy_time::{Duration, Instant, Ticker, Timer}; 12 + 13 + pub const BLOCK_SIZE: usize = 512; 14 + pub type ZeroCopyChannel<'device> = Channel<'device, NoopRawMutex, (i64, [u16; BLOCK_SIZE])>; 15 + 16 + pub trait TimeSource { 17 + fn timestamp(&self) -> i64; 18 + fn get_source() -> Self; 19 + } 20 + 21 + pub trait AdcSource { 22 + fn sample_average(&self, samples: &mut [u16]) -> impl Future<Output = u16>; 23 + fn sample(&self, samples: &mut [u16]) -> impl Future<Output = ()>; 24 + } 25 + 26 + pub trait PwmSource { 27 + fn set_duty(&mut self, duty: u16); 28 + } 29 + 30 + /// A Mutable Buffer trait ⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁤⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁤⁤⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁤⁢⁢⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁤⁢⁤⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁤⁢⁤⁢⁤⁤⁤⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁤⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁢⁤⁤⁢⁤⁤⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁤⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁤⁤⁤⁢⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁤⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁤⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁢⁤⁤⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁤⁢⁢⁤⁢⁢⁢⁤⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁤⁤⁤⁢⁢⁤⁤⁢⁤⁤⁢⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁤⁤⁤⁢⁢⁤⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁢⁤⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁤⁢⁤⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁤⁢⁤⁤⁤⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁤⁢⁤⁢⁢⁤⁢⁤⁤⁢⁢⁢⁢⁢⁤⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁤⁤⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁤⁢⁤⁤⁤⁢⁤⁤⁤⁤⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁢⁢⁤⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁢⁢⁤⁢⁤⁤⁢⁤⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁢⁤⁤⁤⁢⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁤⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁤⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁤⁢⁤⁢⁢⁤⁤⁢⁤⁢⁤⁢⁤⁢⁢⁢⁢⁤⁢⁤⁤⁢⁤⁢⁤⁢⁢⁢⁤⁤⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁤⁢⁢⁤⁤⁢⁢⁢⁤⁢⁢⁢⁤⁢⁤⁢⁢⁤⁢⁢⁢⁢⁤⁢⁢⁤⁢⁢⁤⁢⁢⁢⁤⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁤⁢⁤⁢⁢⁤⁢⁢⁢⁢⁢⁤⁢⁤⁢⁤⁢ 31 + pub trait BufferMut<T> { 32 + fn push(&mut self, value: T); 33 + fn clear(&mut self); 34 + fn as_slice(&self) -> &[T]; 35 + fn len(&self) -> usize; 36 + fn is_empty(&self) -> bool; 37 + } 38 + 39 + #[derive(Debug)] 40 + struct DetectorState { 41 + max_duty: Cell<u8>, 42 + duty: Cell<u8>, 43 + avg: Cell<u16>, 44 + strikes: Cell<u16>, 45 + warn_level: Cell<u16>, 46 + } 47 + 48 + impl Default for DetectorState { 49 + fn default() -> Self { 50 + Self { 51 + max_duty: Cell::new(0), 52 + duty: Cell::new(0), 53 + avg: Cell::new(0), 54 + strikes: Cell::new(0), 55 + warn_level: Cell::new(255), 56 + } 57 + } 58 + } 59 + 60 + #[derive(Debug)] 61 + pub struct DetectorConfig { 62 + blip_threshold: Cell<u16>, 63 + blip_size: Cell<usize>, 64 + } 65 + 66 + impl Default for DetectorConfig { 67 + fn default() -> Self { 68 + Self { 69 + blip_threshold: Cell::new(14), 70 + blip_size: Cell::new(2), 71 + } 72 + } 73 + } 74 + 75 + #[derive(Debug, Clone, Copy, PartialEq, Eq)] 76 + pub enum DetectorUpdate<'a> { 77 + Tick { 78 + timestamp: i64, 79 + level: u16, 80 + }, 81 + Detection { 82 + timestamp: i64, 83 + samples: &'a [u16], 84 + peaks: &'a [usize], 85 + }, 86 + } 87 + 88 + pub struct DetectorDriver<T: TimeSource, P: PwmSource, A: AdcSource> { 89 + state: DetectorState, 90 + config: DetectorConfig, 91 + timer_source: T, 92 + pwm: P, 93 + adc: A, 94 + } 95 + 96 + impl<T, P, A> DetectorDriver<T, P, A> 97 + where 98 + T: TimeSource, 99 + P: PwmSource, 100 + A: AdcSource, 101 + { 102 + pub fn new(config: DetectorConfig, timer_source: T, pwm: P, adc: A) -> Self { 103 + Self { 104 + state: Default::default(), 105 + config, 106 + timer_source, 107 + pwm, 108 + adc, 109 + } 110 + } 111 + 112 + pub fn reset_timer_source(&mut self) { 113 + self.timer_source = T::get_source(); 114 + } 115 + 116 + pub fn get_timestamp(&self) -> i64 { 117 + self.timer_source.timestamp() 118 + } 119 + 120 + pub fn set_blip_threshold(&self, threshold: u16) { 121 + self.config.blip_threshold.set(threshold); 122 + } 123 + 124 + pub fn set_blip_size(&self, size: usize) { 125 + self.config.blip_size.set(size); 126 + } 127 + 128 + pub async fn tune(&mut self, samples: &mut [u16]) { 129 + // info!("Tuning Detector for correct voltage settings"); 130 + let mut duty = 0; 131 + self.pwm.set_duty(duty); 132 + Timer::after_secs(2).await; 133 + let mut act_value = self.adc.sample_average(samples).await; 134 + 135 + // info!("initial ACT: {}", act_value); 136 + 137 + while act_value < 1364 { 138 + duty += 2; 139 + if duty >= 256 { 140 + duty = 0; 141 + self.pwm.set_duty(duty); 142 + Timer::after_secs(2).await; 143 + act_value = self.adc.sample_average(samples).await; 144 + // info!("Restarting tuning"); 145 + continue; 146 + } 147 + self.pwm.set_duty(duty); 148 + Timer::after_millis(250).await; 149 + act_value = self.adc.sample_average(samples).await; 150 + // info!("ACT: {}, Duty: {}", act_value, duty); 151 + } 152 + self.state.max_duty.set(duty as u8); 153 + duty = (duty / 3) * 2; 154 + self.pwm.set_duty(duty); 155 + self.state.duty.set(duty as u8); 156 + // info!("Set detection duty to: {}", duty); 157 + // Allow voltage level to stabilize after tuning 158 + Timer::after_secs(2).await; 159 + let avg = self.adc.sample_average(samples).await; 160 + self.state.avg.set(avg); 161 + } 162 + 163 + /// Samples and returns an `i64` timestamp. 164 + pub async fn sample_with_zerocopy<'a>( 165 + &self, 166 + dma: &mut Sender<'a, NoopRawMutex, (i64, [u16; BLOCK_SIZE])>, 167 + ) { 168 + loop { 169 + let (time, samples) = dma.send().await; 170 + *time = self.timer_source.timestamp(); 171 + self.adc.sample(samples).await; 172 + dma.send_done(); 173 + } 174 + } 175 + 176 + pub fn detect_from_sample<B, F>( 177 + &self, 178 + timestamp: i64, 179 + samples: &[u16], 180 + peaks: &mut B, 181 + update: F, 182 + ) where 183 + B: BufferMut<usize>, 184 + F: Fn(DetectorUpdate<'_>), 185 + { 186 + peaks.clear(); 187 + 188 + let new_avg = analyse_buffer_by_stepped_windows( 189 + self.config.blip_threshold.get(), 190 + samples, 191 + self.state.avg.get(), 192 + peaks, 193 + ); 194 + 195 + let blips = peaks.len(); 196 + 197 + if blips >= self.config.blip_size.get() { 198 + self.state 199 + .strikes 200 + .update(|strike| strike.saturating_add(32)); 201 + 202 + update(DetectorUpdate::Detection { 203 + timestamp, 204 + samples, 205 + peaks: peaks.as_slice(), 206 + }); 207 + } 208 + 209 + self.state.avg.set(new_avg); 210 + } 211 + 212 + pub async fn detect_with_zerocopy<'a, 'd, B, F>( 213 + &self, 214 + dma: &mut Receiver<'a, NoopRawMutex, (i64, [u16; BLOCK_SIZE])>, 215 + peaks: &'d mut B, 216 + update: F, 217 + ) where 218 + B: BufferMut<usize>, 219 + F: Fn(DetectorUpdate<'_>), 220 + { 221 + loop { 222 + let (timestamp, samples) = dma.receive().await; 223 + 224 + self.detect_from_sample(*timestamp, samples, peaks, &update); 225 + 226 + dma.receive_done(); 227 + } 228 + } 229 + 230 + pub async fn tick<F>(&self, update: F) 231 + where 232 + F: Fn(DetectorUpdate), 233 + { 234 + let mut inactive: Option<Instant> = None; 235 + let mut interval = Ticker::every(Duration::from_secs(1)); 236 + 237 + loop { 238 + interval.next().await; 239 + let strikes = self.state.strikes.get(); 240 + let mut warn_level = self.state.warn_level.get(); 241 + 242 + if strikes > 32 { 243 + warn_level = warn_level.saturating_add(strikes); 244 + } 245 + 246 + let decay = warn_level >> 8; 247 + 248 + self.state.strikes.set(0); 249 + self.state.warn_level.set(warn_level - decay); 250 + 251 + match inactive { 252 + Some(_) if decay > 0 => { 253 + inactive = None; 254 + } 255 + Some(val) if val.elapsed() >= Duration::from_secs(3600) => break, 256 + None if decay == 0 => { 257 + inactive = Some(Instant::now()); 258 + } 259 + None => { 260 + update(DetectorUpdate::Tick { 261 + timestamp: self.timer_source.timestamp(), 262 + level: warn_level - 255, 263 + }); 264 + } 265 + _ => continue, 266 + } 267 + } 268 + } 269 + } 270 + 271 + fn analyse_buffer_by_stepped_windows<B: BufferMut<usize>>( 272 + threshold: u16, 273 + buf: &[u16], 274 + average: u16, 275 + peaks: &mut B, 276 + ) -> u16 { 277 + const CHUNK_SIZE: usize = BLOCK_SIZE / 32; 278 + const CHUNK_STEP: usize = CHUNK_SIZE / 2; 279 + 280 + let mut total = 0u32; 281 + let mut len = 0u32; 282 + 283 + for (i, window) in buf.windows(CHUNK_SIZE).enumerate().step_by(CHUNK_STEP) { 284 + let window_total = window.iter().copied().sum::<u16>(); 285 + let window_avg = window_total / CHUNK_SIZE as u16; 286 + let diff = average.saturating_sub(window_avg); 287 + 288 + if diff > threshold { 289 + peaks.push(i); 290 + } else { 291 + total += window_total as u32; 292 + len += CHUNK_SIZE as u32; 293 + } 294 + } 295 + 296 + (total / len) as u16 297 + } 298 + 299 + #[cfg(test)] 300 + mod tests { 301 + use core::future::poll_fn; 302 + use embassy_time::MockDriver; 303 + 304 + extern crate alloc; 305 + use super::*; 306 + 307 + #[derive(Debug, Default)] 308 + struct MockMachine { 309 + pwm: Cell<u16>, 310 + } 311 + 312 + impl MockMachine { 313 + fn adc_sample_avg(&self) -> u16 { 314 + self.pwm.get().saturating_mul(14) 315 + } 316 + } 317 + 318 + struct MockPwm<'a>(&'a MockMachine); 319 + 320 + impl PwmSource for MockPwm<'_> { 321 + fn set_duty(&mut self, duty: u16) { 322 + self.0.pwm.set(duty); 323 + } 324 + } 325 + 326 + struct MockAdc<'a>(&'a MockMachine); 327 + 328 + impl AdcSource for MockAdc<'_> { 329 + async fn sample(&self, samples: &mut [u16]) { 330 + samples.fill(self.0.adc_sample_avg()); 331 + } 332 + 333 + async fn sample_average(&self, _samples: &mut [u16]) -> u16 { 334 + self.0.adc_sample_avg() 335 + } 336 + } 337 + 338 + struct MockTimeSource; 339 + 340 + impl TimeSource for MockTimeSource { 341 + fn get_source() -> Self { 342 + Self 343 + } 344 + fn timestamp(&self) -> i64 { 345 + 0 346 + } 347 + } 348 + 349 + impl BufferMut<usize> for alloc::vec::Vec<usize> { 350 + fn push(&mut self, value: usize) { 351 + self.push(value); 352 + } 353 + 354 + fn clear(&mut self) { 355 + self.clear(); 356 + } 357 + 358 + fn len(&self) -> usize { 359 + self.len() 360 + } 361 + 362 + fn is_empty(&self) -> bool { 363 + self.is_empty() 364 + } 365 + 366 + fn as_slice(&self) -> &[usize] { 367 + self 368 + } 369 + } 370 + 371 + async fn tune_detector_manually<'a>( 372 + detector: &mut DetectorDriver<MockTimeSource, MockPwm<'a>, MockAdc<'a>>, 373 + buf: &mut [u16], 374 + driver: &MockDriver, 375 + ) { 376 + let mut tuning = core::pin::pin!(detector.tune(buf)); 377 + 378 + poll_fn(|cx| match tuning.as_mut().poll(cx) { 379 + core::task::Poll::Ready(_) => core::task::Poll::Ready(()), 380 + core::task::Poll::Pending => { 381 + cx.waker().wake_by_ref(); 382 + driver.advance(Duration::from_secs(2)); 383 + 384 + core::task::Poll::Pending 385 + } 386 + }) 387 + .await; 388 + } 389 + 390 + fn generate_signal(samples: &mut [u16]) { 391 + // Example voltage drop signal 392 + samples[5] -= 60; 393 + samples[6] -= 55; 394 + samples[7] -= 50; 395 + samples[8] -= 45; 396 + samples[9] -= 40; 397 + samples[10] -= 35; 398 + samples[11] -= 30; 399 + samples[12] -= 28; 400 + samples[13] -= 26; 401 + samples[14] -= 24; 402 + samples[15] -= 22; 403 + samples[16] -= 20; 404 + samples[17] -= 18; 405 + samples[18] -= 16; 406 + samples[19] -= 14; 407 + samples[20] -= 12; 408 + samples[21] -= 11; 409 + samples[22] -= 10; 410 + samples[23] -= 9; 411 + samples[24] -= 8; 412 + samples[25] -= 7; 413 + samples[26] -= 6; 414 + samples[27] -= 5; 415 + samples[28] -= 4; 416 + samples[29] -= 3; 417 + samples[30] -= 2; 418 + samples[31] -= 1; 419 + } 420 + 421 + #[pollster::test] 422 + async fn tuning_cycle_completes() { 423 + let driver = embassy_time::MockDriver::get(); 424 + driver.reset(); 425 + let device = MockMachine::default(); 426 + let pwm = MockPwm(&device); 427 + let adc = MockAdc(&device); 428 + 429 + let mut detector = DetectorDriver::new(Default::default(), MockTimeSource, pwm, adc); 430 + let mut buf = [0; 16]; 431 + 432 + tune_detector_manually(&mut detector, &mut buf, driver).await; 433 + 434 + assert_eq!(detector.state.max_duty.get(), 98); 435 + assert_eq!(detector.state.duty.get(), 64); 436 + assert_eq!(detector.adc.sample_average(&mut buf).await, 896); 437 + } 438 + 439 + #[pollster::test] 440 + async fn tick_updates_only_on_raised_levels() { 441 + let driver = embassy_time::MockDriver::get(); 442 + driver.reset(); 443 + let device = MockMachine::default(); 444 + let pwm = MockPwm(&device); 445 + let adc = MockAdc(&device); 446 + 447 + let detector = DetectorDriver::new(Default::default(), MockTimeSource, pwm, adc); 448 + 449 + detector.state.max_duty.set(98); 450 + detector.state.duty.set(64); 451 + detector.state.avg.set(896); 452 + 453 + { 454 + let mut tick = core::pin::pin!(detector.tick(|a| { 455 + assert_eq!( 456 + a, 457 + DetectorUpdate::Tick { 458 + timestamp: 0, 459 + level: 300 460 + } 461 + ); 462 + })); 463 + 464 + poll_fn(|cx| match tick.as_mut().poll(cx) { 465 + core::task::Poll::Ready(_) => core::task::Poll::Ready(()), 466 + core::task::Poll::Pending => { 467 + if detector.state.warn_level.get() > 255 { 468 + return core::task::Poll::Ready(()); 469 + } 470 + detector.state.strikes.set(300); 471 + driver.advance(Duration::from_secs(1)); 472 + cx.waker().wake_by_ref(); 473 + core::task::Poll::Pending 474 + } 475 + }) 476 + .await; 477 + } 478 + 479 + assert_eq!(detector.state.warn_level.get(), 553); 480 + } 481 + 482 + #[pollster::test] 483 + async fn detection_updates_only_on_large_enough_signals() { 484 + let driver = embassy_time::MockDriver::get(); 485 + driver.reset(); 486 + let device = MockMachine::default(); 487 + let pwm = MockPwm(&device); 488 + let adc = MockAdc(&device); 489 + 490 + let detector = DetectorDriver::new(Default::default(), MockTimeSource, pwm, adc); 491 + detector.pwm.0.pwm.set(64); 492 + detector.state.max_duty.set(98); 493 + detector.state.duty.set(64); 494 + detector.state.avg.set(896); 495 + 496 + let mut samples = alloc::vec![0; BLOCK_SIZE]; 497 + 498 + detector.adc.sample(&mut samples).await; 499 + 500 + samples[5] -= 3; 501 + samples[6] -= 1; 502 + 503 + let mut peaks = alloc::vec::Vec::with_capacity(512); 504 + 505 + let update = |_update: DetectorUpdate<'_>| { 506 + panic!("This update function shouldn't be called"); 507 + }; 508 + 509 + detector.detect_from_sample(0, &samples, &mut peaks, update); 510 + 511 + assert_eq!(peaks.len(), 0); 512 + 513 + generate_signal(&mut samples); 514 + 515 + let expected_peaks = alloc::vec![0, 8]; 516 + let called = Cell::new(false); 517 + 518 + let update = |update: DetectorUpdate<'_>| { 519 + called.set(true); 520 + assert_eq!( 521 + update, 522 + DetectorUpdate::Detection { 523 + timestamp: 0, 524 + samples: &samples, 525 + peaks: expected_peaks.as_slice() 526 + } 527 + ) 528 + }; 529 + 530 + detector.detect_from_sample(0, &samples, &mut peaks, update); 531 + 532 + assert_eq!(peaks.len(), 2); 533 + assert!(called.get()); 534 + } 535 + 536 + #[pollster::test] 537 + async fn detection_sensitivity_can_be_configured() { 538 + let driver = embassy_time::MockDriver::get(); 539 + driver.reset(); 540 + let device = MockMachine::default(); 541 + let pwm = MockPwm(&device); 542 + let adc = MockAdc(&device); 543 + 544 + let detector = DetectorDriver::new(Default::default(), MockTimeSource, pwm, adc); 545 + detector.pwm.0.pwm.set(64); 546 + detector.state.max_duty.set(98); 547 + detector.state.duty.set(64); 548 + detector.state.avg.set(896); 549 + 550 + let mut samples = alloc::vec![0; BLOCK_SIZE]; 551 + let mut peaks: alloc::vec::Vec<usize> = alloc::vec::Vec::with_capacity(BLOCK_SIZE); 552 + 553 + // Require bigger size blips. 554 + detector.config.blip_size.set(3); 555 + 556 + detector.adc.sample(&mut samples).await; 557 + generate_signal(&mut samples); 558 + 559 + detector.detect_from_sample(0, &samples, &mut peaks, |_update| { 560 + panic!("Update shouldn't be called"); 561 + }); 562 + 563 + assert_eq!(peaks.len(), 2); 564 + 565 + // Require bigger threshold for blip detection 566 + detector.config.blip_size.set(2); 567 + detector.config.blip_threshold.set(24); 568 + 569 + detector.detect_from_sample(0, &samples, &mut peaks, |_update| { 570 + panic!("Update shouldn't be called"); 571 + }); 572 + 573 + // Update didn't call because detected blip wasn't big enough 574 + assert_eq!(peaks.len(), 1); 575 + } 576 + }