A nightstand noise generator based on M5Stack Atom Echo and integrating with Home Assistant
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Initial commit

Two-Atom-Echo nightstand sound machine project. Includes design docs
(MQTT contract, operating modes, signal chain), hardware reference
(Atom Echo pinmap, datasheets, speaker notes), and a v0.0.1 Rust
firmware that boots, reads the button, and plays a beep through the
onboard amp.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Chris Guidry 82210c19

+3661
+17
.envrc
··· 1 + #!/usr/bin/env bash 2 + # ESP32 / Xtensa Rust toolchain environment. 3 + # 4 + # These were originally written by `espup install` to ~/export-esp.sh; we 5 + # keep them project-local instead so the home dir stays clean. If `espup 6 + # update` is ever run, copy any new/changed exports here from the file 7 + # espup writes (or run `espup install --export-file ./.envrc-esp` to write 8 + # them directly somewhere project-local). 9 + 10 + export PATH="$HOME/.rustup/toolchains/esp/xtensa-esp-elf/esp-15.2.0_20250920/xtensa-esp-elf/bin:$PATH" 11 + export LIBCLANG_PATH="$HOME/.rustup/toolchains/esp/xtensa-esp32-elf-clang/esp-20.1.1_20250829/esp-clang/lib" 12 + 13 + # Project-local SONAME shim: Espressif's bundled esp-clang is linked 14 + # against libxml2.so.2, but Ubuntu Questing only ships libxml2.so.16. 15 + # The symlink in firmware/.lib points the old SONAME at the new library; 16 + # the ABI clang touches is small and stable enough that this works. 17 + export LD_LIBRARY_PATH="$PWD/firmware/.lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
+4
.gitignore
··· 1 + .direnv/ 2 + *.swp 3 + *.swo 4 + .DS_Store
+46
README.md
··· 1 + # sound-machine 2 + 3 + Two M5Stack Atom Echo–based nightstand white noise machines, replacing the Google Home speakers that used to live on our nightstands. Single button press triggers a Home Assistant routine (lights off, white noise on, etc., depending on time of day), with offline fallback for travel use. Long-press for late-night house lights, double-press to cycle volume presets. 4 + 5 + This repo holds everything for the project: firmware, design docs, hardware reference, and (eventually) 3D-printed enclosure models. 6 + 7 + ## Layout 8 + 9 + ``` 10 + sound-machine/ 11 + ├── README.md # this file 12 + ├── .envrc # direnv: ESP toolchain env + libxml2 shim path 13 + ├── firmware/ # Rust firmware (esp-idf-svc, std). See firmware/README.md 14 + └── reference/ # Design docs and hardware reference 15 + ├── mqtt-contract.md # wire protocol between device and HA 16 + ├── operating-modes.md # firmware state machine, LED scheme, NVS 17 + ├── signal-chain.md # audio path: ESP32 → MAX98357A → speaker 18 + ├── atom-echo/ # M5Stack Atom Echo pinmap, dimensions, schematic 19 + ├── speakers/ # Adafruit 1314 driver notes 20 + └── datasheets/ # vendor PDFs for ESP32-PICO-D4, NS4168, SPM1423 21 + ``` 22 + 23 + ## Status 24 + 25 + - ✅ **Hardware research and selection complete** — see `reference/` 26 + - ✅ **MQTT contract and operating modes designed** — `reference/mqtt-contract.md`, `reference/operating-modes.md` 27 + - ✅ **Toolchain validated end-to-end** — `firmware/` builds, flashes, and runs on real hardware 28 + - ✅ **Hello-world milestone (v0.0.1)** — button + beep working — 2026-04-25 29 + - 🚧 **Awaiting hardware** — MAX98357A amps on order from DigiKey 30 + - 🚧 **Firmware v1 build-out in progress** — see `firmware/README.md` for the per-subsystem state 31 + 32 + ## Architecture in one paragraph 33 + 34 + Each device runs Rust firmware (esp-idf-svc, std mode) on an Atom Echo. WiFi connects to the home network, MQTT to a LAN-only broker (no TLS), HA Discovery announces entities. The button publishes events; HA decides what to do; HA sends back a "play white noise" command. Audio is generated locally on-device (no streaming dependency) and sent over I2S to an external MAX98357A amp driving a 3" 4Ω speaker. Onboard NS4168 amp is bypassed (no I2S data sent to its pins) — it's known not to be sized for sustained white noise. When WiFi or MQTT drops, the device falls into offline mode where the button toggles white noise locally; same code path as travel use. 35 + 36 + Both units run **the same firmware binary** — identity is derived at runtime from the chip's MAC address against a `KNOWN_DEVICES` table in source. One build, OTA-pushed to both. (OTA is v1.5; v1 ships USB-flashed.) 37 + 38 + For the gory details: `firmware/README.md`, `reference/mqtt-contract.md`, `reference/operating-modes.md`. 39 + 40 + ## Quick links 41 + 42 + - **How to build firmware**: [`firmware/README.md`](firmware/README.md) 43 + - **MQTT wire protocol**: [`reference/mqtt-contract.md`](reference/mqtt-contract.md) 44 + - **Firmware behavior spec**: [`reference/operating-modes.md`](reference/operating-modes.md) 45 + - **Audio signal chain**: [`reference/signal-chain.md`](reference/signal-chain.md) 46 + - **Atom Echo pinout**: [`reference/atom-echo/pinmap.md`](reference/atom-echo/pinmap.md)
+14
firmware/.cargo/config.toml
··· 1 + [build] 2 + target = "xtensa-esp32-espidf" 3 + 4 + [target.xtensa-esp32-espidf] 5 + linker = "ldproxy" 6 + runner = "espflash flash --monitor" 7 + rustflags = ["--cfg", "espidf_time64"] 8 + 9 + [unstable] 10 + build-std = ["std", "panic_abort"] 11 + 12 + [env] 13 + MCU = "esp32" 14 + ESP_IDF_VERSION = "v5.3.3"
+5
firmware/.gitignore
··· 1 + /target 2 + /.embuild 3 + /.lib 4 + .cargo/.cache 5 + *.log
+2350
firmware/Cargo.lock
··· 1 + # This file is automatically @generated by Cargo. 2 + # It is not intended for manual editing. 3 + version = 3 4 + 5 + [[package]] 6 + name = "adler2" 7 + version = "2.0.1" 8 + source = "registry+https://github.com/rust-lang/crates.io-index" 9 + checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" 10 + 11 + [[package]] 12 + name = "aho-corasick" 13 + version = "1.1.4" 14 + source = "registry+https://github.com/rust-lang/crates.io-index" 15 + checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" 16 + dependencies = [ 17 + "memchr", 18 + ] 19 + 20 + [[package]] 21 + name = "aligned" 22 + version = "0.4.3" 23 + source = "registry+https://github.com/rust-lang/crates.io-index" 24 + checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685" 25 + dependencies = [ 26 + "as-slice", 27 + ] 28 + 29 + [[package]] 30 + name = "android_system_properties" 31 + version = "0.1.5" 32 + source = "registry+https://github.com/rust-lang/crates.io-index" 33 + checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 34 + dependencies = [ 35 + "libc", 36 + ] 37 + 38 + [[package]] 39 + name = "anyhow" 40 + version = "1.0.102" 41 + source = "registry+https://github.com/rust-lang/crates.io-index" 42 + checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" 43 + 44 + [[package]] 45 + name = "as-slice" 46 + version = "0.2.1" 47 + source = "registry+https://github.com/rust-lang/crates.io-index" 48 + checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" 49 + dependencies = [ 50 + "stable_deref_trait", 51 + ] 52 + 53 + [[package]] 54 + name = "atomic-waker" 55 + version = "1.1.2" 56 + source = "registry+https://github.com/rust-lang/crates.io-index" 57 + checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 58 + 59 + [[package]] 60 + name = "autocfg" 61 + version = "1.5.0" 62 + source = "registry+https://github.com/rust-lang/crates.io-index" 63 + checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" 64 + 65 + [[package]] 66 + name = "base64" 67 + version = "0.22.1" 68 + source = "registry+https://github.com/rust-lang/crates.io-index" 69 + checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 70 + 71 + [[package]] 72 + name = "bindgen" 73 + version = "0.71.1" 74 + source = "registry+https://github.com/rust-lang/crates.io-index" 75 + checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" 76 + dependencies = [ 77 + "bitflags 2.11.1", 78 + "cexpr", 79 + "clang-sys", 80 + "itertools", 81 + "log", 82 + "prettyplease", 83 + "proc-macro2", 84 + "quote", 85 + "regex", 86 + "rustc-hash", 87 + "shlex", 88 + "syn 2.0.117", 89 + ] 90 + 91 + [[package]] 92 + name = "bitflags" 93 + version = "1.3.2" 94 + source = "registry+https://github.com/rust-lang/crates.io-index" 95 + checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 96 + 97 + [[package]] 98 + name = "bitflags" 99 + version = "2.11.1" 100 + source = "registry+https://github.com/rust-lang/crates.io-index" 101 + checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" 102 + 103 + [[package]] 104 + name = "bstr" 105 + version = "1.12.1" 106 + source = "registry+https://github.com/rust-lang/crates.io-index" 107 + checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" 108 + dependencies = [ 109 + "memchr", 110 + "serde", 111 + ] 112 + 113 + [[package]] 114 + name = "build-time" 115 + version = "0.1.3" 116 + source = "registry+https://github.com/rust-lang/crates.io-index" 117 + checksum = "f1219c19fc29b7bfd74b7968b420aff5bc951cf517800176e795d6b2300dd382" 118 + dependencies = [ 119 + "chrono", 120 + "once_cell", 121 + "proc-macro2", 122 + "quote", 123 + "syn 2.0.117", 124 + ] 125 + 126 + [[package]] 127 + name = "bumpalo" 128 + version = "3.20.2" 129 + source = "registry+https://github.com/rust-lang/crates.io-index" 130 + checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" 131 + 132 + [[package]] 133 + name = "byteorder" 134 + version = "1.5.0" 135 + source = "registry+https://github.com/rust-lang/crates.io-index" 136 + checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 137 + 138 + [[package]] 139 + name = "camino" 140 + version = "1.2.2" 141 + source = "registry+https://github.com/rust-lang/crates.io-index" 142 + checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" 143 + dependencies = [ 144 + "serde_core", 145 + ] 146 + 147 + [[package]] 148 + name = "cargo-platform" 149 + version = "0.1.9" 150 + source = "registry+https://github.com/rust-lang/crates.io-index" 151 + checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" 152 + dependencies = [ 153 + "serde", 154 + ] 155 + 156 + [[package]] 157 + name = "cargo_metadata" 158 + version = "0.18.1" 159 + source = "registry+https://github.com/rust-lang/crates.io-index" 160 + checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" 161 + dependencies = [ 162 + "camino", 163 + "cargo-platform", 164 + "semver", 165 + "serde", 166 + "serde_json", 167 + "thiserror 1.0.69", 168 + ] 169 + 170 + [[package]] 171 + name = "cargo_toml" 172 + version = "0.15.3" 173 + source = "registry+https://github.com/rust-lang/crates.io-index" 174 + checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" 175 + dependencies = [ 176 + "serde", 177 + "toml", 178 + ] 179 + 180 + [[package]] 181 + name = "cc" 182 + version = "1.2.61" 183 + source = "registry+https://github.com/rust-lang/crates.io-index" 184 + checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" 185 + dependencies = [ 186 + "find-msvc-tools", 187 + "shlex", 188 + ] 189 + 190 + [[package]] 191 + name = "cexpr" 192 + version = "0.6.0" 193 + source = "registry+https://github.com/rust-lang/crates.io-index" 194 + checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 195 + dependencies = [ 196 + "nom", 197 + ] 198 + 199 + [[package]] 200 + name = "cfg-if" 201 + version = "1.0.4" 202 + source = "registry+https://github.com/rust-lang/crates.io-index" 203 + checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" 204 + 205 + [[package]] 206 + name = "cfg_aliases" 207 + version = "0.2.1" 208 + source = "registry+https://github.com/rust-lang/crates.io-index" 209 + checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 210 + 211 + [[package]] 212 + name = "chrono" 213 + version = "0.4.44" 214 + source = "registry+https://github.com/rust-lang/crates.io-index" 215 + checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" 216 + dependencies = [ 217 + "iana-time-zone", 218 + "num-traits", 219 + "windows-link", 220 + ] 221 + 222 + [[package]] 223 + name = "clang-sys" 224 + version = "1.8.1" 225 + source = "registry+https://github.com/rust-lang/crates.io-index" 226 + checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 227 + dependencies = [ 228 + "glob", 229 + "libc", 230 + "libloading", 231 + ] 232 + 233 + [[package]] 234 + name = "cmake" 235 + version = "0.1.58" 236 + source = "registry+https://github.com/rust-lang/crates.io-index" 237 + checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" 238 + dependencies = [ 239 + "cc", 240 + ] 241 + 242 + [[package]] 243 + name = "const_format" 244 + version = "0.2.36" 245 + source = "registry+https://github.com/rust-lang/crates.io-index" 246 + checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" 247 + dependencies = [ 248 + "const_format_proc_macros", 249 + "konst", 250 + ] 251 + 252 + [[package]] 253 + name = "const_format_proc_macros" 254 + version = "0.2.34" 255 + source = "registry+https://github.com/rust-lang/crates.io-index" 256 + checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" 257 + dependencies = [ 258 + "proc-macro2", 259 + "quote", 260 + "unicode-xid", 261 + ] 262 + 263 + [[package]] 264 + name = "core-foundation-sys" 265 + version = "0.8.7" 266 + source = "registry+https://github.com/rust-lang/crates.io-index" 267 + checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 268 + 269 + [[package]] 270 + name = "crc32fast" 271 + version = "1.5.0" 272 + source = "registry+https://github.com/rust-lang/crates.io-index" 273 + checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" 274 + dependencies = [ 275 + "cfg-if", 276 + ] 277 + 278 + [[package]] 279 + name = "critical-section" 280 + version = "1.2.0" 281 + source = "registry+https://github.com/rust-lang/crates.io-index" 282 + checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 283 + 284 + [[package]] 285 + name = "crossbeam-deque" 286 + version = "0.8.6" 287 + source = "registry+https://github.com/rust-lang/crates.io-index" 288 + checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" 289 + dependencies = [ 290 + "crossbeam-epoch", 291 + "crossbeam-utils", 292 + ] 293 + 294 + [[package]] 295 + name = "crossbeam-epoch" 296 + version = "0.9.18" 297 + source = "registry+https://github.com/rust-lang/crates.io-index" 298 + checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 299 + dependencies = [ 300 + "crossbeam-utils", 301 + ] 302 + 303 + [[package]] 304 + name = "crossbeam-utils" 305 + version = "0.8.21" 306 + source = "registry+https://github.com/rust-lang/crates.io-index" 307 + checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 308 + 309 + [[package]] 310 + name = "cvt" 311 + version = "0.1.2" 312 + source = "registry+https://github.com/rust-lang/crates.io-index" 313 + checksum = "d2ae9bf77fbf2d39ef573205d554d87e86c12f1994e9ea335b0651b9b278bcf1" 314 + dependencies = [ 315 + "cfg-if", 316 + ] 317 + 318 + [[package]] 319 + name = "darling" 320 + version = "0.21.3" 321 + source = "registry+https://github.com/rust-lang/crates.io-index" 322 + checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" 323 + dependencies = [ 324 + "darling_core", 325 + "darling_macro", 326 + ] 327 + 328 + [[package]] 329 + name = "darling_core" 330 + version = "0.21.3" 331 + source = "registry+https://github.com/rust-lang/crates.io-index" 332 + checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" 333 + dependencies = [ 334 + "fnv", 335 + "ident_case", 336 + "proc-macro2", 337 + "quote", 338 + "syn 2.0.117", 339 + ] 340 + 341 + [[package]] 342 + name = "darling_macro" 343 + version = "0.21.3" 344 + source = "registry+https://github.com/rust-lang/crates.io-index" 345 + checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" 346 + dependencies = [ 347 + "darling_core", 348 + "quote", 349 + "syn 2.0.117", 350 + ] 351 + 352 + [[package]] 353 + name = "defmt" 354 + version = "0.3.100" 355 + source = "registry+https://github.com/rust-lang/crates.io-index" 356 + checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" 357 + dependencies = [ 358 + "defmt 1.0.1", 359 + ] 360 + 361 + [[package]] 362 + name = "defmt" 363 + version = "1.0.1" 364 + source = "registry+https://github.com/rust-lang/crates.io-index" 365 + checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" 366 + dependencies = [ 367 + "bitflags 1.3.2", 368 + "defmt-macros", 369 + ] 370 + 371 + [[package]] 372 + name = "defmt-macros" 373 + version = "1.0.1" 374 + source = "registry+https://github.com/rust-lang/crates.io-index" 375 + checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" 376 + dependencies = [ 377 + "defmt-parser", 378 + "proc-macro-error2", 379 + "proc-macro2", 380 + "quote", 381 + "syn 2.0.117", 382 + ] 383 + 384 + [[package]] 385 + name = "defmt-parser" 386 + version = "1.0.0" 387 + source = "registry+https://github.com/rust-lang/crates.io-index" 388 + checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" 389 + dependencies = [ 390 + "thiserror 2.0.18", 391 + ] 392 + 393 + [[package]] 394 + name = "displaydoc" 395 + version = "0.2.5" 396 + source = "registry+https://github.com/rust-lang/crates.io-index" 397 + checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 398 + dependencies = [ 399 + "proc-macro2", 400 + "quote", 401 + "syn 2.0.117", 402 + ] 403 + 404 + [[package]] 405 + name = "either" 406 + version = "1.15.0" 407 + source = "registry+https://github.com/rust-lang/crates.io-index" 408 + checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" 409 + 410 + [[package]] 411 + name = "embassy-futures" 412 + version = "0.1.2" 413 + source = "registry+https://github.com/rust-lang/crates.io-index" 414 + checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" 415 + 416 + [[package]] 417 + name = "embassy-sync" 418 + version = "0.6.2" 419 + source = "registry+https://github.com/rust-lang/crates.io-index" 420 + checksum = "8d2c8cdff05a7a51ba0087489ea44b0b1d97a296ca6b1d6d1a33ea7423d34049" 421 + dependencies = [ 422 + "cfg-if", 423 + "critical-section", 424 + "embedded-io-async", 425 + "futures-sink", 426 + "futures-util", 427 + "heapless", 428 + ] 429 + 430 + [[package]] 431 + name = "embedded-can" 432 + version = "0.4.1" 433 + source = "registry+https://github.com/rust-lang/crates.io-index" 434 + checksum = "e9d2e857f87ac832df68fa498d18ddc679175cf3d2e4aa893988e5601baf9438" 435 + dependencies = [ 436 + "nb 1.1.0", 437 + ] 438 + 439 + [[package]] 440 + name = "embedded-hal" 441 + version = "0.2.7" 442 + source = "registry+https://github.com/rust-lang/crates.io-index" 443 + checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" 444 + dependencies = [ 445 + "nb 0.1.3", 446 + "void", 447 + ] 448 + 449 + [[package]] 450 + name = "embedded-hal" 451 + version = "1.0.0" 452 + source = "registry+https://github.com/rust-lang/crates.io-index" 453 + checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" 454 + 455 + [[package]] 456 + name = "embedded-hal-async" 457 + version = "1.0.0" 458 + source = "registry+https://github.com/rust-lang/crates.io-index" 459 + checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" 460 + dependencies = [ 461 + "embedded-hal 1.0.0", 462 + ] 463 + 464 + [[package]] 465 + name = "embedded-hal-nb" 466 + version = "1.0.0" 467 + source = "registry+https://github.com/rust-lang/crates.io-index" 468 + checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" 469 + dependencies = [ 470 + "embedded-hal 1.0.0", 471 + "nb 1.1.0", 472 + ] 473 + 474 + [[package]] 475 + name = "embedded-io" 476 + version = "0.6.1" 477 + source = "registry+https://github.com/rust-lang/crates.io-index" 478 + checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" 479 + 480 + [[package]] 481 + name = "embedded-io-async" 482 + version = "0.6.1" 483 + source = "registry+https://github.com/rust-lang/crates.io-index" 484 + checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" 485 + dependencies = [ 486 + "embedded-io", 487 + ] 488 + 489 + [[package]] 490 + name = "embedded-svc" 491 + version = "0.28.1" 492 + source = "registry+https://github.com/rust-lang/crates.io-index" 493 + checksum = "a7770e30ab55cfbf954c00019522490d6ce26a3334bede05a732ba61010e98e0" 494 + dependencies = [ 495 + "defmt 0.3.100", 496 + "embedded-io", 497 + "embedded-io-async", 498 + "enumset", 499 + "heapless", 500 + "num_enum", 501 + "serde", 502 + "strum 0.25.0", 503 + ] 504 + 505 + [[package]] 506 + name = "embuild" 507 + version = "0.33.1" 508 + source = "registry+https://github.com/rust-lang/crates.io-index" 509 + checksum = "e188ad2bbe82afa841ea4a29880651e53ab86815db036b2cb9f8de3ac32dad75" 510 + dependencies = [ 511 + "anyhow", 512 + "bindgen", 513 + "bitflags 1.3.2", 514 + "cargo_toml", 515 + "cmake", 516 + "filetime", 517 + "globwalk", 518 + "home", 519 + "log", 520 + "regex", 521 + "remove_dir_all", 522 + "serde", 523 + "serde_json", 524 + "shlex", 525 + "strum 0.24.1", 526 + "tempfile", 527 + "thiserror 1.0.69", 528 + "toml", 529 + "ureq", 530 + "which", 531 + ] 532 + 533 + [[package]] 534 + name = "enumset" 535 + version = "1.1.10" 536 + source = "registry+https://github.com/rust-lang/crates.io-index" 537 + checksum = "25b07a8dfbbbfc0064c0a6bdf9edcf966de6b1c33ce344bdeca3b41615452634" 538 + dependencies = [ 539 + "enumset_derive", 540 + ] 541 + 542 + [[package]] 543 + name = "enumset_derive" 544 + version = "0.14.0" 545 + source = "registry+https://github.com/rust-lang/crates.io-index" 546 + checksum = "f43e744e4ea338060faee68ed933e46e722fb7f3617e722a5772d7e856d8b3ce" 547 + dependencies = [ 548 + "darling", 549 + "proc-macro2", 550 + "quote", 551 + "syn 2.0.117", 552 + ] 553 + 554 + [[package]] 555 + name = "envy" 556 + version = "0.4.2" 557 + source = "registry+https://github.com/rust-lang/crates.io-index" 558 + checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" 559 + dependencies = [ 560 + "serde", 561 + ] 562 + 563 + [[package]] 564 + name = "equivalent" 565 + version = "1.0.2" 566 + source = "registry+https://github.com/rust-lang/crates.io-index" 567 + checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 568 + 569 + [[package]] 570 + name = "errno" 571 + version = "0.3.14" 572 + source = "registry+https://github.com/rust-lang/crates.io-index" 573 + checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" 574 + dependencies = [ 575 + "libc", 576 + "windows-sys 0.61.2", 577 + ] 578 + 579 + [[package]] 580 + name = "esp-idf-hal" 581 + version = "0.45.2" 582 + source = "registry+https://github.com/rust-lang/crates.io-index" 583 + checksum = "775ce25171dc4f615146a4a27ed3a64c6fd99ced77d7112062f2b19bf933f5db" 584 + dependencies = [ 585 + "atomic-waker", 586 + "embassy-sync", 587 + "embedded-can", 588 + "embedded-hal 0.2.7", 589 + "embedded-hal 1.0.0", 590 + "embedded-hal-async", 591 + "embedded-hal-nb", 592 + "embedded-io", 593 + "embedded-io-async", 594 + "embuild", 595 + "enumset", 596 + "esp-idf-sys", 597 + "heapless", 598 + "log", 599 + "nb 1.1.0", 600 + "num_enum", 601 + ] 602 + 603 + [[package]] 604 + name = "esp-idf-svc" 605 + version = "0.51.0" 606 + source = "registry+https://github.com/rust-lang/crates.io-index" 607 + checksum = "2bc07aaba257d28d54a96af005ca67d0b38876d8837f5d54a3e0547e100b219c" 608 + dependencies = [ 609 + "embassy-futures", 610 + "embedded-hal-async", 611 + "embedded-svc", 612 + "embuild", 613 + "enumset", 614 + "esp-idf-hal", 615 + "futures-io", 616 + "heapless", 617 + "log", 618 + "num_enum", 619 + "uncased", 620 + ] 621 + 622 + [[package]] 623 + name = "esp-idf-sys" 624 + version = "0.36.1" 625 + source = "registry+https://github.com/rust-lang/crates.io-index" 626 + checksum = "fb77a3d02b579a60a811ed9be22b78c5e794bc492d833ee7fc44d3a0155885e1" 627 + dependencies = [ 628 + "anyhow", 629 + "build-time", 630 + "cargo_metadata", 631 + "cmake", 632 + "const_format", 633 + "embuild", 634 + "envy", 635 + "libc", 636 + "regex", 637 + "serde", 638 + "strum 0.24.1", 639 + "which", 640 + ] 641 + 642 + [[package]] 643 + name = "fastrand" 644 + version = "2.4.1" 645 + source = "registry+https://github.com/rust-lang/crates.io-index" 646 + checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" 647 + 648 + [[package]] 649 + name = "filetime" 650 + version = "0.2.27" 651 + source = "registry+https://github.com/rust-lang/crates.io-index" 652 + checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" 653 + dependencies = [ 654 + "cfg-if", 655 + "libc", 656 + "libredox", 657 + ] 658 + 659 + [[package]] 660 + name = "find-msvc-tools" 661 + version = "0.1.9" 662 + source = "registry+https://github.com/rust-lang/crates.io-index" 663 + checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" 664 + 665 + [[package]] 666 + name = "flate2" 667 + version = "1.1.9" 668 + source = "registry+https://github.com/rust-lang/crates.io-index" 669 + checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" 670 + dependencies = [ 671 + "crc32fast", 672 + "miniz_oxide", 673 + ] 674 + 675 + [[package]] 676 + name = "fnv" 677 + version = "1.0.7" 678 + source = "registry+https://github.com/rust-lang/crates.io-index" 679 + checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 680 + 681 + [[package]] 682 + name = "foldhash" 683 + version = "0.1.5" 684 + source = "registry+https://github.com/rust-lang/crates.io-index" 685 + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" 686 + 687 + [[package]] 688 + name = "form_urlencoded" 689 + version = "1.2.2" 690 + source = "registry+https://github.com/rust-lang/crates.io-index" 691 + checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 692 + dependencies = [ 693 + "percent-encoding", 694 + ] 695 + 696 + [[package]] 697 + name = "fs_at" 698 + version = "0.2.1" 699 + source = "registry+https://github.com/rust-lang/crates.io-index" 700 + checksum = "14af6c9694ea25db25baa2a1788703b9e7c6648dcaeeebeb98f7561b5384c036" 701 + dependencies = [ 702 + "aligned", 703 + "cfg-if", 704 + "cvt", 705 + "libc", 706 + "nix", 707 + "windows-sys 0.52.0", 708 + ] 709 + 710 + [[package]] 711 + name = "futures-core" 712 + version = "0.3.32" 713 + source = "registry+https://github.com/rust-lang/crates.io-index" 714 + checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" 715 + 716 + [[package]] 717 + name = "futures-io" 718 + version = "0.3.32" 719 + source = "registry+https://github.com/rust-lang/crates.io-index" 720 + checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" 721 + 722 + [[package]] 723 + name = "futures-sink" 724 + version = "0.3.32" 725 + source = "registry+https://github.com/rust-lang/crates.io-index" 726 + checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" 727 + 728 + [[package]] 729 + name = "futures-task" 730 + version = "0.3.32" 731 + source = "registry+https://github.com/rust-lang/crates.io-index" 732 + checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" 733 + 734 + [[package]] 735 + name = "futures-util" 736 + version = "0.3.32" 737 + source = "registry+https://github.com/rust-lang/crates.io-index" 738 + checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" 739 + dependencies = [ 740 + "futures-core", 741 + "futures-task", 742 + "pin-project-lite", 743 + ] 744 + 745 + [[package]] 746 + name = "getrandom" 747 + version = "0.2.17" 748 + source = "registry+https://github.com/rust-lang/crates.io-index" 749 + checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" 750 + dependencies = [ 751 + "cfg-if", 752 + "libc", 753 + "wasi", 754 + ] 755 + 756 + [[package]] 757 + name = "getrandom" 758 + version = "0.4.2" 759 + source = "registry+https://github.com/rust-lang/crates.io-index" 760 + checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" 761 + dependencies = [ 762 + "cfg-if", 763 + "libc", 764 + "r-efi", 765 + "wasip2", 766 + "wasip3", 767 + ] 768 + 769 + [[package]] 770 + name = "glob" 771 + version = "0.3.3" 772 + source = "registry+https://github.com/rust-lang/crates.io-index" 773 + checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 774 + 775 + [[package]] 776 + name = "globset" 777 + version = "0.4.18" 778 + source = "registry+https://github.com/rust-lang/crates.io-index" 779 + checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" 780 + dependencies = [ 781 + "aho-corasick", 782 + "bstr", 783 + "log", 784 + "regex-automata", 785 + "regex-syntax", 786 + ] 787 + 788 + [[package]] 789 + name = "globwalk" 790 + version = "0.8.1" 791 + source = "registry+https://github.com/rust-lang/crates.io-index" 792 + checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" 793 + dependencies = [ 794 + "bitflags 1.3.2", 795 + "ignore", 796 + "walkdir", 797 + ] 798 + 799 + [[package]] 800 + name = "hash32" 801 + version = "0.3.1" 802 + source = "registry+https://github.com/rust-lang/crates.io-index" 803 + checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" 804 + dependencies = [ 805 + "byteorder", 806 + ] 807 + 808 + [[package]] 809 + name = "hashbrown" 810 + version = "0.15.5" 811 + source = "registry+https://github.com/rust-lang/crates.io-index" 812 + checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" 813 + dependencies = [ 814 + "foldhash", 815 + ] 816 + 817 + [[package]] 818 + name = "hashbrown" 819 + version = "0.17.0" 820 + source = "registry+https://github.com/rust-lang/crates.io-index" 821 + checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" 822 + 823 + [[package]] 824 + name = "heapless" 825 + version = "0.8.0" 826 + source = "registry+https://github.com/rust-lang/crates.io-index" 827 + checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" 828 + dependencies = [ 829 + "hash32", 830 + "stable_deref_trait", 831 + ] 832 + 833 + [[package]] 834 + name = "heck" 835 + version = "0.4.1" 836 + source = "registry+https://github.com/rust-lang/crates.io-index" 837 + checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 838 + 839 + [[package]] 840 + name = "heck" 841 + version = "0.5.0" 842 + source = "registry+https://github.com/rust-lang/crates.io-index" 843 + checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 844 + 845 + [[package]] 846 + name = "home" 847 + version = "0.5.12" 848 + source = "registry+https://github.com/rust-lang/crates.io-index" 849 + checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" 850 + dependencies = [ 851 + "windows-sys 0.61.2", 852 + ] 853 + 854 + [[package]] 855 + name = "iana-time-zone" 856 + version = "0.1.65" 857 + source = "registry+https://github.com/rust-lang/crates.io-index" 858 + checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" 859 + dependencies = [ 860 + "android_system_properties", 861 + "core-foundation-sys", 862 + "iana-time-zone-haiku", 863 + "js-sys", 864 + "log", 865 + "wasm-bindgen", 866 + "windows-core", 867 + ] 868 + 869 + [[package]] 870 + name = "iana-time-zone-haiku" 871 + version = "0.1.2" 872 + source = "registry+https://github.com/rust-lang/crates.io-index" 873 + checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 874 + dependencies = [ 875 + "cc", 876 + ] 877 + 878 + [[package]] 879 + name = "icu_collections" 880 + version = "2.2.0" 881 + source = "registry+https://github.com/rust-lang/crates.io-index" 882 + checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" 883 + dependencies = [ 884 + "displaydoc", 885 + "potential_utf", 886 + "utf8_iter", 887 + "yoke", 888 + "zerofrom", 889 + "zerovec", 890 + ] 891 + 892 + [[package]] 893 + name = "icu_locale_core" 894 + version = "2.2.0" 895 + source = "registry+https://github.com/rust-lang/crates.io-index" 896 + checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" 897 + dependencies = [ 898 + "displaydoc", 899 + "litemap", 900 + "tinystr", 901 + "writeable", 902 + "zerovec", 903 + ] 904 + 905 + [[package]] 906 + name = "icu_normalizer" 907 + version = "2.2.0" 908 + source = "registry+https://github.com/rust-lang/crates.io-index" 909 + checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" 910 + dependencies = [ 911 + "icu_collections", 912 + "icu_normalizer_data", 913 + "icu_properties", 914 + "icu_provider", 915 + "smallvec", 916 + "zerovec", 917 + ] 918 + 919 + [[package]] 920 + name = "icu_normalizer_data" 921 + version = "2.2.0" 922 + source = "registry+https://github.com/rust-lang/crates.io-index" 923 + checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" 924 + 925 + [[package]] 926 + name = "icu_properties" 927 + version = "2.2.0" 928 + source = "registry+https://github.com/rust-lang/crates.io-index" 929 + checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" 930 + dependencies = [ 931 + "icu_collections", 932 + "icu_locale_core", 933 + "icu_properties_data", 934 + "icu_provider", 935 + "zerotrie", 936 + "zerovec", 937 + ] 938 + 939 + [[package]] 940 + name = "icu_properties_data" 941 + version = "2.2.0" 942 + source = "registry+https://github.com/rust-lang/crates.io-index" 943 + checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" 944 + 945 + [[package]] 946 + name = "icu_provider" 947 + version = "2.2.0" 948 + source = "registry+https://github.com/rust-lang/crates.io-index" 949 + checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" 950 + dependencies = [ 951 + "displaydoc", 952 + "icu_locale_core", 953 + "writeable", 954 + "yoke", 955 + "zerofrom", 956 + "zerotrie", 957 + "zerovec", 958 + ] 959 + 960 + [[package]] 961 + name = "id-arena" 962 + version = "2.3.0" 963 + source = "registry+https://github.com/rust-lang/crates.io-index" 964 + checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" 965 + 966 + [[package]] 967 + name = "ident_case" 968 + version = "1.0.1" 969 + source = "registry+https://github.com/rust-lang/crates.io-index" 970 + checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 971 + 972 + [[package]] 973 + name = "idna" 974 + version = "1.1.0" 975 + source = "registry+https://github.com/rust-lang/crates.io-index" 976 + checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" 977 + dependencies = [ 978 + "idna_adapter", 979 + "smallvec", 980 + "utf8_iter", 981 + ] 982 + 983 + [[package]] 984 + name = "idna_adapter" 985 + version = "1.2.1" 986 + source = "registry+https://github.com/rust-lang/crates.io-index" 987 + checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" 988 + dependencies = [ 989 + "icu_normalizer", 990 + "icu_properties", 991 + ] 992 + 993 + [[package]] 994 + name = "ignore" 995 + version = "0.4.25" 996 + source = "registry+https://github.com/rust-lang/crates.io-index" 997 + checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" 998 + dependencies = [ 999 + "crossbeam-deque", 1000 + "globset", 1001 + "log", 1002 + "memchr", 1003 + "regex-automata", 1004 + "same-file", 1005 + "walkdir", 1006 + "winapi-util", 1007 + ] 1008 + 1009 + [[package]] 1010 + name = "indexmap" 1011 + version = "2.14.0" 1012 + source = "registry+https://github.com/rust-lang/crates.io-index" 1013 + checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" 1014 + dependencies = [ 1015 + "equivalent", 1016 + "hashbrown 0.17.0", 1017 + "serde", 1018 + "serde_core", 1019 + ] 1020 + 1021 + [[package]] 1022 + name = "itertools" 1023 + version = "0.13.0" 1024 + source = "registry+https://github.com/rust-lang/crates.io-index" 1025 + checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 1026 + dependencies = [ 1027 + "either", 1028 + ] 1029 + 1030 + [[package]] 1031 + name = "itoa" 1032 + version = "1.0.18" 1033 + source = "registry+https://github.com/rust-lang/crates.io-index" 1034 + checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" 1035 + 1036 + [[package]] 1037 + name = "js-sys" 1038 + version = "0.3.95" 1039 + source = "registry+https://github.com/rust-lang/crates.io-index" 1040 + checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" 1041 + dependencies = [ 1042 + "once_cell", 1043 + "wasm-bindgen", 1044 + ] 1045 + 1046 + [[package]] 1047 + name = "konst" 1048 + version = "0.2.20" 1049 + source = "registry+https://github.com/rust-lang/crates.io-index" 1050 + checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" 1051 + dependencies = [ 1052 + "konst_macro_rules", 1053 + ] 1054 + 1055 + [[package]] 1056 + name = "konst_macro_rules" 1057 + version = "0.2.19" 1058 + source = "registry+https://github.com/rust-lang/crates.io-index" 1059 + checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" 1060 + 1061 + [[package]] 1062 + name = "leb128fmt" 1063 + version = "0.1.0" 1064 + source = "registry+https://github.com/rust-lang/crates.io-index" 1065 + checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" 1066 + 1067 + [[package]] 1068 + name = "libc" 1069 + version = "0.2.186" 1070 + source = "registry+https://github.com/rust-lang/crates.io-index" 1071 + checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" 1072 + 1073 + [[package]] 1074 + name = "libloading" 1075 + version = "0.8.9" 1076 + source = "registry+https://github.com/rust-lang/crates.io-index" 1077 + checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" 1078 + dependencies = [ 1079 + "cfg-if", 1080 + "windows-link", 1081 + ] 1082 + 1083 + [[package]] 1084 + name = "libredox" 1085 + version = "0.1.16" 1086 + source = "registry+https://github.com/rust-lang/crates.io-index" 1087 + checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" 1088 + dependencies = [ 1089 + "bitflags 2.11.1", 1090 + "libc", 1091 + "plain", 1092 + "redox_syscall", 1093 + ] 1094 + 1095 + [[package]] 1096 + name = "linux-raw-sys" 1097 + version = "0.4.15" 1098 + source = "registry+https://github.com/rust-lang/crates.io-index" 1099 + checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 1100 + 1101 + [[package]] 1102 + name = "linux-raw-sys" 1103 + version = "0.12.1" 1104 + source = "registry+https://github.com/rust-lang/crates.io-index" 1105 + checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" 1106 + 1107 + [[package]] 1108 + name = "litemap" 1109 + version = "0.8.2" 1110 + source = "registry+https://github.com/rust-lang/crates.io-index" 1111 + checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" 1112 + 1113 + [[package]] 1114 + name = "log" 1115 + version = "0.4.29" 1116 + source = "registry+https://github.com/rust-lang/crates.io-index" 1117 + checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" 1118 + 1119 + [[package]] 1120 + name = "memchr" 1121 + version = "2.8.0" 1122 + source = "registry+https://github.com/rust-lang/crates.io-index" 1123 + checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" 1124 + 1125 + [[package]] 1126 + name = "minimal-lexical" 1127 + version = "0.2.1" 1128 + source = "registry+https://github.com/rust-lang/crates.io-index" 1129 + checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1130 + 1131 + [[package]] 1132 + name = "miniz_oxide" 1133 + version = "0.8.9" 1134 + source = "registry+https://github.com/rust-lang/crates.io-index" 1135 + checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" 1136 + dependencies = [ 1137 + "adler2", 1138 + "simd-adler32", 1139 + ] 1140 + 1141 + [[package]] 1142 + name = "nb" 1143 + version = "0.1.3" 1144 + source = "registry+https://github.com/rust-lang/crates.io-index" 1145 + checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" 1146 + dependencies = [ 1147 + "nb 1.1.0", 1148 + ] 1149 + 1150 + [[package]] 1151 + name = "nb" 1152 + version = "1.1.0" 1153 + source = "registry+https://github.com/rust-lang/crates.io-index" 1154 + checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" 1155 + 1156 + [[package]] 1157 + name = "nix" 1158 + version = "0.29.0" 1159 + source = "registry+https://github.com/rust-lang/crates.io-index" 1160 + checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 1161 + dependencies = [ 1162 + "bitflags 2.11.1", 1163 + "cfg-if", 1164 + "cfg_aliases", 1165 + "libc", 1166 + ] 1167 + 1168 + [[package]] 1169 + name = "nom" 1170 + version = "7.1.3" 1171 + source = "registry+https://github.com/rust-lang/crates.io-index" 1172 + checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1173 + dependencies = [ 1174 + "memchr", 1175 + "minimal-lexical", 1176 + ] 1177 + 1178 + [[package]] 1179 + name = "normpath" 1180 + version = "1.5.0" 1181 + source = "registry+https://github.com/rust-lang/crates.io-index" 1182 + checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b" 1183 + dependencies = [ 1184 + "windows-sys 0.61.2", 1185 + ] 1186 + 1187 + [[package]] 1188 + name = "num-traits" 1189 + version = "0.2.19" 1190 + source = "registry+https://github.com/rust-lang/crates.io-index" 1191 + checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1192 + dependencies = [ 1193 + "autocfg", 1194 + ] 1195 + 1196 + [[package]] 1197 + name = "num_enum" 1198 + version = "0.7.6" 1199 + source = "registry+https://github.com/rust-lang/crates.io-index" 1200 + checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" 1201 + dependencies = [ 1202 + "num_enum_derive", 1203 + "rustversion", 1204 + ] 1205 + 1206 + [[package]] 1207 + name = "num_enum_derive" 1208 + version = "0.7.6" 1209 + source = "registry+https://github.com/rust-lang/crates.io-index" 1210 + checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" 1211 + dependencies = [ 1212 + "proc-macro-crate", 1213 + "proc-macro2", 1214 + "quote", 1215 + "syn 2.0.117", 1216 + ] 1217 + 1218 + [[package]] 1219 + name = "once_cell" 1220 + version = "1.21.4" 1221 + source = "registry+https://github.com/rust-lang/crates.io-index" 1222 + checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" 1223 + 1224 + [[package]] 1225 + name = "percent-encoding" 1226 + version = "2.3.2" 1227 + source = "registry+https://github.com/rust-lang/crates.io-index" 1228 + checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" 1229 + 1230 + [[package]] 1231 + name = "pin-project-lite" 1232 + version = "0.2.17" 1233 + source = "registry+https://github.com/rust-lang/crates.io-index" 1234 + checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" 1235 + 1236 + [[package]] 1237 + name = "plain" 1238 + version = "0.2.3" 1239 + source = "registry+https://github.com/rust-lang/crates.io-index" 1240 + checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" 1241 + 1242 + [[package]] 1243 + name = "potential_utf" 1244 + version = "0.1.5" 1245 + source = "registry+https://github.com/rust-lang/crates.io-index" 1246 + checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" 1247 + dependencies = [ 1248 + "zerovec", 1249 + ] 1250 + 1251 + [[package]] 1252 + name = "prettyplease" 1253 + version = "0.2.37" 1254 + source = "registry+https://github.com/rust-lang/crates.io-index" 1255 + checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" 1256 + dependencies = [ 1257 + "proc-macro2", 1258 + "syn 2.0.117", 1259 + ] 1260 + 1261 + [[package]] 1262 + name = "proc-macro-crate" 1263 + version = "3.5.0" 1264 + source = "registry+https://github.com/rust-lang/crates.io-index" 1265 + checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" 1266 + dependencies = [ 1267 + "toml_edit 0.25.11+spec-1.1.0", 1268 + ] 1269 + 1270 + [[package]] 1271 + name = "proc-macro-error-attr2" 1272 + version = "2.0.0" 1273 + source = "registry+https://github.com/rust-lang/crates.io-index" 1274 + checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 1275 + dependencies = [ 1276 + "proc-macro2", 1277 + "quote", 1278 + ] 1279 + 1280 + [[package]] 1281 + name = "proc-macro-error2" 1282 + version = "2.0.1" 1283 + source = "registry+https://github.com/rust-lang/crates.io-index" 1284 + checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 1285 + dependencies = [ 1286 + "proc-macro-error-attr2", 1287 + "proc-macro2", 1288 + "quote", 1289 + "syn 2.0.117", 1290 + ] 1291 + 1292 + [[package]] 1293 + name = "proc-macro2" 1294 + version = "1.0.106" 1295 + source = "registry+https://github.com/rust-lang/crates.io-index" 1296 + checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" 1297 + dependencies = [ 1298 + "unicode-ident", 1299 + ] 1300 + 1301 + [[package]] 1302 + name = "quote" 1303 + version = "1.0.45" 1304 + source = "registry+https://github.com/rust-lang/crates.io-index" 1305 + checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" 1306 + dependencies = [ 1307 + "proc-macro2", 1308 + ] 1309 + 1310 + [[package]] 1311 + name = "r-efi" 1312 + version = "6.0.0" 1313 + source = "registry+https://github.com/rust-lang/crates.io-index" 1314 + checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" 1315 + 1316 + [[package]] 1317 + name = "redox_syscall" 1318 + version = "0.7.4" 1319 + source = "registry+https://github.com/rust-lang/crates.io-index" 1320 + checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" 1321 + dependencies = [ 1322 + "bitflags 2.11.1", 1323 + ] 1324 + 1325 + [[package]] 1326 + name = "regex" 1327 + version = "1.12.3" 1328 + source = "registry+https://github.com/rust-lang/crates.io-index" 1329 + checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" 1330 + dependencies = [ 1331 + "aho-corasick", 1332 + "memchr", 1333 + "regex-automata", 1334 + "regex-syntax", 1335 + ] 1336 + 1337 + [[package]] 1338 + name = "regex-automata" 1339 + version = "0.4.14" 1340 + source = "registry+https://github.com/rust-lang/crates.io-index" 1341 + checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" 1342 + dependencies = [ 1343 + "aho-corasick", 1344 + "memchr", 1345 + "regex-syntax", 1346 + ] 1347 + 1348 + [[package]] 1349 + name = "regex-syntax" 1350 + version = "0.8.10" 1351 + source = "registry+https://github.com/rust-lang/crates.io-index" 1352 + checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" 1353 + 1354 + [[package]] 1355 + name = "remove_dir_all" 1356 + version = "0.8.4" 1357 + source = "registry+https://github.com/rust-lang/crates.io-index" 1358 + checksum = "a694f9e0eb3104451127f6cc1e5de55f59d3b1fc8c5ddfaeb6f1e716479ceb4a" 1359 + dependencies = [ 1360 + "cfg-if", 1361 + "cvt", 1362 + "fs_at", 1363 + "libc", 1364 + "normpath", 1365 + "windows-sys 0.59.0", 1366 + ] 1367 + 1368 + [[package]] 1369 + name = "ring" 1370 + version = "0.17.14" 1371 + source = "registry+https://github.com/rust-lang/crates.io-index" 1372 + checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" 1373 + dependencies = [ 1374 + "cc", 1375 + "cfg-if", 1376 + "getrandom 0.2.17", 1377 + "libc", 1378 + "untrusted", 1379 + "windows-sys 0.52.0", 1380 + ] 1381 + 1382 + [[package]] 1383 + name = "rustc-hash" 1384 + version = "2.1.2" 1385 + source = "registry+https://github.com/rust-lang/crates.io-index" 1386 + checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" 1387 + 1388 + [[package]] 1389 + name = "rustix" 1390 + version = "0.38.44" 1391 + source = "registry+https://github.com/rust-lang/crates.io-index" 1392 + checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 1393 + dependencies = [ 1394 + "bitflags 2.11.1", 1395 + "errno", 1396 + "libc", 1397 + "linux-raw-sys 0.4.15", 1398 + "windows-sys 0.59.0", 1399 + ] 1400 + 1401 + [[package]] 1402 + name = "rustix" 1403 + version = "1.1.4" 1404 + source = "registry+https://github.com/rust-lang/crates.io-index" 1405 + checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" 1406 + dependencies = [ 1407 + "bitflags 2.11.1", 1408 + "errno", 1409 + "libc", 1410 + "linux-raw-sys 0.12.1", 1411 + "windows-sys 0.61.2", 1412 + ] 1413 + 1414 + [[package]] 1415 + name = "rustls" 1416 + version = "0.23.39" 1417 + source = "registry+https://github.com/rust-lang/crates.io-index" 1418 + checksum = "7c2c118cb077cca2822033836dfb1b975355dfb784b5e8da48f7b6c5db74e60e" 1419 + dependencies = [ 1420 + "log", 1421 + "once_cell", 1422 + "ring", 1423 + "rustls-pki-types", 1424 + "rustls-webpki", 1425 + "subtle", 1426 + "zeroize", 1427 + ] 1428 + 1429 + [[package]] 1430 + name = "rustls-pki-types" 1431 + version = "1.14.1" 1432 + source = "registry+https://github.com/rust-lang/crates.io-index" 1433 + checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" 1434 + dependencies = [ 1435 + "zeroize", 1436 + ] 1437 + 1438 + [[package]] 1439 + name = "rustls-webpki" 1440 + version = "0.103.13" 1441 + source = "registry+https://github.com/rust-lang/crates.io-index" 1442 + checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" 1443 + dependencies = [ 1444 + "ring", 1445 + "rustls-pki-types", 1446 + "untrusted", 1447 + ] 1448 + 1449 + [[package]] 1450 + name = "rustversion" 1451 + version = "1.0.22" 1452 + source = "registry+https://github.com/rust-lang/crates.io-index" 1453 + checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" 1454 + 1455 + [[package]] 1456 + name = "same-file" 1457 + version = "1.0.6" 1458 + source = "registry+https://github.com/rust-lang/crates.io-index" 1459 + checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1460 + dependencies = [ 1461 + "winapi-util", 1462 + ] 1463 + 1464 + [[package]] 1465 + name = "semver" 1466 + version = "1.0.28" 1467 + source = "registry+https://github.com/rust-lang/crates.io-index" 1468 + checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" 1469 + dependencies = [ 1470 + "serde", 1471 + "serde_core", 1472 + ] 1473 + 1474 + [[package]] 1475 + name = "serde" 1476 + version = "1.0.228" 1477 + source = "registry+https://github.com/rust-lang/crates.io-index" 1478 + checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" 1479 + dependencies = [ 1480 + "serde_core", 1481 + "serde_derive", 1482 + ] 1483 + 1484 + [[package]] 1485 + name = "serde_core" 1486 + version = "1.0.228" 1487 + source = "registry+https://github.com/rust-lang/crates.io-index" 1488 + checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" 1489 + dependencies = [ 1490 + "serde_derive", 1491 + ] 1492 + 1493 + [[package]] 1494 + name = "serde_derive" 1495 + version = "1.0.228" 1496 + source = "registry+https://github.com/rust-lang/crates.io-index" 1497 + checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" 1498 + dependencies = [ 1499 + "proc-macro2", 1500 + "quote", 1501 + "syn 2.0.117", 1502 + ] 1503 + 1504 + [[package]] 1505 + name = "serde_json" 1506 + version = "1.0.149" 1507 + source = "registry+https://github.com/rust-lang/crates.io-index" 1508 + checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" 1509 + dependencies = [ 1510 + "itoa", 1511 + "memchr", 1512 + "serde", 1513 + "serde_core", 1514 + "zmij", 1515 + ] 1516 + 1517 + [[package]] 1518 + name = "serde_spanned" 1519 + version = "0.6.9" 1520 + source = "registry+https://github.com/rust-lang/crates.io-index" 1521 + checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" 1522 + dependencies = [ 1523 + "serde", 1524 + ] 1525 + 1526 + [[package]] 1527 + name = "shlex" 1528 + version = "1.3.0" 1529 + source = "registry+https://github.com/rust-lang/crates.io-index" 1530 + checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1531 + 1532 + [[package]] 1533 + name = "simd-adler32" 1534 + version = "0.3.9" 1535 + source = "registry+https://github.com/rust-lang/crates.io-index" 1536 + checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" 1537 + 1538 + [[package]] 1539 + name = "smallvec" 1540 + version = "1.15.1" 1541 + source = "registry+https://github.com/rust-lang/crates.io-index" 1542 + checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" 1543 + 1544 + [[package]] 1545 + name = "sound-machine" 1546 + version = "0.0.1" 1547 + dependencies = [ 1548 + "anyhow", 1549 + "embuild", 1550 + "esp-idf-svc", 1551 + "log", 1552 + ] 1553 + 1554 + [[package]] 1555 + name = "stable_deref_trait" 1556 + version = "1.2.1" 1557 + source = "registry+https://github.com/rust-lang/crates.io-index" 1558 + checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" 1559 + 1560 + [[package]] 1561 + name = "strum" 1562 + version = "0.24.1" 1563 + source = "registry+https://github.com/rust-lang/crates.io-index" 1564 + checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" 1565 + dependencies = [ 1566 + "strum_macros 0.24.3", 1567 + ] 1568 + 1569 + [[package]] 1570 + name = "strum" 1571 + version = "0.25.0" 1572 + source = "registry+https://github.com/rust-lang/crates.io-index" 1573 + checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" 1574 + dependencies = [ 1575 + "strum_macros 0.25.3", 1576 + ] 1577 + 1578 + [[package]] 1579 + name = "strum_macros" 1580 + version = "0.24.3" 1581 + source = "registry+https://github.com/rust-lang/crates.io-index" 1582 + checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" 1583 + dependencies = [ 1584 + "heck 0.4.1", 1585 + "proc-macro2", 1586 + "quote", 1587 + "rustversion", 1588 + "syn 1.0.109", 1589 + ] 1590 + 1591 + [[package]] 1592 + name = "strum_macros" 1593 + version = "0.25.3" 1594 + source = "registry+https://github.com/rust-lang/crates.io-index" 1595 + checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" 1596 + dependencies = [ 1597 + "heck 0.4.1", 1598 + "proc-macro2", 1599 + "quote", 1600 + "rustversion", 1601 + "syn 2.0.117", 1602 + ] 1603 + 1604 + [[package]] 1605 + name = "subtle" 1606 + version = "2.6.1" 1607 + source = "registry+https://github.com/rust-lang/crates.io-index" 1608 + checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1609 + 1610 + [[package]] 1611 + name = "syn" 1612 + version = "1.0.109" 1613 + source = "registry+https://github.com/rust-lang/crates.io-index" 1614 + checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1615 + dependencies = [ 1616 + "proc-macro2", 1617 + "quote", 1618 + "unicode-ident", 1619 + ] 1620 + 1621 + [[package]] 1622 + name = "syn" 1623 + version = "2.0.117" 1624 + source = "registry+https://github.com/rust-lang/crates.io-index" 1625 + checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" 1626 + dependencies = [ 1627 + "proc-macro2", 1628 + "quote", 1629 + "unicode-ident", 1630 + ] 1631 + 1632 + [[package]] 1633 + name = "synstructure" 1634 + version = "0.13.2" 1635 + source = "registry+https://github.com/rust-lang/crates.io-index" 1636 + checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" 1637 + dependencies = [ 1638 + "proc-macro2", 1639 + "quote", 1640 + "syn 2.0.117", 1641 + ] 1642 + 1643 + [[package]] 1644 + name = "tempfile" 1645 + version = "3.27.0" 1646 + source = "registry+https://github.com/rust-lang/crates.io-index" 1647 + checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" 1648 + dependencies = [ 1649 + "fastrand", 1650 + "getrandom 0.4.2", 1651 + "once_cell", 1652 + "rustix 1.1.4", 1653 + "windows-sys 0.61.2", 1654 + ] 1655 + 1656 + [[package]] 1657 + name = "thiserror" 1658 + version = "1.0.69" 1659 + source = "registry+https://github.com/rust-lang/crates.io-index" 1660 + checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1661 + dependencies = [ 1662 + "thiserror-impl 1.0.69", 1663 + ] 1664 + 1665 + [[package]] 1666 + name = "thiserror" 1667 + version = "2.0.18" 1668 + source = "registry+https://github.com/rust-lang/crates.io-index" 1669 + checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" 1670 + dependencies = [ 1671 + "thiserror-impl 2.0.18", 1672 + ] 1673 + 1674 + [[package]] 1675 + name = "thiserror-impl" 1676 + version = "1.0.69" 1677 + source = "registry+https://github.com/rust-lang/crates.io-index" 1678 + checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1679 + dependencies = [ 1680 + "proc-macro2", 1681 + "quote", 1682 + "syn 2.0.117", 1683 + ] 1684 + 1685 + [[package]] 1686 + name = "thiserror-impl" 1687 + version = "2.0.18" 1688 + source = "registry+https://github.com/rust-lang/crates.io-index" 1689 + checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" 1690 + dependencies = [ 1691 + "proc-macro2", 1692 + "quote", 1693 + "syn 2.0.117", 1694 + ] 1695 + 1696 + [[package]] 1697 + name = "tinystr" 1698 + version = "0.8.3" 1699 + source = "registry+https://github.com/rust-lang/crates.io-index" 1700 + checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" 1701 + dependencies = [ 1702 + "displaydoc", 1703 + "zerovec", 1704 + ] 1705 + 1706 + [[package]] 1707 + name = "toml" 1708 + version = "0.7.8" 1709 + source = "registry+https://github.com/rust-lang/crates.io-index" 1710 + checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" 1711 + dependencies = [ 1712 + "serde", 1713 + "serde_spanned", 1714 + "toml_datetime 0.6.11", 1715 + "toml_edit 0.19.15", 1716 + ] 1717 + 1718 + [[package]] 1719 + name = "toml_datetime" 1720 + version = "0.6.11" 1721 + source = "registry+https://github.com/rust-lang/crates.io-index" 1722 + checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" 1723 + dependencies = [ 1724 + "serde", 1725 + ] 1726 + 1727 + [[package]] 1728 + name = "toml_datetime" 1729 + version = "1.1.1+spec-1.1.0" 1730 + source = "registry+https://github.com/rust-lang/crates.io-index" 1731 + checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" 1732 + dependencies = [ 1733 + "serde_core", 1734 + ] 1735 + 1736 + [[package]] 1737 + name = "toml_edit" 1738 + version = "0.19.15" 1739 + source = "registry+https://github.com/rust-lang/crates.io-index" 1740 + checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 1741 + dependencies = [ 1742 + "indexmap", 1743 + "serde", 1744 + "serde_spanned", 1745 + "toml_datetime 0.6.11", 1746 + "winnow 0.5.40", 1747 + ] 1748 + 1749 + [[package]] 1750 + name = "toml_edit" 1751 + version = "0.25.11+spec-1.1.0" 1752 + source = "registry+https://github.com/rust-lang/crates.io-index" 1753 + checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" 1754 + dependencies = [ 1755 + "indexmap", 1756 + "toml_datetime 1.1.1+spec-1.1.0", 1757 + "toml_parser", 1758 + "winnow 1.0.2", 1759 + ] 1760 + 1761 + [[package]] 1762 + name = "toml_parser" 1763 + version = "1.1.2+spec-1.1.0" 1764 + source = "registry+https://github.com/rust-lang/crates.io-index" 1765 + checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" 1766 + dependencies = [ 1767 + "winnow 1.0.2", 1768 + ] 1769 + 1770 + [[package]] 1771 + name = "uncased" 1772 + version = "0.9.10" 1773 + source = "registry+https://github.com/rust-lang/crates.io-index" 1774 + checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" 1775 + dependencies = [ 1776 + "version_check", 1777 + ] 1778 + 1779 + [[package]] 1780 + name = "unicode-ident" 1781 + version = "1.0.24" 1782 + source = "registry+https://github.com/rust-lang/crates.io-index" 1783 + checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" 1784 + 1785 + [[package]] 1786 + name = "unicode-xid" 1787 + version = "0.2.6" 1788 + source = "registry+https://github.com/rust-lang/crates.io-index" 1789 + checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 1790 + 1791 + [[package]] 1792 + name = "untrusted" 1793 + version = "0.9.0" 1794 + source = "registry+https://github.com/rust-lang/crates.io-index" 1795 + checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1796 + 1797 + [[package]] 1798 + name = "ureq" 1799 + version = "2.12.1" 1800 + source = "registry+https://github.com/rust-lang/crates.io-index" 1801 + checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" 1802 + dependencies = [ 1803 + "base64", 1804 + "flate2", 1805 + "log", 1806 + "once_cell", 1807 + "rustls", 1808 + "rustls-pki-types", 1809 + "url", 1810 + "webpki-roots 0.26.11", 1811 + ] 1812 + 1813 + [[package]] 1814 + name = "url" 1815 + version = "2.5.8" 1816 + source = "registry+https://github.com/rust-lang/crates.io-index" 1817 + checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" 1818 + dependencies = [ 1819 + "form_urlencoded", 1820 + "idna", 1821 + "percent-encoding", 1822 + "serde", 1823 + ] 1824 + 1825 + [[package]] 1826 + name = "utf8_iter" 1827 + version = "1.0.4" 1828 + source = "registry+https://github.com/rust-lang/crates.io-index" 1829 + checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1830 + 1831 + [[package]] 1832 + name = "version_check" 1833 + version = "0.9.5" 1834 + source = "registry+https://github.com/rust-lang/crates.io-index" 1835 + checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1836 + 1837 + [[package]] 1838 + name = "void" 1839 + version = "1.0.2" 1840 + source = "registry+https://github.com/rust-lang/crates.io-index" 1841 + checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 1842 + 1843 + [[package]] 1844 + name = "walkdir" 1845 + version = "2.5.0" 1846 + source = "registry+https://github.com/rust-lang/crates.io-index" 1847 + checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 1848 + dependencies = [ 1849 + "same-file", 1850 + "winapi-util", 1851 + ] 1852 + 1853 + [[package]] 1854 + name = "wasi" 1855 + version = "0.11.1+wasi-snapshot-preview1" 1856 + source = "registry+https://github.com/rust-lang/crates.io-index" 1857 + checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" 1858 + 1859 + [[package]] 1860 + name = "wasip2" 1861 + version = "1.0.3+wasi-0.2.9" 1862 + source = "registry+https://github.com/rust-lang/crates.io-index" 1863 + checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" 1864 + dependencies = [ 1865 + "wit-bindgen 0.57.1", 1866 + ] 1867 + 1868 + [[package]] 1869 + name = "wasip3" 1870 + version = "0.4.0+wasi-0.3.0-rc-2026-01-06" 1871 + source = "registry+https://github.com/rust-lang/crates.io-index" 1872 + checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" 1873 + dependencies = [ 1874 + "wit-bindgen 0.51.0", 1875 + ] 1876 + 1877 + [[package]] 1878 + name = "wasm-bindgen" 1879 + version = "0.2.118" 1880 + source = "registry+https://github.com/rust-lang/crates.io-index" 1881 + checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" 1882 + dependencies = [ 1883 + "cfg-if", 1884 + "once_cell", 1885 + "rustversion", 1886 + "wasm-bindgen-macro", 1887 + "wasm-bindgen-shared", 1888 + ] 1889 + 1890 + [[package]] 1891 + name = "wasm-bindgen-macro" 1892 + version = "0.2.118" 1893 + source = "registry+https://github.com/rust-lang/crates.io-index" 1894 + checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" 1895 + dependencies = [ 1896 + "quote", 1897 + "wasm-bindgen-macro-support", 1898 + ] 1899 + 1900 + [[package]] 1901 + name = "wasm-bindgen-macro-support" 1902 + version = "0.2.118" 1903 + source = "registry+https://github.com/rust-lang/crates.io-index" 1904 + checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" 1905 + dependencies = [ 1906 + "bumpalo", 1907 + "proc-macro2", 1908 + "quote", 1909 + "syn 2.0.117", 1910 + "wasm-bindgen-shared", 1911 + ] 1912 + 1913 + [[package]] 1914 + name = "wasm-bindgen-shared" 1915 + version = "0.2.118" 1916 + source = "registry+https://github.com/rust-lang/crates.io-index" 1917 + checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" 1918 + dependencies = [ 1919 + "unicode-ident", 1920 + ] 1921 + 1922 + [[package]] 1923 + name = "wasm-encoder" 1924 + version = "0.244.0" 1925 + source = "registry+https://github.com/rust-lang/crates.io-index" 1926 + checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" 1927 + dependencies = [ 1928 + "leb128fmt", 1929 + "wasmparser", 1930 + ] 1931 + 1932 + [[package]] 1933 + name = "wasm-metadata" 1934 + version = "0.244.0" 1935 + source = "registry+https://github.com/rust-lang/crates.io-index" 1936 + checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" 1937 + dependencies = [ 1938 + "anyhow", 1939 + "indexmap", 1940 + "wasm-encoder", 1941 + "wasmparser", 1942 + ] 1943 + 1944 + [[package]] 1945 + name = "wasmparser" 1946 + version = "0.244.0" 1947 + source = "registry+https://github.com/rust-lang/crates.io-index" 1948 + checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" 1949 + dependencies = [ 1950 + "bitflags 2.11.1", 1951 + "hashbrown 0.15.5", 1952 + "indexmap", 1953 + "semver", 1954 + ] 1955 + 1956 + [[package]] 1957 + name = "webpki-roots" 1958 + version = "0.26.11" 1959 + source = "registry+https://github.com/rust-lang/crates.io-index" 1960 + checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" 1961 + dependencies = [ 1962 + "webpki-roots 1.0.7", 1963 + ] 1964 + 1965 + [[package]] 1966 + name = "webpki-roots" 1967 + version = "1.0.7" 1968 + source = "registry+https://github.com/rust-lang/crates.io-index" 1969 + checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" 1970 + dependencies = [ 1971 + "rustls-pki-types", 1972 + ] 1973 + 1974 + [[package]] 1975 + name = "which" 1976 + version = "4.4.2" 1977 + source = "registry+https://github.com/rust-lang/crates.io-index" 1978 + checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 1979 + dependencies = [ 1980 + "either", 1981 + "home", 1982 + "once_cell", 1983 + "rustix 0.38.44", 1984 + ] 1985 + 1986 + [[package]] 1987 + name = "winapi-util" 1988 + version = "0.1.11" 1989 + source = "registry+https://github.com/rust-lang/crates.io-index" 1990 + checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" 1991 + dependencies = [ 1992 + "windows-sys 0.61.2", 1993 + ] 1994 + 1995 + [[package]] 1996 + name = "windows-core" 1997 + version = "0.62.2" 1998 + source = "registry+https://github.com/rust-lang/crates.io-index" 1999 + checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" 2000 + dependencies = [ 2001 + "windows-implement", 2002 + "windows-interface", 2003 + "windows-link", 2004 + "windows-result", 2005 + "windows-strings", 2006 + ] 2007 + 2008 + [[package]] 2009 + name = "windows-implement" 2010 + version = "0.60.2" 2011 + source = "registry+https://github.com/rust-lang/crates.io-index" 2012 + checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" 2013 + dependencies = [ 2014 + "proc-macro2", 2015 + "quote", 2016 + "syn 2.0.117", 2017 + ] 2018 + 2019 + [[package]] 2020 + name = "windows-interface" 2021 + version = "0.59.3" 2022 + source = "registry+https://github.com/rust-lang/crates.io-index" 2023 + checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" 2024 + dependencies = [ 2025 + "proc-macro2", 2026 + "quote", 2027 + "syn 2.0.117", 2028 + ] 2029 + 2030 + [[package]] 2031 + name = "windows-link" 2032 + version = "0.2.1" 2033 + source = "registry+https://github.com/rust-lang/crates.io-index" 2034 + checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" 2035 + 2036 + [[package]] 2037 + name = "windows-result" 2038 + version = "0.4.1" 2039 + source = "registry+https://github.com/rust-lang/crates.io-index" 2040 + checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" 2041 + dependencies = [ 2042 + "windows-link", 2043 + ] 2044 + 2045 + [[package]] 2046 + name = "windows-strings" 2047 + version = "0.5.1" 2048 + source = "registry+https://github.com/rust-lang/crates.io-index" 2049 + checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" 2050 + dependencies = [ 2051 + "windows-link", 2052 + ] 2053 + 2054 + [[package]] 2055 + name = "windows-sys" 2056 + version = "0.52.0" 2057 + source = "registry+https://github.com/rust-lang/crates.io-index" 2058 + checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2059 + dependencies = [ 2060 + "windows-targets", 2061 + ] 2062 + 2063 + [[package]] 2064 + name = "windows-sys" 2065 + version = "0.59.0" 2066 + source = "registry+https://github.com/rust-lang/crates.io-index" 2067 + checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2068 + dependencies = [ 2069 + "windows-targets", 2070 + ] 2071 + 2072 + [[package]] 2073 + name = "windows-sys" 2074 + version = "0.61.2" 2075 + source = "registry+https://github.com/rust-lang/crates.io-index" 2076 + checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" 2077 + dependencies = [ 2078 + "windows-link", 2079 + ] 2080 + 2081 + [[package]] 2082 + name = "windows-targets" 2083 + version = "0.52.6" 2084 + source = "registry+https://github.com/rust-lang/crates.io-index" 2085 + checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2086 + dependencies = [ 2087 + "windows_aarch64_gnullvm", 2088 + "windows_aarch64_msvc", 2089 + "windows_i686_gnu", 2090 + "windows_i686_gnullvm", 2091 + "windows_i686_msvc", 2092 + "windows_x86_64_gnu", 2093 + "windows_x86_64_gnullvm", 2094 + "windows_x86_64_msvc", 2095 + ] 2096 + 2097 + [[package]] 2098 + name = "windows_aarch64_gnullvm" 2099 + version = "0.52.6" 2100 + source = "registry+https://github.com/rust-lang/crates.io-index" 2101 + checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2102 + 2103 + [[package]] 2104 + name = "windows_aarch64_msvc" 2105 + version = "0.52.6" 2106 + source = "registry+https://github.com/rust-lang/crates.io-index" 2107 + checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2108 + 2109 + [[package]] 2110 + name = "windows_i686_gnu" 2111 + version = "0.52.6" 2112 + source = "registry+https://github.com/rust-lang/crates.io-index" 2113 + checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2114 + 2115 + [[package]] 2116 + name = "windows_i686_gnullvm" 2117 + version = "0.52.6" 2118 + source = "registry+https://github.com/rust-lang/crates.io-index" 2119 + checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2120 + 2121 + [[package]] 2122 + name = "windows_i686_msvc" 2123 + version = "0.52.6" 2124 + source = "registry+https://github.com/rust-lang/crates.io-index" 2125 + checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2126 + 2127 + [[package]] 2128 + name = "windows_x86_64_gnu" 2129 + version = "0.52.6" 2130 + source = "registry+https://github.com/rust-lang/crates.io-index" 2131 + checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2132 + 2133 + [[package]] 2134 + name = "windows_x86_64_gnullvm" 2135 + version = "0.52.6" 2136 + source = "registry+https://github.com/rust-lang/crates.io-index" 2137 + checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2138 + 2139 + [[package]] 2140 + name = "windows_x86_64_msvc" 2141 + version = "0.52.6" 2142 + source = "registry+https://github.com/rust-lang/crates.io-index" 2143 + checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2144 + 2145 + [[package]] 2146 + name = "winnow" 2147 + version = "0.5.40" 2148 + source = "registry+https://github.com/rust-lang/crates.io-index" 2149 + checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 2150 + dependencies = [ 2151 + "memchr", 2152 + ] 2153 + 2154 + [[package]] 2155 + name = "winnow" 2156 + version = "1.0.2" 2157 + source = "registry+https://github.com/rust-lang/crates.io-index" 2158 + checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" 2159 + dependencies = [ 2160 + "memchr", 2161 + ] 2162 + 2163 + [[package]] 2164 + name = "wit-bindgen" 2165 + version = "0.51.0" 2166 + source = "registry+https://github.com/rust-lang/crates.io-index" 2167 + checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" 2168 + dependencies = [ 2169 + "wit-bindgen-rust-macro", 2170 + ] 2171 + 2172 + [[package]] 2173 + name = "wit-bindgen" 2174 + version = "0.57.1" 2175 + source = "registry+https://github.com/rust-lang/crates.io-index" 2176 + checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" 2177 + 2178 + [[package]] 2179 + name = "wit-bindgen-core" 2180 + version = "0.51.0" 2181 + source = "registry+https://github.com/rust-lang/crates.io-index" 2182 + checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" 2183 + dependencies = [ 2184 + "anyhow", 2185 + "heck 0.5.0", 2186 + "wit-parser", 2187 + ] 2188 + 2189 + [[package]] 2190 + name = "wit-bindgen-rust" 2191 + version = "0.51.0" 2192 + source = "registry+https://github.com/rust-lang/crates.io-index" 2193 + checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" 2194 + dependencies = [ 2195 + "anyhow", 2196 + "heck 0.5.0", 2197 + "indexmap", 2198 + "prettyplease", 2199 + "syn 2.0.117", 2200 + "wasm-metadata", 2201 + "wit-bindgen-core", 2202 + "wit-component", 2203 + ] 2204 + 2205 + [[package]] 2206 + name = "wit-bindgen-rust-macro" 2207 + version = "0.51.0" 2208 + source = "registry+https://github.com/rust-lang/crates.io-index" 2209 + checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" 2210 + dependencies = [ 2211 + "anyhow", 2212 + "prettyplease", 2213 + "proc-macro2", 2214 + "quote", 2215 + "syn 2.0.117", 2216 + "wit-bindgen-core", 2217 + "wit-bindgen-rust", 2218 + ] 2219 + 2220 + [[package]] 2221 + name = "wit-component" 2222 + version = "0.244.0" 2223 + source = "registry+https://github.com/rust-lang/crates.io-index" 2224 + checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" 2225 + dependencies = [ 2226 + "anyhow", 2227 + "bitflags 2.11.1", 2228 + "indexmap", 2229 + "log", 2230 + "serde", 2231 + "serde_derive", 2232 + "serde_json", 2233 + "wasm-encoder", 2234 + "wasm-metadata", 2235 + "wasmparser", 2236 + "wit-parser", 2237 + ] 2238 + 2239 + [[package]] 2240 + name = "wit-parser" 2241 + version = "0.244.0" 2242 + source = "registry+https://github.com/rust-lang/crates.io-index" 2243 + checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" 2244 + dependencies = [ 2245 + "anyhow", 2246 + "id-arena", 2247 + "indexmap", 2248 + "log", 2249 + "semver", 2250 + "serde", 2251 + "serde_derive", 2252 + "serde_json", 2253 + "unicode-xid", 2254 + "wasmparser", 2255 + ] 2256 + 2257 + [[package]] 2258 + name = "writeable" 2259 + version = "0.6.3" 2260 + source = "registry+https://github.com/rust-lang/crates.io-index" 2261 + checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" 2262 + 2263 + [[package]] 2264 + name = "yoke" 2265 + version = "0.8.2" 2266 + source = "registry+https://github.com/rust-lang/crates.io-index" 2267 + checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" 2268 + dependencies = [ 2269 + "stable_deref_trait", 2270 + "yoke-derive", 2271 + "zerofrom", 2272 + ] 2273 + 2274 + [[package]] 2275 + name = "yoke-derive" 2276 + version = "0.8.2" 2277 + source = "registry+https://github.com/rust-lang/crates.io-index" 2278 + checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" 2279 + dependencies = [ 2280 + "proc-macro2", 2281 + "quote", 2282 + "syn 2.0.117", 2283 + "synstructure", 2284 + ] 2285 + 2286 + [[package]] 2287 + name = "zerofrom" 2288 + version = "0.1.7" 2289 + source = "registry+https://github.com/rust-lang/crates.io-index" 2290 + checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" 2291 + dependencies = [ 2292 + "zerofrom-derive", 2293 + ] 2294 + 2295 + [[package]] 2296 + name = "zerofrom-derive" 2297 + version = "0.1.7" 2298 + source = "registry+https://github.com/rust-lang/crates.io-index" 2299 + checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" 2300 + dependencies = [ 2301 + "proc-macro2", 2302 + "quote", 2303 + "syn 2.0.117", 2304 + "synstructure", 2305 + ] 2306 + 2307 + [[package]] 2308 + name = "zeroize" 2309 + version = "1.8.2" 2310 + source = "registry+https://github.com/rust-lang/crates.io-index" 2311 + checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" 2312 + 2313 + [[package]] 2314 + name = "zerotrie" 2315 + version = "0.2.4" 2316 + source = "registry+https://github.com/rust-lang/crates.io-index" 2317 + checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" 2318 + dependencies = [ 2319 + "displaydoc", 2320 + "yoke", 2321 + "zerofrom", 2322 + ] 2323 + 2324 + [[package]] 2325 + name = "zerovec" 2326 + version = "0.11.6" 2327 + source = "registry+https://github.com/rust-lang/crates.io-index" 2328 + checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" 2329 + dependencies = [ 2330 + "yoke", 2331 + "zerofrom", 2332 + "zerovec-derive", 2333 + ] 2334 + 2335 + [[package]] 2336 + name = "zerovec-derive" 2337 + version = "0.11.3" 2338 + source = "registry+https://github.com/rust-lang/crates.io-index" 2339 + checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" 2340 + dependencies = [ 2341 + "proc-macro2", 2342 + "quote", 2343 + "syn 2.0.117", 2344 + ] 2345 + 2346 + [[package]] 2347 + name = "zmij" 2348 + version = "1.0.21" 2349 + source = "registry+https://github.com/rust-lang/crates.io-index" 2350 + checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+33
firmware/Cargo.toml
··· 1 + [package] 2 + name = "sound-machine" 3 + version = "0.0.1" 4 + edition = "2021" 5 + resolver = "2" 6 + rust-version = "1.77" 7 + 8 + [[bin]] 9 + name = "sound-machine" 10 + harness = false 11 + 12 + [profile.release] 13 + opt-level = "s" 14 + 15 + [profile.dev] 16 + debug = true 17 + opt-level = "z" 18 + 19 + [features] 20 + default = ["std", "esp-idf-svc/native"] 21 + pio = ["esp-idf-svc/pio"] 22 + std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"] 23 + alloc = ["esp-idf-svc/alloc"] 24 + nightly = ["esp-idf-svc/nightly"] 25 + experimental = ["esp-idf-svc/experimental"] 26 + 27 + [dependencies] 28 + log = { version = "0.4", default-features = false } 29 + esp-idf-svc = { version = "0.51", default-features = false } 30 + anyhow = "1" 31 + 32 + [build-dependencies] 33 + embuild = "0.33"
+169
firmware/README.md
··· 1 + # firmware/ 2 + 3 + Rust firmware for the M5Stack Atom Echo–based nightstand sound machines. Built on **`esp-idf-svc` (std)** + the C ESP-IDF underneath. Targets the **`esp` Xtensa Rust toolchain** installed via `espup`. 4 + 5 + For the *what* and *why* (architecture, MQTT contract, operating modes, signal chain), see [`../reference/`](../reference/). This README is the *how* — current state, dev workflow, and the gotchas we've already paid for. 6 + 7 + ## Status 8 + 9 + **Milestone v0.0.1 — hello world (2026-04-25).** Boots, reads the button on G39, plays a clean 880 Hz beep through the onboard NS4168 + tiny built-in speaker. Validates the toolchain end-to-end on Ubuntu Questing. 10 + 11 + | Subsystem | State | 12 + | --- | --- | 13 + | Cargo build + flash | ✅ working | 14 + | GPIO input (button G39) | ✅ working — active-low, on-board pull-up | 15 + | I2S TX (16-bit / 44.1 kHz / stereo, Philips) | ✅ working into onboard NS4168 | 16 + | DMA-fed silence to keep amp idle | ✅ working — no idle hum | 17 + | Audio playback (one-shot beep) | ✅ working | 18 + | RGB LED (SK6812 on G27 via RMT) | ❌ TBD | 19 + | WiFi | ❌ TBD | 20 + | MQTT client + HA discovery | ❌ TBD | 21 + | Continuous white noise generator | ❌ TBD | 22 + | Button state machine (single/double/long) | ❌ TBD — currently just edge detection | 23 + | NVS persistence | ❌ TBD | 24 + | OTA updates | ❌ TBD (v1.5 design lives in `mqtt-contract.md`) | 25 + | Hardware: external MAX98357A + 1314 speaker | ❌ amps in transit; using onboard for now | 26 + 27 + ## Build & flash 28 + 29 + Project uses direnv (`.envrc` at the project root). Open a terminal in any subdir of the project and the env loads automatically. 30 + 31 + ```bash 32 + # from project root or anywhere under it: 33 + cd firmware 34 + cargo build --release 35 + espflash flash --port /dev/ttyUSB0 target/xtensa-esp32-espidf/release/sound-machine 36 + ``` 37 + 38 + Or in one step (uses our configured runner): 39 + 40 + ```bash 41 + cargo run --release # builds, flashes, attaches serial monitor 42 + ``` 43 + 44 + Incremental builds are ~5 s. First-time builds are ~20 min — they download and compile ESP-IDF (~500 MB) plus all the Rust deps. 45 + 46 + ### Monitoring without re-flashing 47 + 48 + To see logs from an already-running device: 49 + 50 + ```bash 51 + espflash monitor --port /dev/ttyUSB0 52 + ``` 53 + 54 + `Ctrl+C` exits the monitor; the device keeps running. Anything written via `log::info!()` etc. shows up here, prefixed with the crate name and a millisecond timestamp. 55 + 56 + ## Toolchain (one-time setup) 57 + 58 + Already done on Chris's vega; documented here for re-setup or a second machine. 59 + 60 + ```bash 61 + # 1. Rust 1.88+ stable (espup needs it) 62 + rustup update stable 63 + 64 + # 2. espup CLI itself 65 + cargo install espup 66 + 67 + # 3. The Xtensa fork of Rust + LLVM + GCC for ESP32 68 + espup install --targets esp32 # writes ~/export-esp.sh — see "Home dir cleanup" below 69 + 70 + # 4. Flasher + linker shim 71 + cargo install espflash 72 + cargo install ldproxy 73 + 74 + # 5. System packages (Ubuntu/Debian) 75 + sudo apt install ninja-build # ESP-IDF's CMake build needs it 76 + sudo apt install libxml2 # see "libxml2 shim" gotcha below 77 + ``` 78 + 79 + ### Home dir cleanup 80 + 81 + `espup install` writes `~/export-esp.sh`. We don't keep that — the two `export` lines it contains are inlined into the project's [`.envrc`](../.envrc) so the home dir stays clean. If you re-run `espup install` (e.g., for a toolchain update), copy the regenerated `~/export-esp.sh` contents into `.envrc` and delete the home-dir file. Or pass `--export-file ./.envrc-esp` to `espup install` and source that explicitly. 82 + 83 + ## Known gotchas (so we don't relearn these) 84 + 85 + These all bit us during initial setup. Some are environmental, some are esp-rs ecosystem quirks. 86 + 87 + ### 1. `libxml2.so.2` missing on Ubuntu Questing (and any libxml2 ≥ 2.14 distro) 88 + 89 + ESP-IDF's bundled esp-clang is linked against the older `libxml2.so.2` SONAME, but Ubuntu Questing ships libxml2 with SONAME `.so.16` (upstream bumped it in 2.14). First `cargo build` fails with: 90 + 91 + ``` 92 + clang: error while loading shared libraries: libxml2.so.2: cannot open shared object file 93 + ``` 94 + 95 + **Fix:** project-local symlink in `firmware/.lib/libxml2.so.2 → /usr/lib/x86_64-linux-gnu/libxml2.so.16`, exposed via `LD_LIBRARY_PATH` in [`.envrc`](../.envrc). The clang/libxml2 ABI surface is small and stable enough that the SONAME alias works. The `.lib/` dir is gitignored. 96 + 97 + ### 2. Default Cargo features pulled in `embassy` and broke linking 98 + 99 + The vanilla `esp-idf-template` enables `embassy` in default features, which adds `embassy-executor` to the dep graph. Without an actual embassy executor wired up, the linker errors with `undefined reference to __pender`. We don't use embassy in v1, so [`Cargo.toml`](Cargo.toml) drops it from `default = [...]`. Re-add it (and properly wire an executor) if/when we want async. 100 + 101 + ### 3. `I2sDriver::new_std_tx` pin order is not what intuition expects 102 + 103 + Signature in `esp-idf-hal 0.45.2`: 104 + 105 + ```rust 106 + new_std_tx(i2s, config, bclk, dout, mclk, ws) 107 + ``` 108 + 109 + That's **`dout` before `ws`**, not `bclk → ws → dout` as you'd guess from reading "Bclk, Ws, Data" in audio convention. Getting them swapped produces: 110 + - "Static" instead of a tone (data on the LRCK line, clock on the data line) 111 + - A persistent quiet squeal during silence (WS clock ticking on the data input) 112 + - The NS4168 amp running warm (sustained switching, no real audio output) 113 + 114 + The compiler accepts both orderings since the pin types are generic. Always check the signature against the version of `esp-idf-hal` you're on. Symptom: garbage output where you'd expect tone. 115 + 116 + ### 4. DMA replays stale data when starved 117 + 118 + ESP-IDF's I2S driver, once enabled, will keep clocking out whatever's in its DMA buffer. If you write data once and then stop, the buffer keeps getting replayed → an "idling" device produces a quiet repeating noise loop. 119 + 120 + **Fix in `main.rs`:** 121 + - Pre-fill DMA with zeros via `i2s.preload_data(&silence)` *before* `tx_enable()` 122 + - In the main loop, write a silence chunk every iteration. `i2s.write` blocks for ~23 ms while DMA consumes it, which is also a perfectly fine button-poll cadence — no `sleep` needed. 123 + 124 + This keeps the amp's input continuously fed, even when "doing nothing." 125 + 126 + ### 5. The onboard NS4168 + tiny speaker is a prototype-only path 127 + 128 + We're using the I2S pins that drive the built-in amp (G19 BCLK, G22 DOUT, G33 LRCK) for hello-world. The plan ([signal-chain.md](../reference/signal-chain.md)) is to switch to an external MAX98357A on G21/G26/G32 once amps arrive. Don't run sustained white noise on the built-in speaker — short test bursts only. The thermal warning on this is real (we briefly demonstrated it via the swapped-pins bug above). 129 + 130 + ## Layout 131 + 132 + ``` 133 + firmware/ 134 + ├── Cargo.toml # deps + features 135 + ├── rust-toolchain.toml # pins to the `esp` channel 136 + ├── .cargo/config.toml # target = xtensa-esp32-espidf, ldproxy, runner 137 + ├── build.rs # embuild bootstrap 138 + ├── sdkconfig.defaults # ESP-IDF kconfig knobs 139 + ├── src/ 140 + │ └── main.rs # entry point — currently the hello-world 141 + ├── .lib/ # libxml2 shim (gitignored) 142 + ├── target/ # cargo build output (gitignored) 143 + └── .embuild/ # embuild-managed ESP-IDF clone (gitignored) 144 + ``` 145 + 146 + ## Hardware-pinout cheat sheet (Atom Echo, prototype config) 147 + 148 + | Pin | Function (this firmware) | Notes | 149 + | --- | --- | --- | 150 + | G39 | Button input | Active-low, on-board pull-up, input-only pin | 151 + | G19 | I2S BCLK → NS4168 | Onboard amp, prototype-only | 152 + | G22 | I2S DOUT → NS4168 | Onboard amp data | 153 + | G33 | I2S WS/LRCK → NS4168 | Onboard amp word select | 154 + | G27 | SK6812 RGB LED data | Not used yet | 155 + | G26 | GROVE yellow / future I2S BCLK to MAX98357A | Free | 156 + | G32 | GROVE white / future I2S WS to MAX98357A | Free | 157 + | G21 | Side header / future I2S DOUT to MAX98357A | Free | 158 + | G25 | Side header / spare | Free | 159 + 160 + Authoritative pinout: [`../reference/atom-echo/pinmap.md`](../reference/atom-echo/pinmap.md). 161 + 162 + ## Sources 163 + 164 + - [ESP-IDF documentation (v5.3)](https://docs.espressif.com/projects/esp-idf/en/v5.3.3/esp32/) 165 + - [`esp-idf-svc` on docs.rs](https://docs.rs/esp-idf-svc/0.51.0/esp_idf_svc/) 166 + - [`esp-idf-hal` on docs.rs](https://docs.rs/esp-idf-hal/0.45.2/esp_idf_hal/) 167 + - [esp-rs / Embedded Rust on Espressif (book)](https://docs.esp-rs.org/book/) 168 + - [`espup` README — env setup options](https://github.com/esp-rs/espup#environment-variables-setup) 169 + - [Project design docs](../reference/) — operating modes, MQTT contract, signal chain, pin map
+3
firmware/build.rs
··· 1 + fn main() { 2 + embuild::espidf::sysenv::output(); 3 + }
+2
firmware/rust-toolchain.toml
··· 1 + [toolchain] 2 + channel = "esp"
+8
firmware/sdkconfig.defaults
··· 1 + # Increase task watchdog to give first-boot WiFi/MQTT room to time out 2 + CONFIG_ESP_TASK_WDT_TIMEOUT_S=10 3 + 4 + # Faster FreeRTOS tick for tighter audio scheduling 5 + CONFIG_FREERTOS_HZ=1000 6 + 7 + # Bigger main task stack — we'll allocate audio buffers on it 8 + CONFIG_ESP_MAIN_TASK_STACK_SIZE=16384
+113
firmware/src/main.rs
··· 1 + //! Sound Machine — hello-world milestone v0.0.1. 2 + //! 3 + //! Press the onboard button, play a short 880 Hz beep through the onboard 4 + //! NS4168 amp + tiny built-in speaker. No WiFi, no MQTT yet — just enough 5 + //! to validate the Rust + ESP-IDF + flash + I2S toolchain end to end. 6 + //! 7 + //! Pin map (from the Atom Echo schematic): 8 + //! GPIO 39 — button (input-only, external pull-up on board, active low) 9 + //! GPIO 19 — I2S BCLK (NS4168) 10 + //! GPIO 33 — I2S LRCK (NS4168) 11 + //! GPIO 22 — I2S DOUT (NS4168) 12 + //! 13 + //! Note: this uses the onboard amp's pins for prototyping. The real firmware 14 + //! will route I2S to the external MAX98357A on G21/G26/G32 instead, leaving 15 + //! the onboard amp idle. See `reference/signal-chain.md`. 16 + 17 + use anyhow::Result; 18 + use esp_idf_svc::hal::gpio::{AnyIOPin, PinDriver}; 19 + use esp_idf_svc::hal::i2s::config::{Config, DataBitWidth, StdClkConfig, StdConfig, StdGpioConfig, StdSlotConfig}; 20 + use esp_idf_svc::hal::i2s::I2sDriver; 21 + use esp_idf_svc::hal::peripherals::Peripherals; 22 + use log::info; 23 + use std::f32::consts::PI; 24 + 25 + const SAMPLE_RATE: u32 = 44_100; 26 + const BEEP_FREQ_HZ: f32 = 880.0; 27 + const BEEP_DURATION_MS: u32 = 150; 28 + const BEEP_AMPLITUDE: f32 = 20_000.0; // ~60% of i16::MAX — louder, cleaner against quantization noise 29 + const ENVELOPE_MS: u32 = 10; // attack + release each, click suppression 30 + const SILENCE_CHUNK_BYTES: usize = 4096; // ~23 ms at 44.1 kHz stereo 16-bit — also our button-poll cadence 31 + 32 + fn main() -> Result<()> { 33 + esp_idf_svc::sys::link_patches(); 34 + esp_idf_svc::log::EspLogger::initialize_default(); 35 + 36 + info!("sound-machine v0.0.1 boot"); 37 + 38 + let peripherals = Peripherals::take()?; 39 + let pins = peripherals.pins; 40 + 41 + let button = PinDriver::input(pins.gpio39)?; 42 + 43 + let i2s_config = StdConfig::new( 44 + Config::default(), 45 + StdClkConfig::from_sample_rate_hz(SAMPLE_RATE), 46 + StdSlotConfig::philips_slot_default(DataBitWidth::Bits16, esp_idf_svc::hal::i2s::config::SlotMode::Stereo), 47 + StdGpioConfig::default(), 48 + ); 49 + 50 + // Signature: new_std_tx(i2s, config, bclk, dout, mclk, ws) 51 + let mut i2s = I2sDriver::new_std_tx( 52 + peripherals.i2s0, 53 + &i2s_config, 54 + pins.gpio19, // BCLK 55 + pins.gpio22, // DOUT (data to NS4168) 56 + Option::<AnyIOPin>::None, // no MCLK 57 + pins.gpio33, // WS / LRCK 58 + )?; 59 + 60 + // Pre-fill DMA buffers with silence so the first thing the NS4168 sees 61 + // when TX enables is zeros, not uninitialized memory. 62 + let silence = vec![0u8; SILENCE_CHUNK_BYTES]; 63 + i2s.preload_data(&silence)?; 64 + i2s.tx_enable()?; 65 + 66 + let beep = build_beep_buffer(); 67 + info!("ready — press the button to beep ({} samples)", beep.len() / 4); 68 + 69 + let mut last_high = button.is_high(); 70 + loop { 71 + let now_high = button.is_high(); 72 + let press_edge = last_high && !now_high; // active-low: H→L = press 73 + last_high = now_high; 74 + 75 + if press_edge { 76 + info!("button pressed"); 77 + // Write the full beep buffer — i2s.write blocks while DMA consumes 78 + let mut w = 0; 79 + while w < beep.len() { 80 + w += i2s.write(&beep[w..], 1_000)?; 81 + } 82 + } 83 + 84 + // Always feed silence so the DMA never replays stale data. 85 + // This call blocks for ~23 ms (one chunk's worth of audio time) and 86 + // doubles as our button-poll cadence — no explicit sleep needed. 87 + i2s.write(&silence, 1_000)?; 88 + } 89 + } 90 + 91 + /// Build a stereo, 16-bit-LE PCM buffer of a single sine beep with attack/release 92 + /// envelopes to avoid pop/click artifacts. 93 + fn build_beep_buffer() -> Vec<u8> { 94 + let total_samples = (SAMPLE_RATE * BEEP_DURATION_MS / 1_000) as usize; 95 + let env_samples = (SAMPLE_RATE * ENVELOPE_MS / 1_000) as usize; 96 + let mut buf = Vec::with_capacity(total_samples * 4); // stereo × 2 bytes 97 + 98 + for i in 0..total_samples { 99 + let t = i as f32 / SAMPLE_RATE as f32; 100 + let envelope = if i < env_samples { 101 + i as f32 / env_samples as f32 102 + } else if i >= total_samples - env_samples { 103 + (total_samples - i) as f32 / env_samples as f32 104 + } else { 105 + 1.0 106 + }; 107 + let s = ((2.0 * PI * BEEP_FREQ_HZ * t).sin() * BEEP_AMPLITUDE * envelope) as i16; 108 + let bytes = s.to_le_bytes(); 109 + buf.extend_from_slice(&bytes); // L 110 + buf.extend_from_slice(&bytes); // R (same — mono content, stereo carrier) 111 + } 112 + buf 113 + }
reference/atom-echo/atomecho_sch_01.png

This is a binary file and will not be displayed.

reference/atom-echo/atomecho_sch_01.webp

This is a binary file and will not be displayed.

+24
reference/atom-echo/dimensions.md
··· 1 + # Atom Echo physical dimensions 2 + 3 + - **Body**: 24 × 24 × 16.8 mm (near-cube) 4 + - **Corner radius**: R3 (body), R0.5 (USB-C cutout) 5 + - **USB-C port**: on top face, ~11.6 mm wide 6 + - **Speaker grille**: front face 7 + - **Button**: top face (through the RGB LED button cap) 8 + - **Mounting**: 2 holes on bottom face, 12 mm apart, centered — M2 screws 9 + - **GROVE connector**: back face (HY2.0-4P) 10 + 11 + ## Enclosure implications 12 + 13 + The Atom Echo itself will be the "compute module" inside a larger nightstand enclosure. Need to account for: 14 + 15 + - Cable exits: USB-C (power) and GROVE (if we use it) — both on opposite faces 16 + - Button access: the top-face button is the whole top surface, so the enclosure needs to either expose it or transmit the press mechanically 17 + - LED visibility: the SK6812 glows through the button — same access concern 18 + - Mic port: there's a small hole on the body for the mic (visible in dimension drawing); probably won't matter since we're only using it for output, but if we ever want wake-word later we'd need an acoustic path 19 + - Mounting: two M2 screw posts inside the enclosure, 12 mm apart 20 + 21 + ## Sources 22 + 23 + - [Atom Echo product docs (M5Stack)](https://docs.m5stack.com/en/atom/atomecho) — dimensions section 24 + - [module-size.jpg](./module-size.jpg) — mechanical drawing, downloaded from M5Stack docs
+50
reference/atom-echo/disabling-internal-speaker.md
··· 1 + # Disabling the onboard NS4168 / using an external DAC 2 + 3 + ## The problem 4 + 5 + M5Stack warns against playing white noise through the Atom Echo because the onboard NS4168 Class-D amp (driving a 0.8 W micro speaker in a tiny unventilated case) isn't sized for sustained RMS power. We want to drive a properly sized external speaker instead, which means either (a) disabling the onboard amp, or (b) routing I2S to pins that don't feed it. 6 + 7 + ## What the community has figured out 8 + 9 + ### Software-only: omit the ESPHome `speaker:` block 10 + 11 + Since a 2024 ESPHome fix by `kevdliu` ([PR referenced in this thread][disable-thread]), you can just remove the speaker component entirely from your ESPHome YAML. The ESP32 then sends nothing to its I2S peripheral, and the NS4168 receives logic zeros — which produces no audio and negligible heat. No hardware modification required. 12 + 13 + This works because "silent I2S" ≠ "DC signal." The M5Stack warning about DC signals applies to a constant non-zero sample value (which would hold the speaker cone deflected and cook the voice coil). Zero samples are fine. 14 + 15 + ### Separate I2S pins for the external DAC (preferred for us) 16 + 17 + ESP32 I2S pins are fully reassignable via the GPIO matrix. Route `i2s_dout_pin`, `i2s_bclk_pin`, and `i2s_lrclk_pin` to free GPIOs — e.g., `G21` on the 5-pin side header, or `G26`/`G32` on the GROVE connector. The NS4168's lines (G19/G22/G33) stay silent, its internal speaker stays silent, and we have full independent control over the external DAC. 18 + 19 + ### Shared I2S pins (works but wrong for us) 20 + 21 + One user reported successfully wiring a UDA1334A in parallel on the onboard I2S lines: 22 + 23 + > vin → 5v, gnd → G, lrck → G33 lrck, din → g22 data out, bck → g19 bclk 24 + 25 + This works but means the onboard NS4168 is also amplifying everything — defeating the point. Only useful if you deliberately want both. 26 + 27 + ## Gotchas from the community 28 + 29 + - **STEMMA speaker on GPIO21 → distortion** that a user couldn't resolve. Unclear if it's the STEMMA amp or a pin/clock issue. Use a known-good DAC chip (UDA1334A or MAX98357A) rather than an all-in-one cheap STEMMA speaker. 30 + - **Old configs that hardcoded the speaker block** auto-reverted when users tried commenting it out. The fix is the post-July-2024 ESPHome release where removing the block just works. 31 + - **Some people drilled a hole in the case and soldered to the internal speaker wires** — don't. We're building our own enclosure. 32 + 33 + ## Our plan 34 + 35 + 1. ESPHome config with **external DAC** (likely UDA1334A or MAX98357A, decision pending) 36 + 2. I2S routed to **G26 and G32 (GROVE) plus one more** — need to confirm we have enough free pins for BCLK + LRCK + DATA. If not, reclaim the button GPIO G39 isn't usable (input-only), so we may need G21 from the header too. 37 + 3. **Omit the internal speaker block** or route its pins to the same external DAC — either way, nothing actually drives the NS4168. 38 + 4. No hardware modifications to the Atom Echo itself. 39 + 40 + ## Sources 41 + 42 + - [M5 Atom Echo: is it possible to disable the speaker?][disable-thread] 43 + - [Link M5 Atom Echo to external speaker][external-thread] 44 + - [M5Atom Echo - Connected External Speaker but it is Distorted][distortion-thread] 45 + - [ESPHome I²S Audio Speaker docs][esphome-i2s] 46 + 47 + [disable-thread]: https://community.home-assistant.io/t/m5-atom-echo-is-it-possible-to-disable-the-speaker/629581 48 + [external-thread]: https://community.home-assistant.io/t/link-m5-atom-echo-to-external-speaker/666773 49 + [distortion-thread]: https://community.home-assistant.io/t/m5atom-echo-connected-external-speaker-but-it-is-distorted/765081 50 + [esphome-i2s]: https://esphome.io/components/speaker/i2s_audio/
reference/atom-echo/module-size.jpg

This is a binary file and will not be displayed.

+55
reference/atom-echo/pinmap.md
··· 1 + # Atom Echo (C008-C / "Atom Voice") Pin Map 2 + 3 + ## Speaker + Microphone 4 + 5 + | ESP32 Pin | Component | Function | 6 + | --------- | -------------- | --------- | 7 + | G22 | NS4168 amp | I2S DATA | 8 + | G19 | NS4168 amp | I2S BCLK | 9 + | G33 | NS4168 amp | I2S LRCK | 10 + | G23 | SPM1423 mic | PDM CLK | 11 + | G33 | SPM1423 mic | PDM DATA | 12 + 13 + Note: **G33 is shared** between the amp's LRCK and the mic's DATA line — this is why the stock firmware can't record and play simultaneously ([confirmed in community docs][m5-docs]). Not a problem for our use case (output only), but something to remember if the design ever wants local wake-word. 14 + 15 + ## Human interface 16 + 17 + | ESP32 Pin | Component | Function | 18 + | --------- | -------------- | --------- | 19 + | G27 | SK6812 RGB LED | Data | 20 + | G39 | Button | Input (input-only pin) | 21 + 22 + ## GROVE connector (HY2.0-4P) 23 + 24 + | Pin | Color | Signal | 25 + | --- | ------ | ------ | 26 + | 1 | Black | GND | 27 + | 2 | Red | 5V | 28 + | 3 | Yellow | G26 | 29 + | 4 | White | G32 | 30 + 31 + ## Reserved pins — do not reuse 32 + 33 + From the M5Stack docs ([pinmap section][m5-docs]): 34 + 35 + > "G19 / G22 / G23 / G33 have been predefined. Do not reuse these pins; otherwise the Atom Echo may be damaged." 36 + 37 + ## Available for external hardware 38 + 39 + - GROVE: G26, G32 (plus 5V / GND) 40 + - Button G39 is reusable if we want a different button function 41 + 42 + ## Key takeaways for this project 43 + 44 + - **I2S output for our external DAC**: route to free pins (G26/G32 on GROVE, plus one more — likely borrowed from the side header) rather than the reserved G19/G22/G33. This avoids driving the NS4168 entirely. See [disabling-internal-speaker.md](./disabling-internal-speaker.md). 45 + - **NS4168 shutdown**: not needed — just don't send it I2S data (software-only solution in ESPHome, no hardware mod). 46 + - **Button** on G39 is our "start/stop bedtime routine" trigger. 47 + - **RGB LED** on G27 is a nice status indicator (idle / playing / HA unreachable / etc.) 48 + - **GROVE G26/G32** can be used for I2S out to the external DAC, or repurposed for a secondary input. 49 + 50 + ## Sources 51 + 52 + - [Atom Echo product docs (M5Stack)][m5-docs] — pin map table, reserved-pin warning 53 + - [ESP32-PICO-D4 datasheet](../datasheets/esp32-pico_series_datasheet_en.pdf) — for confirming GPIO capabilities (input-only pins, drive strength, etc.) 54 + 55 + [m5-docs]: https://docs.m5stack.com/en/atom/atomecho
reference/datasheets/NS4168_CN_datasheet.pdf

This is a binary file and will not be displayed.

reference/datasheets/SPM1423HM4H-B_datasheet_en.pdf

This is a binary file and will not be displayed.

reference/datasheets/esp32-pico_series_datasheet_en.pdf

This is a binary file and will not be displayed.

+406
reference/mqtt-contract.md
··· 1 + # MQTT contract 2 + 3 + The interface between each nightstand device and Home Assistant. Firmware and HA automations both target this spec. 4 + 5 + ## Design principles 6 + 7 + 1. **The device is dumb; HA is the brain.** The device reports button events and accepts commands. It does not know whether it's nighttime, what "bedtime routine" means, or what lights exist in the house. That's all HA. 8 + 2. **Local audio, remote control.** White noise is generated on the device (no dependency on HA media streaming). HA tells it when to start/stop and at what volume. 9 + 3. **MQTT Discovery** is the contract. At boot, the device publishes retained discovery configs under `homeassistant/<type>/nightstand_N/...` and HA auto-creates entities. 10 + 4. **Either unit can trigger a routine** — HA's automations use wildcards (`nightstand/+/button`) so it doesn't care which one. 11 + 5. **No TLS** — LAN-only broker at `mqtt.home.theguidrys.us`, per project decision. 12 + 13 + ## Division of labor 14 + 15 + | Responsibility | Device | HA | 16 + | --- | --- | --- | 17 + | Detect button press (short / long) | ✓ | | 18 + | Debounce | ✓ | | 19 + | Publish button event | ✓ | | 20 + | Decide what to do with the press | | ✓ | 21 + | Turn off house lights, thermostat, etc. | | ✓ | 22 + | Send "start white noise" command | | ✓ | 23 + | Generate white noise samples | ✓ | | 24 + | Drive speaker via I2S | ✓ | | 25 + | Track playing state | ✓ | | 26 + | Announce entities via discovery | ✓ | | 27 + 28 + ## Device identity 29 + 30 + **MAC-derived, with a known-device lookup table in firmware.** 31 + 32 + - At boot, read the ESP32's base MAC address. 33 + - Look it up in a `KNOWN_DEVICES` table hardcoded in the firmware: 34 + ```rust 35 + const KNOWN_DEVICES: &[(u64, &str)] = &[ 36 + (0xAABBCCDDEEFF, "nightstand_1"), 37 + (0x112233445566, "nightstand_2"), 38 + ]; 39 + ``` 40 + - If found: logical name is `nightstand_1` / `nightstand_2`, topic prefix is `nightstand/1/...` / `nightstand/2/...`. 41 + - If not found: fall back to `nightstand_<mac_hex>` (so the device still works and shows up in HA under its MAC, user can see it and add to the table). 42 + - `unique_id` fields in discovery always include the MAC to guarantee HA-side uniqueness even if names collide. 43 + 44 + **Why:** one firmware binary works on both units. OTA pushes the same `.bin` to both; each identifies itself. Eliminates per-unit build configs. 45 + 46 + **First-boot procedure** (per unit): 47 + 1. Flash with a stub `KNOWN_DEVICES` table 48 + 2. Device boots, logs its MAC to serial, publishes MQTT under `nightstand/<mac_hex>` 49 + 3. Add the MAC → name mapping to the table, reflash 50 + 4. Subsequent boots have stable identity 51 + 52 + ## Topic namespace 53 + 54 + ``` 55 + homeassistant/<type>/nightstand_N/<object>/config ← discovery (retain=true) 56 + nightstand/N/available ← LWT + online announce (retain=true) 57 + nightstand/N/button ← event stream (retain=false) 58 + nightstand/N/state ← JSON state snapshot (retain=true) 59 + nightstand/N/cmd/play ← inbound: "ON" / "OFF" 60 + nightstand/N/cmd/volume ← inbound: integer 0-100 61 + ``` 62 + 63 + Where `N` is `1` or `2`. 64 + 65 + Keeping all state in a single `state` JSON topic (rather than one topic per field) simplifies the device's publish logic and HA's `value_template` wiring. 66 + 67 + ## Entities exposed 68 + 69 + ### 1. Button — `event` type 70 + 71 + Distinguishes short press, double press, and long press (≥ 2s hold). HA automations trigger on event type. 72 + 73 + Discovery topic: `homeassistant/event/nightstand_1/button/config` 74 + ```json 75 + { 76 + "name": "Button", 77 + "unique_id": "nightstand_1_button", 78 + "state_topic": "nightstand/1/button", 79 + "event_types": ["short", "double", "long"], 80 + "value_template": "{{ value_json.event_type }}", 81 + "device": { 82 + "identifiers": ["nightstand_1"], 83 + "name": "Nightstand 1", 84 + "manufacturer": "guid.foo", 85 + "model": "Sound Machine v1", 86 + "sw_version": "0.1.0" 87 + }, 88 + "availability_topic": "nightstand/1/available" 89 + } 90 + ``` 91 + 92 + Event payload (published to `nightstand/1/button`, not retained): 93 + ```json 94 + {"event_type": "short"} 95 + ``` 96 + or `double`, or `long`. 97 + 98 + ### 2. White noise — `switch` 99 + 100 + Lets HA (and the HA UI) start/stop the noise and see its current state. 101 + 102 + Discovery topic: `homeassistant/switch/nightstand_1/white_noise/config` 103 + ```json 104 + { 105 + "name": "White Noise", 106 + "unique_id": "nightstand_1_white_noise", 107 + "state_topic": "nightstand/1/state", 108 + "value_template": "{{ value_json.playing }}", 109 + "command_topic": "nightstand/1/cmd/play", 110 + "payload_on": "ON", 111 + "payload_off": "OFF", 112 + "state_on": "ON", 113 + "state_off": "OFF", 114 + "device": {"identifiers": ["nightstand_1"]}, 115 + "availability_topic": "nightstand/1/available" 116 + } 117 + ``` 118 + 119 + ### 3. Volume — `number` 120 + 121 + 0–100 slider in HA. Device persists the last-set volume across reboots (NVS). 122 + 123 + Volume can also be changed by **double-pressing the physical button**, which cycles through a preset list defined in firmware (currently `[30, 50, 70, 90]` — tunable in source). The cycle advances to the next preset strictly greater than current volume, wrapping to the smallest when past the end. Either slider or button is authoritative at any moment — device publishes the new volume to `state` whichever path set it. 124 + 125 + Discovery topic: `homeassistant/number/nightstand_1/volume/config` 126 + ```json 127 + { 128 + "name": "Volume", 129 + "unique_id": "nightstand_1_volume", 130 + "state_topic": "nightstand/1/state", 131 + "value_template": "{{ value_json.volume }}", 132 + "command_topic": "nightstand/1/cmd/volume", 133 + "min": 0, 134 + "max": 100, 135 + "step": 1, 136 + "mode": "slider", 137 + "device": {"identifiers": ["nightstand_1"]}, 138 + "availability_topic": "nightstand/1/available" 139 + } 140 + ``` 141 + 142 + ### 4. Diagnostics — `sensor` ×2 143 + 144 + Helpful for debugging; marked as diagnostic so they hide in the default device view. 145 + 146 + `homeassistant/sensor/nightstand_1/rssi/config`: 147 + ```json 148 + { 149 + "name": "WiFi Signal", 150 + "unique_id": "nightstand_1_rssi", 151 + "state_topic": "nightstand/1/state", 152 + "value_template": "{{ value_json.rssi }}", 153 + "unit_of_measurement": "dBm", 154 + "device_class": "signal_strength", 155 + "entity_category": "diagnostic", 156 + "device": {"identifiers": ["nightstand_1"]}, 157 + "availability_topic": "nightstand/1/available" 158 + } 159 + ``` 160 + 161 + `homeassistant/sensor/nightstand_1/uptime/config`: 162 + ```json 163 + { 164 + "name": "Uptime", 165 + "unique_id": "nightstand_1_uptime", 166 + "state_topic": "nightstand/1/state", 167 + "value_template": "{{ value_json.uptime_s }}", 168 + "unit_of_measurement": "s", 169 + "device_class": "duration", 170 + "entity_category": "diagnostic", 171 + "device": {"identifiers": ["nightstand_1"]}, 172 + "availability_topic": "nightstand/1/available" 173 + } 174 + ``` 175 + 176 + ## State payload 177 + 178 + Published to `nightstand/1/state` (retained) on every state change: 179 + 180 + ```json 181 + { 182 + "playing": "ON", 183 + "volume": 65, 184 + "rssi": -58, 185 + "uptime_s": 12847 186 + } 187 + ``` 188 + 189 + Single JSON payload keeps discovery templates simple and lets HA parse any field out with `value_template`. 190 + 191 + ## Availability (LWT) 192 + 193 + Set at MQTT connect time: 194 + - **Last Will**: topic `nightstand/1/available`, payload `offline`, retained, QoS 1 195 + - On successful connect: publish `online` to the same topic, retained 196 + 197 + HA marks every entity unavailable within ~seconds of the device losing WiFi. 198 + 199 + ## Connection / boot sequence 200 + 201 + 1. WiFi up → MQTT connect (with LWT registered) 202 + 2. Publish retained `online` to `nightstand/1/available` 203 + 3. Publish retained discovery configs for every entity (cheap — broker dedupes retained messages) 204 + 4. Publish retained initial state snapshot to `nightstand/1/state` 205 + 5. Subscribe to `nightstand/1/cmd/+` 206 + 6. Enter main loop 207 + 208 + Republishing discovery every boot is fine — it's idempotent and makes entity config portable even after HA restores from backup or the broker loses retained state. 209 + 210 + ## Button behavior 211 + 212 + ### Firmware side 213 + 214 + Constants: 215 + - Debounce: 20ms 216 + - Double-press max gap (between first release and second press): **400ms** 217 + - Long-press threshold: **2000ms** 218 + 219 + State machine: 220 + 221 + ``` 222 + IDLE ──button down──► PRESSING 223 + PRESSING: 224 + released before 2s ──► WAITING_DOUBLE (note release time) 225 + held 2s ──► fire "long" ──► HOLD (wait for release) ──► IDLE 226 + WAITING_DOUBLE: 227 + button down in 400ms ──► DOUBLE_PRESSING 228 + 400ms elapsed ──► fire "short" ──► IDLE 229 + DOUBLE_PRESSING: 230 + released ──► fire "double" ──► IDLE 231 + ``` 232 + 233 + Notes: 234 + - **Short press has ~400ms of latency** after release (unavoidable cost of double-press detection). Imperceptible for sleepy-user use cases. 235 + - **Long press fires at the 2s mark**, not on release — crisper feedback. 236 + - **Double press fires on the second release** regardless of how long the second press is held. Simpler than distinguishing "double" from "double-then-long." 237 + - **Triple press** is treated as double-then-new-sequence — fine, users won't do this intentionally. 238 + - **Each interaction fires exactly one event**: either `short`, `double`, or `long`. A double-press does not also fire a `short` for its first press — the state machine transitions into `DOUBLE_PRESSING` before `WAITING_DOUBLE`'s 400ms timer can fire `short`. Offline mode: double-press cycles volume and does **not** toggle the white noise, even though a single short-press does toggle it. 239 + 240 + Firmware-local effects (regardless of online/offline): 241 + - `double` event → cycle volume to next preset, persist to NVS, publish new volume state to MQTT (if online) 242 + 243 + Online/offline-dependent behavior: 244 + - `short` → online: publish event; offline: toggle white noise locally 245 + - `long` → online: publish event; offline: no-op 246 + - `double` → online: publish event (informational); offline: just the volume cycle 247 + 248 + ### HA-side (example — not firmware's responsibility) 249 + 250 + Shown here so it's clear what use cases the button events are targeting. Chris owns the HA automation side; this is just illustrative. 251 + 252 + ```yaml 253 + - alias: "Nightstand: short press" 254 + trigger: 255 + - platform: mqtt 256 + topic: nightstand/+/button 257 + payload: '{"event_type":"short"}' 258 + action: 259 + - choose: 260 + - conditions: 261 + - condition: state 262 + entity_id: sun.sun 263 + state: below_horizon 264 + sequence: 265 + # Bedtime: toggle white noise on both, lights off 266 + - service: switch.toggle 267 + target: 268 + entity_id: 269 + - switch.nightstand_1_white_noise 270 + - switch.nightstand_2_white_noise 271 + - service: light.turn_off 272 + target: 273 + area_id: [bedroom, living_room, kitchen, hallway] 274 + - conditions: 275 + - condition: state 276 + entity_id: sun.sun 277 + state: above_horizon 278 + sequence: 279 + # Morning: lights on, white noise off 280 + - service: light.turn_on 281 + target: 282 + area_id: [bedroom, hallway] 283 + - service: switch.turn_off 284 + target: 285 + entity_id: 286 + - switch.nightstand_1_white_noise 287 + - switch.nightstand_2_white_noise 288 + 289 + - alias: "Nightstand: long press (late-night check)" 290 + trigger: 291 + - platform: mqtt 292 + topic: nightstand/+/button 293 + payload: '{"event_type":"long"}' 294 + condition: 295 + - condition: state 296 + entity_id: sun.sun 297 + state: below_horizon 298 + action: 299 + - service: light.turn_on 300 + target: 301 + area_id: [living_room, kitchen, outdoor, hallway] 302 + data: 303 + brightness_pct: 30 304 + ``` 305 + 306 + The device is blissfully unaware of any of this. 307 + 308 + ## Example flow: short press at night 309 + 310 + ``` 311 + ┌──────────────┐ ┌──────────┐ ┌──────────────┐ 312 + │ Nightstand 1 │ │ Mosquitto│ │ HA │ 313 + └──────┬───────┘ └────┬─────┘ └──────┬───────┘ 314 + │ (button pressed, │ │ 315 + │ released < 2s) │ │ 316 + │─ publish │ │ 317 + │ nightstand/1/button │ │ 318 + │ {"event_type":"short"} │ 319 + │─────────────────────► │ │ 320 + │ │─ deliver ─────────► │ 321 + │ │ │── automation 322 + │ │ │ triggers, 323 + │ │ │ sun is below 324 + │ │ │ horizon 325 + │ │◄── publish cmd/play │ 326 + │ │ "ON" to each unit│ 327 + │◄── "nightstand/1/ │ │ 328 + │ cmd/play" = "ON" │ │ 329 + │ │ │ 330 + │── start white noise │ │ 331 + │─ publish │ │ 332 + │ nightstand/1/state │ │ 333 + │ {"playing":"ON",...} │ │ 334 + │─────────────────────► │── deliver ─────────►│ 335 + │ │ (HA UI updates) 336 + ``` 337 + 338 + Total latency: tens of milliseconds on LAN. Feels instant. 339 + 340 + ## v1.5 planned extension: OTA via HA `update` entity 341 + 342 + Not in v1, but designed-in so we don't paint ourselves into a corner. Added when the units are enclosed and physical USB reflash becomes tedious. 343 + 344 + ### Mechanism 345 + 346 + - Chris builds firmware locally, copies `.bin` to HA's `/config/www/firmware/`, publishes a `latest_version` announcement to MQTT. 347 + - HA's MQTT `update` entity compares `installed_version` vs `latest_version` and shows an "Install" button on the device card. 348 + - User clicks Install → HA publishes to `nightstand/N/cmd/update` → firmware downloads from `http://homeassistant.local:8123/local/firmware/sound-machine-<ver>.bin` → `esp_ota_*` writes to the inactive partition → reboot into new firmware → device reports new `installed_version`. 349 + - ESP-IDF's OTA handles two-partition rollback automatically — a bootloop reverts to the previous good firmware. 350 + 351 + ### Additional entity 352 + 353 + `homeassistant/update/nightstand_1/firmware/config`: 354 + ```json 355 + { 356 + "name": "Firmware", 357 + "unique_id": "nightstand_1_firmware", 358 + "state_topic": "nightstand/1/update", 359 + "command_topic": "nightstand/1/cmd/update", 360 + "payload_install": "INSTALL", 361 + "latest_version_topic": "nightstand/1/update", 362 + "latest_version_template": "{{ value_json.latest_version }}", 363 + "value_template": "{{ value_json.installed_version }}", 364 + "release_url": "", 365 + "device": {"identifiers": ["nightstand_1"]}, 366 + "availability_topic": "nightstand/1/available" 367 + } 368 + ``` 369 + 370 + Update state topic payload (retained, written by device on boot and after install): 371 + ```json 372 + { 373 + "installed_version": "0.2.1", 374 + "latest_version": "0.3.0" 375 + } 376 + ``` 377 + 378 + Chris's release workflow (`make ota` or similar) publishes the `latest_version` field with retain=true; devices pick it up on next connect. 379 + 380 + ### Why one binary works for both units 381 + 382 + MAC-derived identity (see Device Identity section) means the same `sound-machine-v0.3.0.bin` runs correctly on both nightstands without per-unit builds. `mosquitto_pub -t nightstand/+/update ...` notifies both units of the new version with one command. 383 + 384 + ## What we're deliberately NOT including (v1) 385 + 386 + - **Noise type selection** (pink, brown, rain, etc.) — shipping with a single hand-tuned noise generator that Chris will iterate on to match what he and his wife actually want. Parameters live in source, not in MQTT; tuning = reflash, not a runtime knob. 387 + - **RGB LED control from HA** — the onboard SK6812 will be used by firmware for local status (idle / playing / WiFi down). No HA entity for it yet. 388 + - **Media player entity** — too much complexity for what is basically a toggle. Can revisit if we want HA TTS announcements on the device. 389 + - **Triple press patterns** — too much to remember. Single/double/long is the max. 390 + - **OTA updates** — designed in (see v1.5 section) but not built for v1. Bring-up with USB flashing; add OTA once enclosed. 391 + 392 + ## Sources 393 + 394 + - [MQTT Discovery docs (Home Assistant)][ha-discovery] 395 + - [MQTT Event entity docs][ha-event] 396 + - [MQTT Switch entity docs][ha-switch] 397 + - [MQTT Number entity docs][ha-number] 398 + - [MQTT Sensor entity docs][ha-sensor] 399 + - [Availability (LWT) patterns in HA MQTT integration][ha-availability] 400 + 401 + [ha-discovery]: https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery 402 + [ha-event]: https://www.home-assistant.io/integrations/event.mqtt/ 403 + [ha-switch]: https://www.home-assistant.io/integrations/switch.mqtt/ 404 + [ha-number]: https://www.home-assistant.io/integrations/number.mqtt/ 405 + [ha-sensor]: https://www.home-assistant.io/integrations/sensor.mqtt/ 406 + [ha-availability]: https://www.home-assistant.io/integrations/mqtt/#using-availability-topics
+181
reference/operating-modes.md
··· 1 + # Device operating modes and firmware behavior 2 + 3 + Companion to [mqtt-contract.md](./mqtt-contract.md). Where the contract defines the **wire protocol** between the device and HA, this doc defines the **device's own behavior** — what it does locally, independent of the protocol. 4 + 5 + ## Scope 6 + 7 + Covers: 8 + - Operating mode state machine (BOOT / ONLINE / OFFLINE) 9 + - Button behavior in each mode 10 + - LED status colors 11 + - Persistent state (what's saved in NVS, what resets) 12 + - Boot behavior and state restoration 13 + - Reconnection strategy 14 + 15 + Does not cover: the Rust implementation details (that's firmware code), the audio signal chain (see [signal-chain.md](./signal-chain.md)), or the MQTT wire format (see [mqtt-contract.md](./mqtt-contract.md)). 16 + 17 + ## Operating modes 18 + 19 + ``` 20 + power on 21 + 22 + 23 + ┌────────┐ 24 + │ BOOT │ Initializing peripherals, restoring NVS state, attempting WiFi 25 + └────┬───┘ 26 + 27 + │ WiFi ok + MQTT ok WiFi fails OR MQTT fails 28 + │ │ │ 29 + ▼ ▼ ▼ 30 + ┌──────────┐ ┌───────────┐ 31 + │ ONLINE │◄─── reconnect ──│ OFFLINE │ 32 + │ │ │ │ 33 + │ Button → │ │ Button → │ 34 + │ MQTT │─── drop ────────► local │ 35 + │ events │ │ effect │ 36 + └──────────┘ └───────────┘ 37 + ``` 38 + 39 + ### BOOT 40 + 41 + Entered on power-on or reset. Responsibilities: 42 + 1. Initialize I2S, GPIO, NVS, RGB LED 43 + 2. Read persistent state from NVS: `volume`, `was_playing` 44 + 3. Look up this chip's MAC in `KNOWN_DEVICES` table → logical identity 45 + 4. **If `was_playing == true`**: start white noise generator immediately at saved volume (power-blip recovery — don't wake the user with silence) 46 + 5. Attempt WiFi connect with 30s timeout against stored credentials 47 + 6. If WiFi connects, attempt MQTT connect with 10s timeout 48 + 7. Transition to ONLINE or OFFLINE based on outcome 49 + 50 + BOOT should complete to some steady mode within ~45 seconds worst case. 51 + 52 + ### ONLINE 53 + 54 + WiFi up, MQTT connected, discovery configs published, subscribed to command topics. 55 + 56 + - Button events publish to `nightstand/N/button` 57 + - Commands from `nightstand/N/cmd/+` are received and acted on 58 + - State changes publish to `nightstand/N/state` (retained) 59 + - Availability topic shows `online` 60 + 61 + If MQTT drops (broker down, network partition, etc.): transition to OFFLINE. 62 + 63 + ### OFFLINE 64 + 65 + WiFi or MQTT not available. Device keeps working locally. 66 + 67 + - Button events **not published** (there's nobody listening) 68 + - Short press toggles white noise locally 69 + - Long press: see "Button behavior" below 70 + - Double press cycles volume (unchanged from ONLINE) 71 + - No commands are received 72 + - Background task retries WiFi + MQTT every 60s; on success, transition to ONLINE 73 + 74 + Offline mode is the primary travel mode — when the device wakes up in a hotel room and doesn't see the home WiFi, it just works as a standalone white noise machine with a button. 75 + 76 + ## Button behavior matrix 77 + 78 + Full combined table (pairs with the state machine in `mqtt-contract.md`): 79 + 80 + | Input | ONLINE | OFFLINE | 81 + | --- | --- | --- | 82 + | Short press | Publish `{"event_type":"short"}` | Toggle white noise on/off | 83 + | Double press | Cycle volume preset (local) + publish `{"event_type":"double"}` | Cycle volume preset (local) | 84 + | Long press (≥2s) | Publish `{"event_type":"long"}` | **Fade out white noise over 3s**, then stop | 85 + 86 + Rationale for long-press-in-offline = fade out: travel-friendly. You pressed the button to start the noise; same button with a long hold stops it gently without a jarring silence. Parallels "pull the plug slowly." The fade duration (3s) is firmware-tunable. 87 + 88 + Volume cycle is consistent in both modes so the muscle memory doesn't change. 89 + 90 + ## LED status colors 91 + 92 + The SK6812 behind the button cap is the only status indicator. Goal: visible enough to read at 1m, dim enough to not light the room at night. 93 + 94 + All colors are at **dim brightness** (~5-10% of full) unless noted. 95 + 96 + | State | Color | Pattern | 97 + | --- | --- | --- | 98 + | BOOT (connecting WiFi) | Blue | Slow pulse (1 Hz) | 99 + | BOOT (connecting MQTT) | Cyan | Slow pulse (1 Hz) | 100 + | ONLINE, idle | Green | Solid, very dim | 101 + | ONLINE, playing | Green | Solid, medium-dim | 102 + | OFFLINE, idle | Amber | Solid, very dim | 103 + | OFFLINE, playing | Amber | Solid, medium-dim | 104 + | Error (I2S failed, OTA failed, etc.) | Red | Slow blink | 105 + | OTA in progress (v1.5) | Magenta | Slow pulse | 106 + | Button press ack (transient) | Flash brighter for ~100ms, then return to status color | — | 107 + 108 + The button-press flash is a nice tactile confirmation — press, see a brief brighter pulse, know it registered even in the dark. 109 + 110 + ## Persistent state (NVS) 111 + 112 + Stored in ESP32's NVS flash partition. Survives power loss, restarts, even OTA updates (separate partition from app binaries). 113 + 114 + | Key | Type | Purpose | Written when | 115 + | --- | --- | --- | --- | 116 + | `volume` | u8 | Current volume 0–100 | Changed via MQTT cmd or double-press | 117 + | `was_playing` | bool | Whether white noise was playing at last state change | Every play/stop transition | 118 + | `wifi_ssid` | string | Known WiFi SSID | Provisioning (first flash or later update) | 119 + | `wifi_password` | string | Known WiFi password | Provisioning | 120 + 121 + Not stored (computed / volatile): 122 + - Logical name (derived from MAC) 123 + - Connection state 124 + - RSSI, uptime 125 + 126 + **Multi-SSID support**: `wifi_ssid` and `wifi_password` are actually a list of up to 3 networks (home + travel router + backup). Firmware tries them in order during BOOT's WiFi connect. A single pair is fine for now; extending to a list is a non-breaking NVS schema bump. 127 + 128 + ## Boot-time state restoration 129 + 130 + Design decision: **if the device was playing white noise before losing power, it resumes automatically on boot.** 131 + 132 + Rationale: 133 + - Power blips happen. If the device came back silent at 3 AM, the user wakes up. 134 + - If the user deliberately turned it off (via button or HA), `was_playing` is already `false` in NVS, so it stays off. 135 + - The check is `was_playing` (what was the last state I was asked to be in?), not "am I currently playing" (obviously no, I just booted). 136 + 137 + Edge case: if WiFi/MQTT never connect and `was_playing` was true, the device plays offline from the start. Correct behavior. 138 + 139 + ## Reconnection strategy 140 + 141 + When OFFLINE (either never connected or dropped from ONLINE): 142 + 143 + - Retry WiFi every **60 seconds** 144 + - If WiFi connects, retry MQTT within 10s 145 + - On MQTT success, transition to ONLINE: 146 + - Re-publish retained discovery configs (idempotent, covers HA restart during our offline window) 147 + - Re-publish retained `online` to availability topic 148 + - Re-publish retained state snapshot to `nightstand/N/state` 149 + - Re-subscribe to command topics 150 + - On MQTT fail, stay OFFLINE; try again in 60s 151 + 152 + No exponential backoff — device is wall-powered, we don't care about battery life, and 60s is a reasonable balance between "react to the network coming back" and "not spam the broker during multi-hour outages." 153 + 154 + ## Error handling 155 + 156 + | Error | Behavior | 157 + | --- | --- | 158 + | I2S driver init fails | Red blink, no audio. Stay in whatever connection mode works. Log loudly. | 159 + | NVS read fails | Use defaults (volume=50, was_playing=false). Log. | 160 + | NVS write fails | Log, keep running. State won't persist across reboot but that's a graceful degradation. | 161 + | WiFi password wrong | Stay OFFLINE forever until updated. No good recovery. | 162 + | MQTT broker unreachable | Stay OFFLINE, retry per strategy above. | 163 + | OTA download fails (v1.5) | Keep running current firmware. Log. Report failure via MQTT. | 164 + | OTA bootloop (v1.5) | ESP-IDF's two-partition system auto-reverts to previous firmware. User sees the device come back on old version; MQTT state reflects it. | 165 + 166 + ## What's not in this doc 167 + 168 + - **WiFi provisioning mechanism** — for v1, credentials are hardcoded-per-flash (via `cfg.toml` or similar). SoftAP/BLE/Improv provisioning is a v2 concern if we want it. 169 + - **Audio generation parameters** — the actual noise generator's filter shape, amplitude, etc. live in source and are tuned over time. Chris will iterate on these with his wife's input once hardware is assembled. 170 + - **OTA implementation** — designed in MQTT contract for v1.5; firmware implementation TBD. 171 + 172 + ## Sources 173 + 174 + - [MQTT Contract](./mqtt-contract.md) — companion doc; wire protocol 175 + - [Signal chain](./signal-chain.md) — hardware audio path 176 + - [Atom Echo pinmap](./atom-echo/pinmap.md) — GPIO usage 177 + - [ESP-IDF NVS documentation][esp-idf-nvs] 178 + - [ESP-IDF OTA documentation][esp-idf-ota] — v1.5 reference 179 + 180 + [esp-idf-nvs]: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/storage/nvs_flash.html 181 + [esp-idf-ota]: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/ota.html
+133
reference/signal-chain.md
··· 1 + # Audio signal chain 2 + 3 + ## Why we chose an I2S amp 4 + 5 + The ESP32 on the Atom Echo outputs **digital I2S** — a three-wire serial protocol carrying PCM samples as ones and zeros. No raw speaker or analog amp can play I2S; it has to be decoded first. 6 + 7 + Two architectures were on the table: 8 + 9 + | Path | Chain | Parts | 10 + | ---- | ----- | ----- | 11 + | **I2S amp (chosen)** | ESP32 → I2S amp → speaker | MAX98357A | 12 + | Analog amp | ESP32 → I2S DAC → analog amp → speaker | PCM5102A + PAM8302 | 13 + 14 + Both work. The I2S-amp path is shorter, uses one fewer board, has fewer wires, less noise surface, smaller enclosure footprint, and simpler ESPHome config. We're going with it. 15 + 16 + The onboard NS4168 on the Atom Echo is itself an I2S amp — the MAX98357A is in the same category, just better-suited because we can put it outside the thermally-constrained Atom Echo case and drive a properly-sized speaker. 17 + 18 + ## Full signal chain 19 + 20 + ``` 21 + Atom Echo (ESP32-PICO-D4) 22 + 23 + │ digital I2S: BCLK + LRCK + DOUT 24 + │ (on unused GPIOs — G21, G26, G32) 25 + 26 + MAX98357A 27 + (I2S in, Class D amp out) 28 + 29 + │ bridge-tied speaker output (V+ / V−) 30 + │ no common ground to the speaker 31 + 32 + Adafruit 1314 (4Ω 3W) 33 + ``` 34 + 35 + ## MAX98357A specs 36 + 37 + - **Input**: I2S (BCLK + LRCK + DIN) 38 + - **Output**: bridge-tied load, 3.2 W into 4Ω at 5V supply (matches our driver exactly) 39 + - **Gain**: configurable via GAIN pin — 3 / 6 / 9 / 12 / 15 dB. Floating = 9 dB default, which is fine for our volumes. 40 + - **Channel select (SD pin)**: 41 + - Float = both L+R channels summed (mono mix) — our default 42 + - Tied low = shutdown 43 + - Tied with specific voltage dividers = left-only or right-only 44 + - **Supply**: 2.5–5.5 V (we use 5 V) 45 + - **Package**: on a ~21×16 mm breakout board (Adafruit 3006 variant; generic Chinese boards are similar size) 46 + 47 + ## Wiring detail 48 + 49 + ### Atom Echo → MAX98357A 50 + 51 + | Atom Echo pin | MAX98357A pin | Notes | 52 + | ------------------- | ------------- | ----- | 53 + | 5V | VIN | side header or GROVE red | 54 + | GND | GND | | 55 + | G26 (GROVE yellow) | BCLK | bit clock | 56 + | G32 (GROVE white) | LRC | word select (LRCK) | 57 + | G21 (side header) | DIN | I2S serial data | 58 + | — | SD | leave floating = mono (L+R summed) | 59 + | — | GAIN | leave floating = 9 dB default | 60 + 61 + ### MAX98357A → speaker 62 + 63 + | MAX98357A | Speaker | 64 + | --------- | ------- | 65 + | OUT+ | speaker + | 66 + | OUT− | speaker − | 67 + 68 + **Bridge-tied load — don't ground either speaker terminal.** Both speaker wires go to the amp, nowhere else. 69 + 70 + ### Power 71 + 72 + - Atom Echo powered via USB-C from a 5V 2A UL-listed wall adapter 73 + - MAX98357A taps 5V + GND from the Atom Echo's side header 74 + - Total estimated draw: Atom Echo ~0.3 A + MAX98357A at typical volume ~0.2 A ≈ 0.5 A. Well within a 2 A supply. 75 + 76 + ## Volume control 77 + 78 + The MAX98357A has no onboard potentiometer — gain is fixed by the GAIN pin configuration. Volume comes from two places: 79 + 80 + 1. **GAIN pin** — set once at build time. Floating (9 dB) is the default and fine. If we find it's too loud at full software volume, pull GAIN to ground through a 100 kΩ resistor for 3 dB, or to VIN for 15 dB. We'll tune if needed. 81 + 2. **ESPHome software volume** (via HA) — fine-grained runtime control. 82 + 83 + ## ESPHome config sketch 84 + 85 + ```yaml 86 + i2s_audio: 87 + - id: i2sout 88 + i2s_lrclk_pin: GPIO32 89 + i2s_bclk_pin: GPIO26 90 + 91 + speaker: 92 + - platform: i2s_audio 93 + id: echo_speaker 94 + i2s_dout_pin: GPIO21 95 + dac_type: external 96 + mode: mono 97 + ``` 98 + 99 + Critically, we do **not** configure the `speaker:` block against the onboard NS4168 pins (G19/G22/G33). That leaves the onboard amp receiving no I2S data — idle, silent, cool. 100 + 101 + ## Pin availability recap 102 + 103 + Free GPIOs on the Atom Echo after accounting for mic / amp / LED / button: 104 + 105 + - G26 (GROVE yellow) → I2S BCLK 106 + - G32 (GROVE white) → I2S LRC 107 + - G21 (side header) → I2S DIN 108 + - G25 (side header) → free spare (future button, rotary encoder, IR, etc.) 109 + 110 + ## Parts list for this chain (per unit) 111 + 112 + | Part | Source | Notes | 113 + | ---- | ------ | ----- | 114 + | M5Stack Atom Echo | owned | | 115 + | MAX98357A breakout | [Adafruit 3006][adafruit-3006] (~$6) or generic Amazon/AliExpress (~$3) | need 2 total for both units | 116 + | Adafruit 1314 speaker | owned | | 117 + | 5V 2A USB wall adapter + USB-C cable | off-the-shelf | UL-listed brick | 118 + | Dupont wires / hookup wire | stock | | 119 + 120 + ## Sources 121 + 122 + - [MAX98357A datasheet (Analog Devices / Maxim)][max98357-ds] 123 + - [Adafruit MAX98357 I2S Class-D Mono Amp (product page + guide)][adafruit-3006] 124 + - [ESPHome I²S Audio Speaker component][esphome-i2s] 125 + - [SparkFun I2S Audio Breakout (MAX98357A)][sparkfun-14809] 126 + - [M5Stack Atom Echo docs][m5-docs] — pin map 127 + - [Internal speaker disable notes](./atom-echo/disabling-internal-speaker.md) 128 + 129 + [max98357-ds]: https://www.analog.com/media/en/technical-documentation/data-sheets/MAX98357A-MAX98357B.pdf 130 + [adafruit-3006]: https://www.adafruit.com/product/3006 131 + [esphome-i2s]: https://esphome.io/components/speaker/i2s_audio/ 132 + [sparkfun-14809]: https://www.sparkfun.com/products/14809 133 + [m5-docs]: https://docs.m5stack.com/en/atom/atomecho
+48
reference/speakers/adafruit-1314.md
··· 1 + # Adafruit 1314 — 3" speaker, 4Ω 3W 2 + 3 + Full-range driver, generic Chinese OEM, sold by Adafruit as ["Speaker - 3" Diameter - 4 Ohm 3 Watt"][adafruit-1314]. Chris already has two. 4 + 5 + ## Published specs 6 + 7 + | Spec | Value | 8 + | ----------- | ----- | 9 + | Impedance | 4 Ω | 10 + | Max power | 3 W | 11 + | Cone diameter | 3" (76 mm nominal) | 12 + | Frame (L×W×D) | 77.8 × 77.8 × 25.49 mm | 13 + | Weight | 50.48 g | 14 + | Mounting | 4 tabs, 60 mm apart | 15 + 16 + ## Unknown / to measure from physical unit 17 + 18 + These aren't in the Adafruit listing and the Verical-hosted datasheet was unreachable at the time of this research. We'll measure when we have a unit on the bench: 19 + 20 + - **Cone cutout diameter** (baffle hole) — estimate ~65–70 mm based on standard 3" driver dimensions 21 + - **Mounting hole diameter** (what screw fits the tabs) — likely M3 or similar 22 + - **Magnet diameter and depth** — sets rear clearance needed inside the enclosure 23 + - **Basket depth above / below the mounting flange** 24 + - **Frequency response** — not published; irrelevant for white noise but would matter if we wanted music quality 25 + - **Thiele-Small parameters** (Fs, Qts, Vas) — not published; fine, we're using a sealed box and not tuning 26 + 27 + ## Why this is fine for our use 28 + 29 + - **Power match**: 3 W handling vs. PAM8302 delivering ~2.5 W into 4 Ω at 5V. Comfortable headroom; we'll never run the amp at max anyway. 30 + - **Impedance match**: PAM8302 is rated for 4Ω loads. Exact match. 31 + - **Size match**: 78 mm baffle + a few mm per side for the enclosure wall gives a face ~90 mm square. Reasonable nightstand footprint. 32 + - **White noise doesn't care about bass extension**, so a sealed box with any sensible internal volume (~0.3 L+) will sound fine. Fill with polyfill / pillow stuffing to damp standing waves. 33 + 34 + ## Enclosure design notes 35 + 36 + - Baffle opening will need to be cut to match the cone cutout (TBD from measurement). 37 + - Four M3 (assumed) mounting holes in a 60 mm square pattern on the inside of the baffle. 38 + - Magnet clearance at the rear: leave at least 30 mm behind the mounting flange until we measure. 39 + - The 25.49 mm total depth means the whole driver (including magnet) fits within ~26 mm of baffle depth. 40 + - Seal the baffle-to-frame joint (gasket, foam, or printed lip) to avoid air leaks that kill bass coupling. 41 + 42 + ## Sources 43 + 44 + - [Adafruit product page for 1314][adafruit-1314] 45 + - [Verical datasheet link (currently unreachable)][verical-1314] — referenced here for future attempts 46 + 47 + [adafruit-1314]: https://www.adafruit.com/product/1314 48 + [verical-1314]: https://www.verical.com/datasheet/adafruit-speakers-1314-5770604.pdf