├── .cargo └── config.toml ├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── README.md ├── build.rs ├── components └── bindings.h ├── components_esp32.lock ├── examples ├── idotmatrix │ ├── idotmatrixble.rs │ └── main.rs ├── telegram_bot │ ├── bot_api.rs │ └── main.rs └── webserver.rs ├── rust-toolchain.toml ├── sdkconfig.defaults └── src ├── ble.rs ├── config.rs ├── espcam.rs ├── lib.rs ├── main.rs ├── wifi_config.rs.example └── wifi_handler.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "xtensa-esp32-espidf" 3 | 4 | [target.xtensa-esp32-espidf] 5 | linker = "ldproxy" 6 | # runner = "espflash --monitor" # Select this runner for espflash v1.x.x 7 | runner = "espflash flash --baud=921600 --monitor" # Select this runner for espflash v2.x.x 8 | rustflags = [ "--cfg", "espidf_time64"] # Extending time_t for ESP IDF 5: https://github.com/esp-rs/rust/issues/110 9 | 10 | [unstable] 11 | build-std = ["std", "panic_abort"] 12 | 13 | [env] 14 | MCU="esp32" 15 | # Note: this variable is not used by the pio builder (`cargo build --features pio`) 16 | ESP_IDF_VERSION = "v5.1.1" 17 | 18 | CARGO_WORKSPACE_DIR = { value = "", relative = true } -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [ "master" ] 5 | pull_request: 6 | branches: [ "master" ] 7 | 8 | jobs: 9 | check: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | submodules: 'true' 15 | - name: Install Rust for Xtensa 16 | uses: esp-rs/xtensa-toolchain@v1.5.2 17 | with: 18 | default: true 19 | ldproxy: true 20 | - name: copy config 21 | run: cp src/wifi_config.rs.example src/wifi_config.rs 22 | - name: Run cargo check 23 | run: cargo check --bins --examples 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.vscode 2 | /.embuild 3 | /target 4 | 5 | cfg.toml -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "components/esp32-camera"] 2 | path = components/esp32-camera 3 | url = https://github.com/espressif/esp32-camera.git 4 | -------------------------------------------------------------------------------- /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 = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "aligned" 31 | version = "0.4.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923" 34 | dependencies = [ 35 | "as-slice", 36 | ] 37 | 38 | [[package]] 39 | name = "android-tzdata" 40 | version = "0.1.1" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 43 | 44 | [[package]] 45 | name = "android_system_properties" 46 | version = "0.1.5" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" 49 | dependencies = [ 50 | "libc", 51 | ] 52 | 53 | [[package]] 54 | name = "anyhow" 55 | version = "1.0.94" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" 58 | 59 | [[package]] 60 | name = "as-slice" 61 | version = "0.2.1" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" 64 | dependencies = [ 65 | "stable_deref_trait", 66 | ] 67 | 68 | [[package]] 69 | name = "atomic-waker" 70 | version = "1.1.2" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 73 | 74 | [[package]] 75 | name = "autocfg" 76 | version = "1.4.0" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 79 | 80 | [[package]] 81 | name = "backtrace" 82 | version = "0.3.74" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 85 | dependencies = [ 86 | "addr2line", 87 | "cfg-if", 88 | "libc", 89 | "miniz_oxide", 90 | "object", 91 | "rustc-demangle", 92 | "windows-targets", 93 | ] 94 | 95 | [[package]] 96 | name = "base64" 97 | version = "0.22.1" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 100 | 101 | [[package]] 102 | name = "bindgen" 103 | version = "0.69.5" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" 106 | dependencies = [ 107 | "bitflags 2.6.0", 108 | "cexpr", 109 | "clang-sys", 110 | "itertools", 111 | "lazy_static", 112 | "lazycell", 113 | "log", 114 | "prettyplease", 115 | "proc-macro2", 116 | "quote", 117 | "regex", 118 | "rustc-hash", 119 | "shlex", 120 | "syn 2.0.90", 121 | "which", 122 | ] 123 | 124 | [[package]] 125 | name = "bitflags" 126 | version = "1.3.2" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 129 | 130 | [[package]] 131 | name = "bitflags" 132 | version = "2.6.0" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 135 | 136 | [[package]] 137 | name = "bon" 138 | version = "3.3.0" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "f265cdb2e8501f1c952749e78babe8f1937be92c98120e5f78fc72d634682bad" 141 | dependencies = [ 142 | "bon-macros", 143 | "rustversion", 144 | ] 145 | 146 | [[package]] 147 | name = "bon-macros" 148 | version = "3.3.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "38aa5c627cd7706490e5b003d685f8b9d69bc343b1a00b9fdd01e75fdf6827cf" 151 | dependencies = [ 152 | "darling", 153 | "ident_case", 154 | "prettyplease", 155 | "proc-macro2", 156 | "quote", 157 | "rustversion", 158 | "syn 2.0.90", 159 | ] 160 | 161 | [[package]] 162 | name = "bstr" 163 | version = "1.11.0" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" 166 | dependencies = [ 167 | "memchr", 168 | "serde", 169 | ] 170 | 171 | [[package]] 172 | name = "build-time" 173 | version = "0.1.3" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "f1219c19fc29b7bfd74b7968b420aff5bc951cf517800176e795d6b2300dd382" 176 | dependencies = [ 177 | "chrono", 178 | "once_cell", 179 | "proc-macro2", 180 | "quote", 181 | "syn 2.0.90", 182 | ] 183 | 184 | [[package]] 185 | name = "bumpalo" 186 | version = "3.16.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 189 | 190 | [[package]] 191 | name = "bytemuck" 192 | version = "1.20.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" 195 | 196 | [[package]] 197 | name = "byteorder" 198 | version = "1.5.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 201 | 202 | [[package]] 203 | name = "byteorder-lite" 204 | version = "0.1.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" 207 | 208 | [[package]] 209 | name = "camino" 210 | version = "1.1.9" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" 213 | dependencies = [ 214 | "serde", 215 | ] 216 | 217 | [[package]] 218 | name = "cargo-platform" 219 | version = "0.1.9" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" 222 | dependencies = [ 223 | "serde", 224 | ] 225 | 226 | [[package]] 227 | name = "cargo_metadata" 228 | version = "0.18.1" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" 231 | dependencies = [ 232 | "camino", 233 | "cargo-platform", 234 | "semver", 235 | "serde", 236 | "serde_json", 237 | "thiserror 1.0.69", 238 | ] 239 | 240 | [[package]] 241 | name = "cargo_toml" 242 | version = "0.15.3" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" 245 | dependencies = [ 246 | "serde", 247 | "toml", 248 | ] 249 | 250 | [[package]] 251 | name = "cc" 252 | version = "1.2.3" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" 255 | dependencies = [ 256 | "shlex", 257 | ] 258 | 259 | [[package]] 260 | name = "cexpr" 261 | version = "0.6.0" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 264 | dependencies = [ 265 | "nom", 266 | ] 267 | 268 | [[package]] 269 | name = "cfg-if" 270 | version = "1.0.0" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 273 | 274 | [[package]] 275 | name = "cfg_aliases" 276 | version = "0.2.1" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" 279 | 280 | [[package]] 281 | name = "chrono" 282 | version = "0.4.39" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" 285 | dependencies = [ 286 | "android-tzdata", 287 | "iana-time-zone", 288 | "num-traits", 289 | "serde", 290 | "windows-targets", 291 | ] 292 | 293 | [[package]] 294 | name = "clang-sys" 295 | version = "1.8.1" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 298 | dependencies = [ 299 | "glob", 300 | "libc", 301 | "libloading", 302 | ] 303 | 304 | [[package]] 305 | name = "cmake" 306 | version = "0.1.52" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" 309 | dependencies = [ 310 | "cc", 311 | ] 312 | 313 | [[package]] 314 | name = "const_format" 315 | version = "0.2.34" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" 318 | dependencies = [ 319 | "const_format_proc_macros", 320 | ] 321 | 322 | [[package]] 323 | name = "const_format_proc_macros" 324 | version = "0.2.34" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" 327 | dependencies = [ 328 | "proc-macro2", 329 | "quote", 330 | "unicode-xid", 331 | ] 332 | 333 | [[package]] 334 | name = "core-foundation-sys" 335 | version = "0.8.7" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 338 | 339 | [[package]] 340 | name = "crc32fast" 341 | version = "1.4.2" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" 344 | dependencies = [ 345 | "cfg-if", 346 | ] 347 | 348 | [[package]] 349 | name = "critical-section" 350 | version = "1.2.0" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" 353 | 354 | [[package]] 355 | name = "crossbeam-deque" 356 | version = "0.8.5" 357 | source = "registry+https://github.com/rust-lang/crates.io-index" 358 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 359 | dependencies = [ 360 | "crossbeam-epoch", 361 | "crossbeam-utils", 362 | ] 363 | 364 | [[package]] 365 | name = "crossbeam-epoch" 366 | version = "0.9.18" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 369 | dependencies = [ 370 | "crossbeam-utils", 371 | ] 372 | 373 | [[package]] 374 | name = "crossbeam-utils" 375 | version = "0.8.20" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 378 | 379 | [[package]] 380 | name = "cvt" 381 | version = "0.1.2" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "d2ae9bf77fbf2d39ef573205d554d87e86c12f1994e9ea335b0651b9b278bcf1" 384 | dependencies = [ 385 | "cfg-if", 386 | ] 387 | 388 | [[package]] 389 | name = "darling" 390 | version = "0.20.10" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" 393 | dependencies = [ 394 | "darling_core", 395 | "darling_macro", 396 | ] 397 | 398 | [[package]] 399 | name = "darling_core" 400 | version = "0.20.10" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" 403 | dependencies = [ 404 | "fnv", 405 | "ident_case", 406 | "proc-macro2", 407 | "quote", 408 | "strsim", 409 | "syn 2.0.90", 410 | ] 411 | 412 | [[package]] 413 | name = "darling_macro" 414 | version = "0.20.10" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" 417 | dependencies = [ 418 | "darling_core", 419 | "quote", 420 | "syn 2.0.90", 421 | ] 422 | 423 | [[package]] 424 | name = "defmt" 425 | version = "0.3.10" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "86f6162c53f659f65d00619fe31f14556a6e9f8752ccc4a41bd177ffcf3d6130" 428 | dependencies = [ 429 | "bitflags 1.3.2", 430 | "defmt-macros", 431 | ] 432 | 433 | [[package]] 434 | name = "defmt-macros" 435 | version = "0.4.0" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "9d135dd939bad62d7490b0002602d35b358dce5fd9233a709d3c1ef467d4bde6" 438 | dependencies = [ 439 | "defmt-parser", 440 | "proc-macro-error2", 441 | "proc-macro2", 442 | "quote", 443 | "syn 2.0.90", 444 | ] 445 | 446 | [[package]] 447 | name = "defmt-parser" 448 | version = "0.4.1" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "3983b127f13995e68c1e29071e5d115cd96f215ccb5e6812e3728cd6f92653b3" 451 | dependencies = [ 452 | "thiserror 2.0.6", 453 | ] 454 | 455 | [[package]] 456 | name = "deranged" 457 | version = "0.3.11" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" 460 | dependencies = [ 461 | "powerfmt", 462 | "serde", 463 | ] 464 | 465 | [[package]] 466 | name = "displaydoc" 467 | version = "0.2.5" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 470 | dependencies = [ 471 | "proc-macro2", 472 | "quote", 473 | "syn 2.0.90", 474 | ] 475 | 476 | [[package]] 477 | name = "document-features" 478 | version = "0.2.10" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "cb6969eaabd2421f8a2775cfd2471a2b634372b4a25d41e3bd647b79912850a0" 481 | dependencies = [ 482 | "litrs", 483 | ] 484 | 485 | [[package]] 486 | name = "either" 487 | version = "1.13.0" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 490 | 491 | [[package]] 492 | name = "embassy-futures" 493 | version = "0.1.1" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" 496 | 497 | [[package]] 498 | name = "embassy-sync" 499 | version = "0.6.1" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "3899a6e39fa3f54bf8aaf00979f9f9c0145a522f7244810533abbb748be6ce82" 502 | dependencies = [ 503 | "cfg-if", 504 | "critical-section", 505 | "embedded-io-async", 506 | "futures-sink", 507 | "futures-util", 508 | "heapless", 509 | ] 510 | 511 | [[package]] 512 | name = "embassy-time-driver" 513 | version = "0.1.0" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "6e0c214077aaa9206958b16411c157961fb7990d4ea628120a78d1a5a28aed24" 516 | dependencies = [ 517 | "document-features", 518 | ] 519 | 520 | [[package]] 521 | name = "embedded-can" 522 | version = "0.4.1" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "e9d2e857f87ac832df68fa498d18ddc679175cf3d2e4aa893988e5601baf9438" 525 | dependencies = [ 526 | "nb 1.1.0", 527 | ] 528 | 529 | [[package]] 530 | name = "embedded-hal" 531 | version = "0.2.7" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" 534 | dependencies = [ 535 | "nb 0.1.3", 536 | "void", 537 | ] 538 | 539 | [[package]] 540 | name = "embedded-hal" 541 | version = "1.0.0" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" 544 | 545 | [[package]] 546 | name = "embedded-hal-async" 547 | version = "1.0.0" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" 550 | dependencies = [ 551 | "embedded-hal 1.0.0", 552 | ] 553 | 554 | [[package]] 555 | name = "embedded-hal-nb" 556 | version = "1.0.0" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" 559 | dependencies = [ 560 | "embedded-hal 1.0.0", 561 | "nb 1.1.0", 562 | ] 563 | 564 | [[package]] 565 | name = "embedded-io" 566 | version = "0.6.1" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" 569 | 570 | [[package]] 571 | name = "embedded-io-async" 572 | version = "0.6.1" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" 575 | dependencies = [ 576 | "embedded-io", 577 | ] 578 | 579 | [[package]] 580 | name = "embedded-svc" 581 | version = "0.28.0" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "7b85590a7a120e370bed5b6bc2b399b345c60d4c749d3bc06039472da37b9893" 584 | dependencies = [ 585 | "defmt", 586 | "embedded-io", 587 | "embedded-io-async", 588 | "enumset", 589 | "heapless", 590 | "log", 591 | "num_enum", 592 | "serde", 593 | "strum 0.25.0", 594 | "strum_macros 0.25.3", 595 | ] 596 | 597 | [[package]] 598 | name = "embuild" 599 | version = "0.32.0" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "a6e3e470e31fd4cae065d37f7cad56d42861ba1f9a35aa277694dee3d6b357c4" 602 | dependencies = [ 603 | "anyhow", 604 | "bindgen", 605 | "bitflags 1.3.2", 606 | "cargo_toml", 607 | "cmake", 608 | "filetime", 609 | "globwalk", 610 | "home", 611 | "log", 612 | "regex", 613 | "remove_dir_all", 614 | "serde", 615 | "serde_json", 616 | "shlex", 617 | "strum 0.24.1", 618 | "tempfile", 619 | "thiserror 1.0.69", 620 | "toml", 621 | "ureq", 622 | "which", 623 | ] 624 | 625 | [[package]] 626 | name = "enumset" 627 | version = "1.1.5" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "d07a4b049558765cef5f0c1a273c3fc57084d768b44d2f98127aef4cceb17293" 630 | dependencies = [ 631 | "enumset_derive", 632 | "serde", 633 | ] 634 | 635 | [[package]] 636 | name = "enumset_derive" 637 | version = "0.10.0" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "59c3b24c345d8c314966bdc1832f6c2635bfcce8e7cf363bd115987bba2ee242" 640 | dependencies = [ 641 | "darling", 642 | "proc-macro2", 643 | "quote", 644 | "syn 2.0.90", 645 | ] 646 | 647 | [[package]] 648 | name = "envy" 649 | version = "0.4.2" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" 652 | dependencies = [ 653 | "serde", 654 | ] 655 | 656 | [[package]] 657 | name = "equivalent" 658 | version = "1.0.1" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 661 | 662 | [[package]] 663 | name = "errno" 664 | version = "0.3.10" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 667 | dependencies = [ 668 | "libc", 669 | "windows-sys 0.59.0", 670 | ] 671 | 672 | [[package]] 673 | name = "esp-idf-hal" 674 | version = "0.44.1" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "bfa893ab84c4a7db5ca42ab45e2e09942412976fe3100a9dd72e56ba0a9a58b4" 677 | dependencies = [ 678 | "atomic-waker", 679 | "critical-section", 680 | "embassy-sync", 681 | "embedded-can", 682 | "embedded-hal 0.2.7", 683 | "embedded-hal 1.0.0", 684 | "embedded-hal-async", 685 | "embedded-hal-nb", 686 | "embedded-io", 687 | "embedded-io-async", 688 | "embuild", 689 | "enumset", 690 | "esp-idf-sys", 691 | "heapless", 692 | "log", 693 | "nb 1.1.0", 694 | "num_enum", 695 | ] 696 | 697 | [[package]] 698 | name = "esp-idf-svc" 699 | version = "0.49.1" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "ac42f9303792348e3217c570b0f0d8280a381d053bcb730c3018ec6873928513" 702 | dependencies = [ 703 | "embassy-futures", 704 | "embassy-time-driver", 705 | "embedded-hal-async", 706 | "embedded-svc", 707 | "embuild", 708 | "enumset", 709 | "esp-idf-hal", 710 | "heapless", 711 | "log", 712 | "num_enum", 713 | "uncased", 714 | ] 715 | 716 | [[package]] 717 | name = "esp-idf-sys" 718 | version = "0.35.0" 719 | source = "registry+https://github.com/rust-lang/crates.io-index" 720 | checksum = "bb97e3800686a4d64f3c0a9998be3d6f16c903bca2a425746e97f00ed28cde5e" 721 | dependencies = [ 722 | "anyhow", 723 | "bindgen", 724 | "build-time", 725 | "cargo_metadata", 726 | "const_format", 727 | "embuild", 728 | "envy", 729 | "libc", 730 | "regex", 731 | "serde", 732 | "strum 0.24.1", 733 | "which", 734 | ] 735 | 736 | [[package]] 737 | name = "esp32-nimble" 738 | version = "0.8.2" 739 | source = "registry+https://github.com/rust-lang/crates.io-index" 740 | checksum = "53ee06613867453660128b3d772c31a5d38b02c4fa66952d29022b3f35bdd920" 741 | dependencies = [ 742 | "anyhow", 743 | "bitflags 2.6.0", 744 | "bstr", 745 | "cfg-if", 746 | "embassy-sync", 747 | "embuild", 748 | "esp-idf-svc", 749 | "heapless", 750 | "log", 751 | "num_enum", 752 | "once_cell", 753 | "uuid", 754 | ] 755 | 756 | [[package]] 757 | name = "espcam" 758 | version = "0.1.0" 759 | dependencies = [ 760 | "anyhow", 761 | "bstr", 762 | "embedded-svc", 763 | "embuild", 764 | "esp-idf-hal", 765 | "esp-idf-svc", 766 | "esp-idf-sys", 767 | "esp32-nimble", 768 | "frankenstein", 769 | "idotmatrix", 770 | "image", 771 | "lazy_static", 772 | "log", 773 | "rgb565", 774 | "serde", 775 | "serde_json", 776 | "thiserror 2.0.6", 777 | "tokio", 778 | "uuid", 779 | ] 780 | 781 | [[package]] 782 | name = "fastrand" 783 | version = "2.3.0" 784 | source = "registry+https://github.com/rust-lang/crates.io-index" 785 | checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" 786 | 787 | [[package]] 788 | name = "fdeflate" 789 | version = "0.3.7" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" 792 | dependencies = [ 793 | "simd-adler32", 794 | ] 795 | 796 | [[package]] 797 | name = "filetime" 798 | version = "0.2.25" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" 801 | dependencies = [ 802 | "cfg-if", 803 | "libc", 804 | "libredox", 805 | "windows-sys 0.59.0", 806 | ] 807 | 808 | [[package]] 809 | name = "flate2" 810 | version = "1.0.35" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" 813 | dependencies = [ 814 | "crc32fast", 815 | "miniz_oxide", 816 | ] 817 | 818 | [[package]] 819 | name = "fnv" 820 | version = "1.0.7" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 823 | 824 | [[package]] 825 | name = "form_urlencoded" 826 | version = "1.2.1" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 829 | dependencies = [ 830 | "percent-encoding", 831 | ] 832 | 833 | [[package]] 834 | name = "frankenstein" 835 | version = "0.37.0" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "625fe4cc6dbfc88fe05ccc0cd03611d8db0e7a84936d5bdb4c8e7541817b3804" 838 | dependencies = [ 839 | "bon", 840 | "macro_rules_attribute", 841 | "paste", 842 | "serde", 843 | "serde_with", 844 | "thiserror 2.0.6", 845 | ] 846 | 847 | [[package]] 848 | name = "fs_at" 849 | version = "0.2.1" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "14af6c9694ea25db25baa2a1788703b9e7c6648dcaeeebeb98f7561b5384c036" 852 | dependencies = [ 853 | "aligned", 854 | "cfg-if", 855 | "cvt", 856 | "libc", 857 | "nix", 858 | "windows-sys 0.52.0", 859 | ] 860 | 861 | [[package]] 862 | name = "futures-core" 863 | version = "0.3.31" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 866 | 867 | [[package]] 868 | name = "futures-sink" 869 | version = "0.3.31" 870 | source = "registry+https://github.com/rust-lang/crates.io-index" 871 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 872 | 873 | [[package]] 874 | name = "futures-task" 875 | version = "0.3.31" 876 | source = "registry+https://github.com/rust-lang/crates.io-index" 877 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 878 | 879 | [[package]] 880 | name = "futures-util" 881 | version = "0.3.31" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 884 | dependencies = [ 885 | "futures-core", 886 | "futures-task", 887 | "pin-project-lite", 888 | "pin-utils", 889 | ] 890 | 891 | [[package]] 892 | name = "getrandom" 893 | version = "0.2.15" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 896 | dependencies = [ 897 | "cfg-if", 898 | "libc", 899 | "wasi", 900 | ] 901 | 902 | [[package]] 903 | name = "gimli" 904 | version = "0.31.1" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 907 | 908 | [[package]] 909 | name = "glob" 910 | version = "0.3.1" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 913 | 914 | [[package]] 915 | name = "globset" 916 | version = "0.4.15" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" 919 | dependencies = [ 920 | "aho-corasick", 921 | "bstr", 922 | "log", 923 | "regex-automata", 924 | "regex-syntax", 925 | ] 926 | 927 | [[package]] 928 | name = "globwalk" 929 | version = "0.8.1" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" 932 | dependencies = [ 933 | "bitflags 1.3.2", 934 | "ignore", 935 | "walkdir", 936 | ] 937 | 938 | [[package]] 939 | name = "hash32" 940 | version = "0.3.1" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" 943 | dependencies = [ 944 | "byteorder", 945 | ] 946 | 947 | [[package]] 948 | name = "hashbrown" 949 | version = "0.12.3" 950 | source = "registry+https://github.com/rust-lang/crates.io-index" 951 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 952 | 953 | [[package]] 954 | name = "hashbrown" 955 | version = "0.15.2" 956 | source = "registry+https://github.com/rust-lang/crates.io-index" 957 | checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" 958 | 959 | [[package]] 960 | name = "heapless" 961 | version = "0.8.0" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" 964 | dependencies = [ 965 | "hash32", 966 | "serde", 967 | "stable_deref_trait", 968 | ] 969 | 970 | [[package]] 971 | name = "heck" 972 | version = "0.4.1" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 975 | 976 | [[package]] 977 | name = "hex" 978 | version = "0.4.3" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 981 | 982 | [[package]] 983 | name = "home" 984 | version = "0.5.9" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" 987 | dependencies = [ 988 | "windows-sys 0.52.0", 989 | ] 990 | 991 | [[package]] 992 | name = "iana-time-zone" 993 | version = "0.1.61" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 996 | dependencies = [ 997 | "android_system_properties", 998 | "core-foundation-sys", 999 | "iana-time-zone-haiku", 1000 | "js-sys", 1001 | "wasm-bindgen", 1002 | "windows-core", 1003 | ] 1004 | 1005 | [[package]] 1006 | name = "iana-time-zone-haiku" 1007 | version = "0.1.2" 1008 | source = "registry+https://github.com/rust-lang/crates.io-index" 1009 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 1010 | dependencies = [ 1011 | "cc", 1012 | ] 1013 | 1014 | [[package]] 1015 | name = "icu_collections" 1016 | version = "1.5.0" 1017 | source = "registry+https://github.com/rust-lang/crates.io-index" 1018 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 1019 | dependencies = [ 1020 | "displaydoc", 1021 | "yoke", 1022 | "zerofrom", 1023 | "zerovec", 1024 | ] 1025 | 1026 | [[package]] 1027 | name = "icu_locid" 1028 | version = "1.5.0" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 1031 | dependencies = [ 1032 | "displaydoc", 1033 | "litemap", 1034 | "tinystr", 1035 | "writeable", 1036 | "zerovec", 1037 | ] 1038 | 1039 | [[package]] 1040 | name = "icu_locid_transform" 1041 | version = "1.5.0" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 1044 | dependencies = [ 1045 | "displaydoc", 1046 | "icu_locid", 1047 | "icu_locid_transform_data", 1048 | "icu_provider", 1049 | "tinystr", 1050 | "zerovec", 1051 | ] 1052 | 1053 | [[package]] 1054 | name = "icu_locid_transform_data" 1055 | version = "1.5.0" 1056 | source = "registry+https://github.com/rust-lang/crates.io-index" 1057 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 1058 | 1059 | [[package]] 1060 | name = "icu_normalizer" 1061 | version = "1.5.0" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 1064 | dependencies = [ 1065 | "displaydoc", 1066 | "icu_collections", 1067 | "icu_normalizer_data", 1068 | "icu_properties", 1069 | "icu_provider", 1070 | "smallvec", 1071 | "utf16_iter", 1072 | "utf8_iter", 1073 | "write16", 1074 | "zerovec", 1075 | ] 1076 | 1077 | [[package]] 1078 | name = "icu_normalizer_data" 1079 | version = "1.5.0" 1080 | source = "registry+https://github.com/rust-lang/crates.io-index" 1081 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 1082 | 1083 | [[package]] 1084 | name = "icu_properties" 1085 | version = "1.5.1" 1086 | source = "registry+https://github.com/rust-lang/crates.io-index" 1087 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 1088 | dependencies = [ 1089 | "displaydoc", 1090 | "icu_collections", 1091 | "icu_locid_transform", 1092 | "icu_properties_data", 1093 | "icu_provider", 1094 | "tinystr", 1095 | "zerovec", 1096 | ] 1097 | 1098 | [[package]] 1099 | name = "icu_properties_data" 1100 | version = "1.5.0" 1101 | source = "registry+https://github.com/rust-lang/crates.io-index" 1102 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 1103 | 1104 | [[package]] 1105 | name = "icu_provider" 1106 | version = "1.5.0" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 1109 | dependencies = [ 1110 | "displaydoc", 1111 | "icu_locid", 1112 | "icu_provider_macros", 1113 | "stable_deref_trait", 1114 | "tinystr", 1115 | "writeable", 1116 | "yoke", 1117 | "zerofrom", 1118 | "zerovec", 1119 | ] 1120 | 1121 | [[package]] 1122 | name = "icu_provider_macros" 1123 | version = "1.5.0" 1124 | source = "registry+https://github.com/rust-lang/crates.io-index" 1125 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 1126 | dependencies = [ 1127 | "proc-macro2", 1128 | "quote", 1129 | "syn 2.0.90", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "ident_case" 1134 | version = "1.0.1" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 1137 | 1138 | [[package]] 1139 | name = "idna" 1140 | version = "1.0.3" 1141 | source = "registry+https://github.com/rust-lang/crates.io-index" 1142 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 1143 | dependencies = [ 1144 | "idna_adapter", 1145 | "smallvec", 1146 | "utf8_iter", 1147 | ] 1148 | 1149 | [[package]] 1150 | name = "idna_adapter" 1151 | version = "1.2.0" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 1154 | dependencies = [ 1155 | "icu_normalizer", 1156 | "icu_properties", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "idotmatrix" 1161 | version = "0.1.0" 1162 | source = "git+https://github.com/Kezii/idotmatrix.git#a8bff943d3974d12574ab9751d5a93ab212ec7fa" 1163 | dependencies = [ 1164 | "crc32fast", 1165 | ] 1166 | 1167 | [[package]] 1168 | name = "ignore" 1169 | version = "0.4.23" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" 1172 | dependencies = [ 1173 | "crossbeam-deque", 1174 | "globset", 1175 | "log", 1176 | "memchr", 1177 | "regex-automata", 1178 | "same-file", 1179 | "walkdir", 1180 | "winapi-util", 1181 | ] 1182 | 1183 | [[package]] 1184 | name = "image" 1185 | version = "0.25.5" 1186 | source = "registry+https://github.com/rust-lang/crates.io-index" 1187 | checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" 1188 | dependencies = [ 1189 | "bytemuck", 1190 | "byteorder-lite", 1191 | "num-traits", 1192 | "png", 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "indexmap" 1197 | version = "1.9.3" 1198 | source = "registry+https://github.com/rust-lang/crates.io-index" 1199 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 1200 | dependencies = [ 1201 | "autocfg", 1202 | "hashbrown 0.12.3", 1203 | "serde", 1204 | ] 1205 | 1206 | [[package]] 1207 | name = "indexmap" 1208 | version = "2.7.0" 1209 | source = "registry+https://github.com/rust-lang/crates.io-index" 1210 | checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" 1211 | dependencies = [ 1212 | "equivalent", 1213 | "hashbrown 0.15.2", 1214 | "serde", 1215 | ] 1216 | 1217 | [[package]] 1218 | name = "itertools" 1219 | version = "0.12.1" 1220 | source = "registry+https://github.com/rust-lang/crates.io-index" 1221 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 1222 | dependencies = [ 1223 | "either", 1224 | ] 1225 | 1226 | [[package]] 1227 | name = "itoa" 1228 | version = "1.0.14" 1229 | source = "registry+https://github.com/rust-lang/crates.io-index" 1230 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 1231 | 1232 | [[package]] 1233 | name = "js-sys" 1234 | version = "0.3.76" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" 1237 | dependencies = [ 1238 | "once_cell", 1239 | "wasm-bindgen", 1240 | ] 1241 | 1242 | [[package]] 1243 | name = "lazy_static" 1244 | version = "1.5.0" 1245 | source = "registry+https://github.com/rust-lang/crates.io-index" 1246 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 1247 | 1248 | [[package]] 1249 | name = "lazycell" 1250 | version = "1.3.0" 1251 | source = "registry+https://github.com/rust-lang/crates.io-index" 1252 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 1253 | 1254 | [[package]] 1255 | name = "libc" 1256 | version = "0.2.168" 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" 1258 | checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" 1259 | 1260 | [[package]] 1261 | name = "libloading" 1262 | version = "0.8.6" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 1265 | dependencies = [ 1266 | "cfg-if", 1267 | "windows-targets", 1268 | ] 1269 | 1270 | [[package]] 1271 | name = "libredox" 1272 | version = "0.1.3" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 1275 | dependencies = [ 1276 | "bitflags 2.6.0", 1277 | "libc", 1278 | "redox_syscall", 1279 | ] 1280 | 1281 | [[package]] 1282 | name = "linux-raw-sys" 1283 | version = "0.4.14" 1284 | source = "registry+https://github.com/rust-lang/crates.io-index" 1285 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 1286 | 1287 | [[package]] 1288 | name = "litemap" 1289 | version = "0.7.4" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" 1292 | 1293 | [[package]] 1294 | name = "litrs" 1295 | version = "0.4.1" 1296 | source = "registry+https://github.com/rust-lang/crates.io-index" 1297 | checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" 1298 | 1299 | [[package]] 1300 | name = "log" 1301 | version = "0.4.22" 1302 | source = "registry+https://github.com/rust-lang/crates.io-index" 1303 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 1304 | 1305 | [[package]] 1306 | name = "macro_rules_attribute" 1307 | version = "0.2.0" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "8a82271f7bc033d84bbca59a3ce3e4159938cb08a9c3aebbe54d215131518a13" 1310 | dependencies = [ 1311 | "macro_rules_attribute-proc_macro", 1312 | "paste", 1313 | ] 1314 | 1315 | [[package]] 1316 | name = "macro_rules_attribute-proc_macro" 1317 | version = "0.2.0" 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" 1319 | checksum = "b8dd856d451cc0da70e2ef2ce95a18e39a93b7558bedf10201ad28503f918568" 1320 | 1321 | [[package]] 1322 | name = "memchr" 1323 | version = "2.7.4" 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" 1325 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 1326 | 1327 | [[package]] 1328 | name = "minimal-lexical" 1329 | version = "0.2.1" 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" 1331 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 1332 | 1333 | [[package]] 1334 | name = "miniz_oxide" 1335 | version = "0.8.0" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 1338 | dependencies = [ 1339 | "adler2", 1340 | "simd-adler32", 1341 | ] 1342 | 1343 | [[package]] 1344 | name = "nb" 1345 | version = "0.1.3" 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" 1347 | checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" 1348 | dependencies = [ 1349 | "nb 1.1.0", 1350 | ] 1351 | 1352 | [[package]] 1353 | name = "nb" 1354 | version = "1.1.0" 1355 | source = "registry+https://github.com/rust-lang/crates.io-index" 1356 | checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" 1357 | 1358 | [[package]] 1359 | name = "nix" 1360 | version = "0.29.0" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" 1363 | dependencies = [ 1364 | "bitflags 2.6.0", 1365 | "cfg-if", 1366 | "cfg_aliases", 1367 | "libc", 1368 | ] 1369 | 1370 | [[package]] 1371 | name = "nom" 1372 | version = "7.1.3" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 1375 | dependencies = [ 1376 | "memchr", 1377 | "minimal-lexical", 1378 | ] 1379 | 1380 | [[package]] 1381 | name = "normpath" 1382 | version = "1.3.0" 1383 | source = "registry+https://github.com/rust-lang/crates.io-index" 1384 | checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed" 1385 | dependencies = [ 1386 | "windows-sys 0.59.0", 1387 | ] 1388 | 1389 | [[package]] 1390 | name = "num-conv" 1391 | version = "0.1.0" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" 1394 | 1395 | [[package]] 1396 | name = "num-traits" 1397 | version = "0.2.19" 1398 | source = "registry+https://github.com/rust-lang/crates.io-index" 1399 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 1400 | dependencies = [ 1401 | "autocfg", 1402 | ] 1403 | 1404 | [[package]] 1405 | name = "num_enum" 1406 | version = "0.7.3" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" 1409 | dependencies = [ 1410 | "num_enum_derive", 1411 | ] 1412 | 1413 | [[package]] 1414 | name = "num_enum_derive" 1415 | version = "0.7.3" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" 1418 | dependencies = [ 1419 | "proc-macro-crate", 1420 | "proc-macro2", 1421 | "quote", 1422 | "syn 2.0.90", 1423 | ] 1424 | 1425 | [[package]] 1426 | name = "object" 1427 | version = "0.36.5" 1428 | source = "registry+https://github.com/rust-lang/crates.io-index" 1429 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 1430 | dependencies = [ 1431 | "memchr", 1432 | ] 1433 | 1434 | [[package]] 1435 | name = "once_cell" 1436 | version = "1.20.2" 1437 | source = "registry+https://github.com/rust-lang/crates.io-index" 1438 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 1439 | 1440 | [[package]] 1441 | name = "paste" 1442 | version = "1.0.15" 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" 1444 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 1445 | 1446 | [[package]] 1447 | name = "percent-encoding" 1448 | version = "2.3.1" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1451 | 1452 | [[package]] 1453 | name = "pin-project-lite" 1454 | version = "0.2.15" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 1457 | 1458 | [[package]] 1459 | name = "pin-utils" 1460 | version = "0.1.0" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1463 | 1464 | [[package]] 1465 | name = "png" 1466 | version = "0.17.15" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d" 1469 | dependencies = [ 1470 | "bitflags 1.3.2", 1471 | "crc32fast", 1472 | "fdeflate", 1473 | "flate2", 1474 | "miniz_oxide", 1475 | ] 1476 | 1477 | [[package]] 1478 | name = "powerfmt" 1479 | version = "0.2.0" 1480 | source = "registry+https://github.com/rust-lang/crates.io-index" 1481 | checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" 1482 | 1483 | [[package]] 1484 | name = "prettyplease" 1485 | version = "0.2.25" 1486 | source = "registry+https://github.com/rust-lang/crates.io-index" 1487 | checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" 1488 | dependencies = [ 1489 | "proc-macro2", 1490 | "syn 2.0.90", 1491 | ] 1492 | 1493 | [[package]] 1494 | name = "proc-macro-crate" 1495 | version = "3.2.0" 1496 | source = "registry+https://github.com/rust-lang/crates.io-index" 1497 | checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" 1498 | dependencies = [ 1499 | "toml_edit 0.22.22", 1500 | ] 1501 | 1502 | [[package]] 1503 | name = "proc-macro-error-attr2" 1504 | version = "2.0.0" 1505 | source = "registry+https://github.com/rust-lang/crates.io-index" 1506 | checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 1507 | dependencies = [ 1508 | "proc-macro2", 1509 | "quote", 1510 | ] 1511 | 1512 | [[package]] 1513 | name = "proc-macro-error2" 1514 | version = "2.0.1" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 1517 | dependencies = [ 1518 | "proc-macro-error-attr2", 1519 | "proc-macro2", 1520 | "quote", 1521 | "syn 2.0.90", 1522 | ] 1523 | 1524 | [[package]] 1525 | name = "proc-macro2" 1526 | version = "1.0.92" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 1529 | dependencies = [ 1530 | "unicode-ident", 1531 | ] 1532 | 1533 | [[package]] 1534 | name = "quote" 1535 | version = "1.0.37" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 1538 | dependencies = [ 1539 | "proc-macro2", 1540 | ] 1541 | 1542 | [[package]] 1543 | name = "redox_syscall" 1544 | version = "0.5.7" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" 1547 | dependencies = [ 1548 | "bitflags 2.6.0", 1549 | ] 1550 | 1551 | [[package]] 1552 | name = "regex" 1553 | version = "1.11.1" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 1556 | dependencies = [ 1557 | "aho-corasick", 1558 | "memchr", 1559 | "regex-automata", 1560 | "regex-syntax", 1561 | ] 1562 | 1563 | [[package]] 1564 | name = "regex-automata" 1565 | version = "0.4.9" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 1568 | dependencies = [ 1569 | "aho-corasick", 1570 | "memchr", 1571 | "regex-syntax", 1572 | ] 1573 | 1574 | [[package]] 1575 | name = "regex-syntax" 1576 | version = "0.8.5" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 1579 | 1580 | [[package]] 1581 | name = "remove_dir_all" 1582 | version = "0.8.4" 1583 | source = "registry+https://github.com/rust-lang/crates.io-index" 1584 | checksum = "a694f9e0eb3104451127f6cc1e5de55f59d3b1fc8c5ddfaeb6f1e716479ceb4a" 1585 | dependencies = [ 1586 | "cfg-if", 1587 | "cvt", 1588 | "fs_at", 1589 | "libc", 1590 | "normpath", 1591 | "windows-sys 0.59.0", 1592 | ] 1593 | 1594 | [[package]] 1595 | name = "rgb565" 1596 | version = "0.1.3" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "6d43e85498d0bb728f77a88b4313eaf4ed21673f3f8a05c36e835cf6c9c0d066" 1599 | 1600 | [[package]] 1601 | name = "ring" 1602 | version = "0.17.8" 1603 | source = "registry+https://github.com/rust-lang/crates.io-index" 1604 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 1605 | dependencies = [ 1606 | "cc", 1607 | "cfg-if", 1608 | "getrandom", 1609 | "libc", 1610 | "spin", 1611 | "untrusted", 1612 | "windows-sys 0.52.0", 1613 | ] 1614 | 1615 | [[package]] 1616 | name = "rustc-demangle" 1617 | version = "0.1.24" 1618 | source = "registry+https://github.com/rust-lang/crates.io-index" 1619 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1620 | 1621 | [[package]] 1622 | name = "rustc-hash" 1623 | version = "1.1.0" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1626 | 1627 | [[package]] 1628 | name = "rustix" 1629 | version = "0.38.42" 1630 | source = "registry+https://github.com/rust-lang/crates.io-index" 1631 | checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" 1632 | dependencies = [ 1633 | "bitflags 2.6.0", 1634 | "errno", 1635 | "libc", 1636 | "linux-raw-sys", 1637 | "windows-sys 0.59.0", 1638 | ] 1639 | 1640 | [[package]] 1641 | name = "rustls" 1642 | version = "0.23.19" 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" 1644 | checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" 1645 | dependencies = [ 1646 | "log", 1647 | "once_cell", 1648 | "ring", 1649 | "rustls-pki-types", 1650 | "rustls-webpki", 1651 | "subtle", 1652 | "zeroize", 1653 | ] 1654 | 1655 | [[package]] 1656 | name = "rustls-pki-types" 1657 | version = "1.10.0" 1658 | source = "registry+https://github.com/rust-lang/crates.io-index" 1659 | checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" 1660 | 1661 | [[package]] 1662 | name = "rustls-webpki" 1663 | version = "0.102.8" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 1666 | dependencies = [ 1667 | "ring", 1668 | "rustls-pki-types", 1669 | "untrusted", 1670 | ] 1671 | 1672 | [[package]] 1673 | name = "rustversion" 1674 | version = "1.0.18" 1675 | source = "registry+https://github.com/rust-lang/crates.io-index" 1676 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" 1677 | 1678 | [[package]] 1679 | name = "ryu" 1680 | version = "1.0.18" 1681 | source = "registry+https://github.com/rust-lang/crates.io-index" 1682 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1683 | 1684 | [[package]] 1685 | name = "same-file" 1686 | version = "1.0.6" 1687 | source = "registry+https://github.com/rust-lang/crates.io-index" 1688 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1689 | dependencies = [ 1690 | "winapi-util", 1691 | ] 1692 | 1693 | [[package]] 1694 | name = "semver" 1695 | version = "1.0.23" 1696 | source = "registry+https://github.com/rust-lang/crates.io-index" 1697 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 1698 | dependencies = [ 1699 | "serde", 1700 | ] 1701 | 1702 | [[package]] 1703 | name = "serde" 1704 | version = "1.0.215" 1705 | source = "registry+https://github.com/rust-lang/crates.io-index" 1706 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 1707 | dependencies = [ 1708 | "serde_derive", 1709 | ] 1710 | 1711 | [[package]] 1712 | name = "serde_derive" 1713 | version = "1.0.215" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 1716 | dependencies = [ 1717 | "proc-macro2", 1718 | "quote", 1719 | "syn 2.0.90", 1720 | ] 1721 | 1722 | [[package]] 1723 | name = "serde_json" 1724 | version = "1.0.133" 1725 | source = "registry+https://github.com/rust-lang/crates.io-index" 1726 | checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" 1727 | dependencies = [ 1728 | "itoa", 1729 | "memchr", 1730 | "ryu", 1731 | "serde", 1732 | ] 1733 | 1734 | [[package]] 1735 | name = "serde_spanned" 1736 | version = "0.6.8" 1737 | source = "registry+https://github.com/rust-lang/crates.io-index" 1738 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 1739 | dependencies = [ 1740 | "serde", 1741 | ] 1742 | 1743 | [[package]] 1744 | name = "serde_with" 1745 | version = "3.11.0" 1746 | source = "registry+https://github.com/rust-lang/crates.io-index" 1747 | checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" 1748 | dependencies = [ 1749 | "base64", 1750 | "chrono", 1751 | "hex", 1752 | "indexmap 1.9.3", 1753 | "indexmap 2.7.0", 1754 | "serde", 1755 | "serde_derive", 1756 | "serde_json", 1757 | "serde_with_macros", 1758 | "time", 1759 | ] 1760 | 1761 | [[package]] 1762 | name = "serde_with_macros" 1763 | version = "3.11.0" 1764 | source = "registry+https://github.com/rust-lang/crates.io-index" 1765 | checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" 1766 | dependencies = [ 1767 | "darling", 1768 | "proc-macro2", 1769 | "quote", 1770 | "syn 2.0.90", 1771 | ] 1772 | 1773 | [[package]] 1774 | name = "shlex" 1775 | version = "1.3.0" 1776 | source = "registry+https://github.com/rust-lang/crates.io-index" 1777 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1778 | 1779 | [[package]] 1780 | name = "simd-adler32" 1781 | version = "0.3.7" 1782 | source = "registry+https://github.com/rust-lang/crates.io-index" 1783 | checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" 1784 | 1785 | [[package]] 1786 | name = "smallvec" 1787 | version = "1.13.2" 1788 | source = "registry+https://github.com/rust-lang/crates.io-index" 1789 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1790 | 1791 | [[package]] 1792 | name = "spin" 1793 | version = "0.9.8" 1794 | source = "registry+https://github.com/rust-lang/crates.io-index" 1795 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1796 | 1797 | [[package]] 1798 | name = "stable_deref_trait" 1799 | version = "1.2.0" 1800 | source = "registry+https://github.com/rust-lang/crates.io-index" 1801 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1802 | 1803 | [[package]] 1804 | name = "strsim" 1805 | version = "0.11.1" 1806 | source = "registry+https://github.com/rust-lang/crates.io-index" 1807 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1808 | 1809 | [[package]] 1810 | name = "strum" 1811 | version = "0.24.1" 1812 | source = "registry+https://github.com/rust-lang/crates.io-index" 1813 | checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" 1814 | dependencies = [ 1815 | "strum_macros 0.24.3", 1816 | ] 1817 | 1818 | [[package]] 1819 | name = "strum" 1820 | version = "0.25.0" 1821 | source = "registry+https://github.com/rust-lang/crates.io-index" 1822 | checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" 1823 | dependencies = [ 1824 | "strum_macros 0.25.3", 1825 | ] 1826 | 1827 | [[package]] 1828 | name = "strum_macros" 1829 | version = "0.24.3" 1830 | source = "registry+https://github.com/rust-lang/crates.io-index" 1831 | checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" 1832 | dependencies = [ 1833 | "heck", 1834 | "proc-macro2", 1835 | "quote", 1836 | "rustversion", 1837 | "syn 1.0.109", 1838 | ] 1839 | 1840 | [[package]] 1841 | name = "strum_macros" 1842 | version = "0.25.3" 1843 | source = "registry+https://github.com/rust-lang/crates.io-index" 1844 | checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" 1845 | dependencies = [ 1846 | "heck", 1847 | "proc-macro2", 1848 | "quote", 1849 | "rustversion", 1850 | "syn 2.0.90", 1851 | ] 1852 | 1853 | [[package]] 1854 | name = "subtle" 1855 | version = "2.6.1" 1856 | source = "registry+https://github.com/rust-lang/crates.io-index" 1857 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1858 | 1859 | [[package]] 1860 | name = "syn" 1861 | version = "1.0.109" 1862 | source = "registry+https://github.com/rust-lang/crates.io-index" 1863 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1864 | dependencies = [ 1865 | "proc-macro2", 1866 | "quote", 1867 | "unicode-ident", 1868 | ] 1869 | 1870 | [[package]] 1871 | name = "syn" 1872 | version = "2.0.90" 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" 1874 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 1875 | dependencies = [ 1876 | "proc-macro2", 1877 | "quote", 1878 | "unicode-ident", 1879 | ] 1880 | 1881 | [[package]] 1882 | name = "synstructure" 1883 | version = "0.13.1" 1884 | source = "registry+https://github.com/rust-lang/crates.io-index" 1885 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1886 | dependencies = [ 1887 | "proc-macro2", 1888 | "quote", 1889 | "syn 2.0.90", 1890 | ] 1891 | 1892 | [[package]] 1893 | name = "tempfile" 1894 | version = "3.14.0" 1895 | source = "registry+https://github.com/rust-lang/crates.io-index" 1896 | checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" 1897 | dependencies = [ 1898 | "cfg-if", 1899 | "fastrand", 1900 | "once_cell", 1901 | "rustix", 1902 | "windows-sys 0.59.0", 1903 | ] 1904 | 1905 | [[package]] 1906 | name = "thiserror" 1907 | version = "1.0.69" 1908 | source = "registry+https://github.com/rust-lang/crates.io-index" 1909 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1910 | dependencies = [ 1911 | "thiserror-impl 1.0.69", 1912 | ] 1913 | 1914 | [[package]] 1915 | name = "thiserror" 1916 | version = "2.0.6" 1917 | source = "registry+https://github.com/rust-lang/crates.io-index" 1918 | checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" 1919 | dependencies = [ 1920 | "thiserror-impl 2.0.6", 1921 | ] 1922 | 1923 | [[package]] 1924 | name = "thiserror-impl" 1925 | version = "1.0.69" 1926 | source = "registry+https://github.com/rust-lang/crates.io-index" 1927 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1928 | dependencies = [ 1929 | "proc-macro2", 1930 | "quote", 1931 | "syn 2.0.90", 1932 | ] 1933 | 1934 | [[package]] 1935 | name = "thiserror-impl" 1936 | version = "2.0.6" 1937 | source = "registry+https://github.com/rust-lang/crates.io-index" 1938 | checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" 1939 | dependencies = [ 1940 | "proc-macro2", 1941 | "quote", 1942 | "syn 2.0.90", 1943 | ] 1944 | 1945 | [[package]] 1946 | name = "time" 1947 | version = "0.3.37" 1948 | source = "registry+https://github.com/rust-lang/crates.io-index" 1949 | checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" 1950 | dependencies = [ 1951 | "deranged", 1952 | "itoa", 1953 | "num-conv", 1954 | "powerfmt", 1955 | "serde", 1956 | "time-core", 1957 | "time-macros", 1958 | ] 1959 | 1960 | [[package]] 1961 | name = "time-core" 1962 | version = "0.1.2" 1963 | source = "registry+https://github.com/rust-lang/crates.io-index" 1964 | checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" 1965 | 1966 | [[package]] 1967 | name = "time-macros" 1968 | version = "0.2.19" 1969 | source = "registry+https://github.com/rust-lang/crates.io-index" 1970 | checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" 1971 | dependencies = [ 1972 | "num-conv", 1973 | "time-core", 1974 | ] 1975 | 1976 | [[package]] 1977 | name = "tinystr" 1978 | version = "0.7.6" 1979 | source = "registry+https://github.com/rust-lang/crates.io-index" 1980 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1981 | dependencies = [ 1982 | "displaydoc", 1983 | "zerovec", 1984 | ] 1985 | 1986 | [[package]] 1987 | name = "tokio" 1988 | version = "1.42.0" 1989 | source = "registry+https://github.com/rust-lang/crates.io-index" 1990 | checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" 1991 | dependencies = [ 1992 | "backtrace", 1993 | "pin-project-lite", 1994 | "tokio-macros", 1995 | ] 1996 | 1997 | [[package]] 1998 | name = "tokio-macros" 1999 | version = "2.4.0" 2000 | source = "registry+https://github.com/rust-lang/crates.io-index" 2001 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 2002 | dependencies = [ 2003 | "proc-macro2", 2004 | "quote", 2005 | "syn 2.0.90", 2006 | ] 2007 | 2008 | [[package]] 2009 | name = "toml" 2010 | version = "0.7.8" 2011 | source = "registry+https://github.com/rust-lang/crates.io-index" 2012 | checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" 2013 | dependencies = [ 2014 | "serde", 2015 | "serde_spanned", 2016 | "toml_datetime", 2017 | "toml_edit 0.19.15", 2018 | ] 2019 | 2020 | [[package]] 2021 | name = "toml_datetime" 2022 | version = "0.6.8" 2023 | source = "registry+https://github.com/rust-lang/crates.io-index" 2024 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 2025 | dependencies = [ 2026 | "serde", 2027 | ] 2028 | 2029 | [[package]] 2030 | name = "toml_edit" 2031 | version = "0.19.15" 2032 | source = "registry+https://github.com/rust-lang/crates.io-index" 2033 | checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" 2034 | dependencies = [ 2035 | "indexmap 2.7.0", 2036 | "serde", 2037 | "serde_spanned", 2038 | "toml_datetime", 2039 | "winnow 0.5.40", 2040 | ] 2041 | 2042 | [[package]] 2043 | name = "toml_edit" 2044 | version = "0.22.22" 2045 | source = "registry+https://github.com/rust-lang/crates.io-index" 2046 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 2047 | dependencies = [ 2048 | "indexmap 2.7.0", 2049 | "toml_datetime", 2050 | "winnow 0.6.20", 2051 | ] 2052 | 2053 | [[package]] 2054 | name = "uncased" 2055 | version = "0.9.10" 2056 | source = "registry+https://github.com/rust-lang/crates.io-index" 2057 | checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" 2058 | dependencies = [ 2059 | "version_check", 2060 | ] 2061 | 2062 | [[package]] 2063 | name = "unicode-ident" 2064 | version = "1.0.14" 2065 | source = "registry+https://github.com/rust-lang/crates.io-index" 2066 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 2067 | 2068 | [[package]] 2069 | name = "unicode-xid" 2070 | version = "0.2.6" 2071 | source = "registry+https://github.com/rust-lang/crates.io-index" 2072 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" 2073 | 2074 | [[package]] 2075 | name = "untrusted" 2076 | version = "0.9.0" 2077 | source = "registry+https://github.com/rust-lang/crates.io-index" 2078 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 2079 | 2080 | [[package]] 2081 | name = "ureq" 2082 | version = "2.12.1" 2083 | source = "registry+https://github.com/rust-lang/crates.io-index" 2084 | checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" 2085 | dependencies = [ 2086 | "base64", 2087 | "flate2", 2088 | "log", 2089 | "once_cell", 2090 | "rustls", 2091 | "rustls-pki-types", 2092 | "url", 2093 | "webpki-roots", 2094 | ] 2095 | 2096 | [[package]] 2097 | name = "url" 2098 | version = "2.5.4" 2099 | source = "registry+https://github.com/rust-lang/crates.io-index" 2100 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 2101 | dependencies = [ 2102 | "form_urlencoded", 2103 | "idna", 2104 | "percent-encoding", 2105 | ] 2106 | 2107 | [[package]] 2108 | name = "utf16_iter" 2109 | version = "1.0.5" 2110 | source = "registry+https://github.com/rust-lang/crates.io-index" 2111 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 2112 | 2113 | [[package]] 2114 | name = "utf8_iter" 2115 | version = "1.0.4" 2116 | source = "registry+https://github.com/rust-lang/crates.io-index" 2117 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2118 | 2119 | [[package]] 2120 | name = "uuid" 2121 | version = "1.11.0" 2122 | source = "registry+https://github.com/rust-lang/crates.io-index" 2123 | checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" 2124 | dependencies = [ 2125 | "uuid-macro-internal", 2126 | ] 2127 | 2128 | [[package]] 2129 | name = "uuid-macro-internal" 2130 | version = "1.11.0" 2131 | source = "registry+https://github.com/rust-lang/crates.io-index" 2132 | checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08" 2133 | dependencies = [ 2134 | "proc-macro2", 2135 | "quote", 2136 | "syn 2.0.90", 2137 | ] 2138 | 2139 | [[package]] 2140 | name = "version_check" 2141 | version = "0.9.5" 2142 | source = "registry+https://github.com/rust-lang/crates.io-index" 2143 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 2144 | 2145 | [[package]] 2146 | name = "void" 2147 | version = "1.0.2" 2148 | source = "registry+https://github.com/rust-lang/crates.io-index" 2149 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 2150 | 2151 | [[package]] 2152 | name = "walkdir" 2153 | version = "2.5.0" 2154 | source = "registry+https://github.com/rust-lang/crates.io-index" 2155 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 2156 | dependencies = [ 2157 | "same-file", 2158 | "winapi-util", 2159 | ] 2160 | 2161 | [[package]] 2162 | name = "wasi" 2163 | version = "0.11.0+wasi-snapshot-preview1" 2164 | source = "registry+https://github.com/rust-lang/crates.io-index" 2165 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2166 | 2167 | [[package]] 2168 | name = "wasm-bindgen" 2169 | version = "0.2.99" 2170 | source = "registry+https://github.com/rust-lang/crates.io-index" 2171 | checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" 2172 | dependencies = [ 2173 | "cfg-if", 2174 | "once_cell", 2175 | "wasm-bindgen-macro", 2176 | ] 2177 | 2178 | [[package]] 2179 | name = "wasm-bindgen-backend" 2180 | version = "0.2.99" 2181 | source = "registry+https://github.com/rust-lang/crates.io-index" 2182 | checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" 2183 | dependencies = [ 2184 | "bumpalo", 2185 | "log", 2186 | "proc-macro2", 2187 | "quote", 2188 | "syn 2.0.90", 2189 | "wasm-bindgen-shared", 2190 | ] 2191 | 2192 | [[package]] 2193 | name = "wasm-bindgen-macro" 2194 | version = "0.2.99" 2195 | source = "registry+https://github.com/rust-lang/crates.io-index" 2196 | checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" 2197 | dependencies = [ 2198 | "quote", 2199 | "wasm-bindgen-macro-support", 2200 | ] 2201 | 2202 | [[package]] 2203 | name = "wasm-bindgen-macro-support" 2204 | version = "0.2.99" 2205 | source = "registry+https://github.com/rust-lang/crates.io-index" 2206 | checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" 2207 | dependencies = [ 2208 | "proc-macro2", 2209 | "quote", 2210 | "syn 2.0.90", 2211 | "wasm-bindgen-backend", 2212 | "wasm-bindgen-shared", 2213 | ] 2214 | 2215 | [[package]] 2216 | name = "wasm-bindgen-shared" 2217 | version = "0.2.99" 2218 | source = "registry+https://github.com/rust-lang/crates.io-index" 2219 | checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" 2220 | 2221 | [[package]] 2222 | name = "webpki-roots" 2223 | version = "0.26.7" 2224 | source = "registry+https://github.com/rust-lang/crates.io-index" 2225 | checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" 2226 | dependencies = [ 2227 | "rustls-pki-types", 2228 | ] 2229 | 2230 | [[package]] 2231 | name = "which" 2232 | version = "4.4.2" 2233 | source = "registry+https://github.com/rust-lang/crates.io-index" 2234 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 2235 | dependencies = [ 2236 | "either", 2237 | "home", 2238 | "once_cell", 2239 | "rustix", 2240 | ] 2241 | 2242 | [[package]] 2243 | name = "winapi-util" 2244 | version = "0.1.9" 2245 | source = "registry+https://github.com/rust-lang/crates.io-index" 2246 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 2247 | dependencies = [ 2248 | "windows-sys 0.59.0", 2249 | ] 2250 | 2251 | [[package]] 2252 | name = "windows-core" 2253 | version = "0.52.0" 2254 | source = "registry+https://github.com/rust-lang/crates.io-index" 2255 | checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 2256 | dependencies = [ 2257 | "windows-targets", 2258 | ] 2259 | 2260 | [[package]] 2261 | name = "windows-sys" 2262 | version = "0.52.0" 2263 | source = "registry+https://github.com/rust-lang/crates.io-index" 2264 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2265 | dependencies = [ 2266 | "windows-targets", 2267 | ] 2268 | 2269 | [[package]] 2270 | name = "windows-sys" 2271 | version = "0.59.0" 2272 | source = "registry+https://github.com/rust-lang/crates.io-index" 2273 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 2274 | dependencies = [ 2275 | "windows-targets", 2276 | ] 2277 | 2278 | [[package]] 2279 | name = "windows-targets" 2280 | version = "0.52.6" 2281 | source = "registry+https://github.com/rust-lang/crates.io-index" 2282 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 2283 | dependencies = [ 2284 | "windows_aarch64_gnullvm", 2285 | "windows_aarch64_msvc", 2286 | "windows_i686_gnu", 2287 | "windows_i686_gnullvm", 2288 | "windows_i686_msvc", 2289 | "windows_x86_64_gnu", 2290 | "windows_x86_64_gnullvm", 2291 | "windows_x86_64_msvc", 2292 | ] 2293 | 2294 | [[package]] 2295 | name = "windows_aarch64_gnullvm" 2296 | version = "0.52.6" 2297 | source = "registry+https://github.com/rust-lang/crates.io-index" 2298 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 2299 | 2300 | [[package]] 2301 | name = "windows_aarch64_msvc" 2302 | version = "0.52.6" 2303 | source = "registry+https://github.com/rust-lang/crates.io-index" 2304 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 2305 | 2306 | [[package]] 2307 | name = "windows_i686_gnu" 2308 | version = "0.52.6" 2309 | source = "registry+https://github.com/rust-lang/crates.io-index" 2310 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 2311 | 2312 | [[package]] 2313 | name = "windows_i686_gnullvm" 2314 | version = "0.52.6" 2315 | source = "registry+https://github.com/rust-lang/crates.io-index" 2316 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 2317 | 2318 | [[package]] 2319 | name = "windows_i686_msvc" 2320 | version = "0.52.6" 2321 | source = "registry+https://github.com/rust-lang/crates.io-index" 2322 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 2323 | 2324 | [[package]] 2325 | name = "windows_x86_64_gnu" 2326 | version = "0.52.6" 2327 | source = "registry+https://github.com/rust-lang/crates.io-index" 2328 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 2329 | 2330 | [[package]] 2331 | name = "windows_x86_64_gnullvm" 2332 | version = "0.52.6" 2333 | source = "registry+https://github.com/rust-lang/crates.io-index" 2334 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 2335 | 2336 | [[package]] 2337 | name = "windows_x86_64_msvc" 2338 | version = "0.52.6" 2339 | source = "registry+https://github.com/rust-lang/crates.io-index" 2340 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 2341 | 2342 | [[package]] 2343 | name = "winnow" 2344 | version = "0.5.40" 2345 | source = "registry+https://github.com/rust-lang/crates.io-index" 2346 | checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" 2347 | dependencies = [ 2348 | "memchr", 2349 | ] 2350 | 2351 | [[package]] 2352 | name = "winnow" 2353 | version = "0.6.20" 2354 | source = "registry+https://github.com/rust-lang/crates.io-index" 2355 | checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" 2356 | dependencies = [ 2357 | "memchr", 2358 | ] 2359 | 2360 | [[package]] 2361 | name = "write16" 2362 | version = "1.0.0" 2363 | source = "registry+https://github.com/rust-lang/crates.io-index" 2364 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2365 | 2366 | [[package]] 2367 | name = "writeable" 2368 | version = "0.5.5" 2369 | source = "registry+https://github.com/rust-lang/crates.io-index" 2370 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2371 | 2372 | [[package]] 2373 | name = "yoke" 2374 | version = "0.7.5" 2375 | source = "registry+https://github.com/rust-lang/crates.io-index" 2376 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 2377 | dependencies = [ 2378 | "serde", 2379 | "stable_deref_trait", 2380 | "yoke-derive", 2381 | "zerofrom", 2382 | ] 2383 | 2384 | [[package]] 2385 | name = "yoke-derive" 2386 | version = "0.7.5" 2387 | source = "registry+https://github.com/rust-lang/crates.io-index" 2388 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 2389 | dependencies = [ 2390 | "proc-macro2", 2391 | "quote", 2392 | "syn 2.0.90", 2393 | "synstructure", 2394 | ] 2395 | 2396 | [[package]] 2397 | name = "zerofrom" 2398 | version = "0.1.5" 2399 | source = "registry+https://github.com/rust-lang/crates.io-index" 2400 | checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" 2401 | dependencies = [ 2402 | "zerofrom-derive", 2403 | ] 2404 | 2405 | [[package]] 2406 | name = "zerofrom-derive" 2407 | version = "0.1.5" 2408 | source = "registry+https://github.com/rust-lang/crates.io-index" 2409 | checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" 2410 | dependencies = [ 2411 | "proc-macro2", 2412 | "quote", 2413 | "syn 2.0.90", 2414 | "synstructure", 2415 | ] 2416 | 2417 | [[package]] 2418 | name = "zeroize" 2419 | version = "1.8.1" 2420 | source = "registry+https://github.com/rust-lang/crates.io-index" 2421 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2422 | 2423 | [[package]] 2424 | name = "zerovec" 2425 | version = "0.10.4" 2426 | source = "registry+https://github.com/rust-lang/crates.io-index" 2427 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 2428 | dependencies = [ 2429 | "yoke", 2430 | "zerofrom", 2431 | "zerovec-derive", 2432 | ] 2433 | 2434 | [[package]] 2435 | name = "zerovec-derive" 2436 | version = "0.10.3" 2437 | source = "registry+https://github.com/rust-lang/crates.io-index" 2438 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 2439 | dependencies = [ 2440 | "proc-macro2", 2441 | "quote", 2442 | "syn 2.0.90", 2443 | ] 2444 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "espcam" 3 | version = "0.1.0" 4 | authors = ["Kezi "] 5 | edition = "2021" 6 | resolver = "2" 7 | rust-version = "1.71" 8 | 9 | [profile.release] 10 | opt-level = 2 11 | lto = true 12 | 13 | [profile.dev] 14 | debug = true # Symbols are nice and they don't increase the size on Flash 15 | opt-level = "z" 16 | 17 | [features] 18 | default = ["std", "embassy", "esp-idf-svc/native"] 19 | 20 | pio = ["esp-idf-svc/pio"] 21 | std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"] 22 | alloc = ["esp-idf-svc/alloc"] 23 | nightly = ["esp-idf-svc/nightly"] 24 | experimental = ["esp-idf-svc/experimental"] 25 | embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"] 26 | 27 | [dependencies] 28 | log = { version = "0.4", default-features = false } 29 | esp-idf-svc = { version = "0.49" , default-features = false } 30 | embedded-svc = "0.28" 31 | anyhow = "1.0.79" 32 | 33 | bstr = { version = "1.8.0", default-features = false } 34 | esp32-nimble = "0.8.2" 35 | tokio = { version = "*", features = ["rt", "time", "sync","macros"] } 36 | lazy_static = "1.4.0" 37 | uuid = { version = "1.2.2", default-features = false, features = ["macro-diagnostics"] } 38 | rgb565 = "0.1.3" 39 | image = { version = "0.25", default-features = false, features = ["png"] } 40 | frankenstein = { version = "0.37", default-features = false, features = ["telegram-trait"]} 41 | serde = { version = "1", features = ["derive"]} 42 | serde_json = { version = "1"} 43 | 44 | idotmatrix = {git = "https://github.com/Kezii/idotmatrix.git"} 45 | esp-idf-hal = "0.44" 46 | esp-idf-sys = "0.35" 47 | thiserror = "2.0.6" 48 | 49 | [[package.metadata.esp-idf-sys.extra_components]] 50 | component_dirs = "components/esp32-camera" 51 | bindings_header = "components/bindings.h" 52 | bindings_module = "camera" 53 | 54 | 55 | [build-dependencies] 56 | embuild = "0.32" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esp32cam-rs 2 | actions 3 | 4 | Rust esp32-cam examples 5 | 6 | ### Usage 7 | 8 | After cloning the repo, download the esp32-camera component 9 | 10 | ```bash 11 | git submodule update --init 12 | ``` 13 | 14 | then copy `src/wifi_config.rs.example` into `src/wifi_config.rs` and fill in the correct values 15 | 16 | ## Telegram bot 17 | 18 | ```bash 19 | cargo run --example telegram_bot 20 | 21 | ``` 22 | 23 | Insert the correct token and owner id, then use the /photo command to take a picture 24 | 25 | image 26 | 27 | ## Webserver 28 | 29 | ```bash 30 | cargo run --example webserver 31 | ``` 32 | 33 | Connect to the ip in the log output, then access the /camera.jpg path to take a picture and have it delivered to your browser 34 | 35 | ## IDotMatrix 36 | 37 | ```bash 38 | cargo run --example idotmatrix 39 | ``` 40 | 41 | If you have an idotmatrix display, the esp32-cam will deliver an image to it every few seconds 42 | 43 | image 44 | 45 | 46 | ## credits: 47 | https://github.com/esp-rs/std-training 48 | 49 | https://github.com/jlocash/esp-camera-rs 50 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | embuild::espidf::sysenv::output(); 3 | } 4 | -------------------------------------------------------------------------------- /components/bindings.h: -------------------------------------------------------------------------------- 1 | #include "esp_camera.h" 2 | -------------------------------------------------------------------------------- /components_esp32.lock: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | source: 4 | type: idf 5 | version: 5.1.1 6 | manifest_hash: 06a76c1bdea0ee9953dbe66dc1eb5ea4b157dbc5999962e4b65153dbefdb7f21 7 | target: esp32 8 | version: 2.0.0 9 | -------------------------------------------------------------------------------- /examples/idotmatrix/idotmatrixble.rs: -------------------------------------------------------------------------------- 1 | use crate::Camera; 2 | use anyhow::Result; 3 | use bstr::ByteSlice; 4 | use esp32_nimble::{uuid128, BLEClient, BLEDevice, BLEError, BLEScan}; 5 | use esp_idf_sys::camera; 6 | use espcam::espcam::FrameBuffer; 7 | use image::{ImageBuffer, ImageFormat, Rgb}; 8 | use log::{error, info}; 9 | 10 | pub struct IDMBle<'a> { 11 | characteristic: &'a mut esp32_nimble::BLERemoteCharacteristic, 12 | } 13 | 14 | impl<'a> IDMBle<'a> { 15 | pub async fn new( 16 | ble_device: &'a BLEDevice, 17 | client: &'a mut BLEClient, 18 | ) -> Result { 19 | let mut ble_scan = BLEScan::new(); 20 | 21 | info!("Scanning for BLE devices..."); 22 | 23 | let device = ble_scan 24 | .active_scan(true) 25 | .interval(100) 26 | .window(99) 27 | .start(ble_device, 1000, |device, data| { 28 | if let Some(name) = data.name() { 29 | if name.contains_str("IDM") { 30 | return Some(*device); 31 | } 32 | } 33 | None 34 | }) 35 | .await?; 36 | 37 | if let Some(device) = device { 38 | client.on_connect(|client| { 39 | client.update_conn_params(120, 120, 0, 60).unwrap(); 40 | }); 41 | 42 | client.connect(&device.addr()).await?; 43 | 44 | let service = client 45 | .get_service(uuid128!("000000fa-0000-1000-8000-00805f9b34fb")) 46 | .await?; 47 | 48 | let uuid = uuid128!("0000fa02-0000-1000-8000-00805f9b34fb"); 49 | let characteristic = service.get_characteristic(uuid).await?; 50 | 51 | Ok(Self { characteristic }) 52 | } else { 53 | error!("No device found"); 54 | Err(BLEError::fail().unwrap_err()) 55 | } 56 | } 57 | 58 | pub async fn send_data(&mut self, bytes: &[u8]) -> Result<(), BLEError> { 59 | for (counter, chunk) in bytes.chunks(512).enumerate() { 60 | let succ = self.characteristic.write_value(chunk, true).await; 61 | info!("progress: {}%", (counter * chunk.len()) * 100 / bytes.len()); 62 | 63 | if let Err(succ) = succ { 64 | error!("upload png command error: {:?}", succ); 65 | return Err(succ); 66 | } 67 | } 68 | 69 | Ok(()) 70 | } 71 | } 72 | 73 | pub async fn idotmatrix_stream_task(camera: Camera<'_>) -> Result<()> { 74 | let ble_device = BLEDevice::take(); 75 | let mut ble_client = BLEClient::new(); 76 | 77 | let mut ble_handler = IDMBle::new(ble_device, &mut ble_client).await.unwrap(); 78 | 79 | ble_handler 80 | .send_data(&idotmatrix::IDMCommand::ImageMode(1).to_bytes()) 81 | .await 82 | .unwrap(); 83 | 84 | loop { 85 | camera.get_framebuffer(); 86 | let framebuffer = camera.get_framebuffer(); 87 | 88 | if let Some(framebuffer) = framebuffer { 89 | info!("Creating image"); 90 | 91 | let img = framebuffer_to_img(framebuffer); 92 | 93 | info!("Resizing image"); 94 | let scaled = 95 | image::imageops::resize(&img, 32, 32, image::imageops::FilterType::Lanczos3); 96 | 97 | let mut c = std::io::Cursor::new(Vec::new()); 98 | 99 | info!("Writing png"); 100 | scaled.write_to(&mut c, ImageFormat::Png).unwrap(); 101 | 102 | info!("Creating command"); 103 | let command = idotmatrix::IDMCommand::UploadPng(c.into_inner()); 104 | 105 | info!("Sending command"); 106 | 107 | ble_handler.send_data(&command.to_bytes()).await.unwrap(); 108 | 109 | //tokio::time::sleep(std::time::Duration::from_millis(10)).await; 110 | } else { 111 | log::info!("no framebuffer"); 112 | } 113 | } 114 | //client.disconnect().unwrap(); 115 | 116 | //ble::ble_advertise_task(name, ble_server, ble_advertising).await; 117 | } 118 | 119 | fn framebuffer_to_img(framebuffer: FrameBuffer<'_>) -> ImageBuffer, Vec> { 120 | let data = framebuffer.data(); 121 | ImageBuffer::from_fn( 122 | framebuffer.width() as u32, 123 | framebuffer.height() as u32, 124 | |x, y| match framebuffer.format() { 125 | camera::pixformat_t_PIXFORMAT_RGB565 => { 126 | let pix_addr = (x + y * framebuffer.width() as u32) as usize * 2; 127 | let raw_pixel = u16::from_be_bytes([data[pix_addr], data[pix_addr + 1]]); 128 | 129 | let decoded = rgb565::Rgb565::unpack_565(raw_pixel); 130 | 131 | Rgb([decoded.0, decoded.1, decoded.2]) 132 | } 133 | 134 | camera::pixformat_t_PIXFORMAT_GRAYSCALE => { 135 | let pix_addr = (x + y * framebuffer.width() as u32) as usize; 136 | let raw_pixel = data[pix_addr]; 137 | 138 | Rgb([raw_pixel, raw_pixel, raw_pixel]) 139 | } 140 | 141 | _ => { 142 | panic!("unsupported format"); 143 | } 144 | }, 145 | ) 146 | } 147 | -------------------------------------------------------------------------------- /examples/idotmatrix/main.rs: -------------------------------------------------------------------------------- 1 | use core::panic; 2 | 3 | use anyhow::Result; 4 | 5 | use esp_idf_svc::hal::peripherals::Peripherals; 6 | use espcam::espcam::Camera; 7 | use log::error; 8 | 9 | use crate::idotmatrixble::idotmatrix_stream_task; 10 | 11 | mod idotmatrixble; 12 | 13 | fn main() -> Result<()> { 14 | esp_idf_svc::sys::link_patches(); 15 | esp_idf_svc::log::EspLogger::initialize_default(); 16 | 17 | let peripherals = Peripherals::take().unwrap(); 18 | 19 | let camera = Camera::new( 20 | peripherals.pins.gpio32, 21 | peripherals.pins.gpio0, 22 | peripherals.pins.gpio5, 23 | peripherals.pins.gpio18, 24 | peripherals.pins.gpio19, 25 | peripherals.pins.gpio21, 26 | peripherals.pins.gpio36, 27 | peripherals.pins.gpio39, 28 | peripherals.pins.gpio34, 29 | peripherals.pins.gpio35, 30 | peripherals.pins.gpio25, 31 | peripherals.pins.gpio23, 32 | peripherals.pins.gpio22, 33 | peripherals.pins.gpio26, 34 | peripherals.pins.gpio27, 35 | esp_idf_sys::camera::pixformat_t_PIXFORMAT_RGB565, 36 | esp_idf_sys::camera::framesize_t_FRAMESIZE_240X240, 37 | ) 38 | .unwrap(); 39 | 40 | tokio::runtime::Builder::new_current_thread() 41 | .enable_all() 42 | .build() 43 | .unwrap() 44 | .block_on(async move { 45 | tokio::select! { 46 | ret = idotmatrix_stream_task(camera) => { 47 | if let Err(e) = ret { 48 | error!("ble_task: {}", e) 49 | } 50 | } 51 | } 52 | }); 53 | 54 | loop { 55 | std::thread::sleep(std::time::Duration::from_millis(1000)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/telegram_bot/bot_api.rs: -------------------------------------------------------------------------------- 1 | use embedded_svc::http::{client::Client, Method}; 2 | use esp_idf_hal::io::EspIOError; 3 | use esp_idf_svc::http::client::{Configuration, EspHttpConnection}; 4 | 5 | pub fn http_post(url: impl AsRef, data: &[u8]) -> Result, EspBotError> { 6 | // 1. Create a new EspHttpConnection with default Configuration. (Check documentation) 7 | let configuration = Configuration { 8 | timeout: Some(core::time::Duration::from_secs(130)), 9 | ..Default::default() 10 | }; 11 | 12 | let connection = EspHttpConnection::new(&configuration)?; 13 | // 2. Get a client using the embedded_svc Client::wrap method. (Check documentation) 14 | let mut client = Client::wrap(connection); 15 | 16 | let headers = [("Content-Type", "application/json")]; 17 | 18 | let mut request = client.request(Method::Post, url.as_ref(), &headers)?; 19 | 20 | request.write(data)?; 21 | 22 | // 4. Submit the request and check the status code of the response. 23 | // Successful http status codes are in the 200..=299 range. 24 | let mut response = request.submit()?; 25 | let status = response.status(); 26 | match status { 27 | 200..=299 => { 28 | let mut buf = [0_u8; 4]; 29 | let mut output = Vec::new(); 30 | 31 | loop { 32 | match response.read(&mut buf)? { 33 | 0 => break, 34 | b => { 35 | output.extend_from_slice(&buf[..b]); 36 | } 37 | } 38 | } 39 | 40 | Ok(output) 41 | } 42 | _ => { 43 | let mut buf = [0_u8; 256]; 44 | response.read(buf.as_mut())?; 45 | let resp_string = 46 | core::str::from_utf8(&buf).unwrap_or("invalid utf8 when parsing error"); 47 | 48 | log::error!("{}\n", resp_string); 49 | 50 | Err(EspBotError::Http(HttpError { 51 | _code: status, 52 | _message: format!("response code: {}", status), 53 | })) 54 | } 55 | } 56 | } 57 | 58 | pub fn telegram_post_multipart( 59 | url: impl AsRef, 60 | data: &[u8], 61 | chat_id: i64, 62 | ) -> Result, EspBotError> { 63 | // 1. Create a new EspHttpConnection with default Configuration. (Check documentation) 64 | let config = Configuration::default(); 65 | 66 | let connection = EspHttpConnection::new(&config)?; 67 | // 2. Get a client using the embedded_svc Client::wrap method. (Check documentation) 68 | let mut client = Client::wrap(connection); 69 | 70 | let chat_id = chat_id.to_string(); 71 | 72 | let boundary = "esp32esp32esp32"; 73 | 74 | let head = format!("--{boundary}\r\nContent-Disposition: form-data; name=\"chat_id\"; \r\n\r\n{chat_id}\r\n--{boundary}\r\nContent-Disposition: form-data; name=\"photo\"; filename=\"esp32-cam.jpg\"\r\nContent-Type: image/jpeg\r\n\r\n"); 75 | let tail = format!("\r\n--{boundary}--\r\n"); 76 | 77 | let datalen = head.len() + data.len() + tail.len(); 78 | 79 | // 3. Open a GET request to `url` 80 | let headers = [ 81 | ( 82 | "Content-Type", 83 | &format!("multipart/form-data; boundary={boundary}") as &str, 84 | ), 85 | ("Content-Length", &datalen.to_string()), 86 | ]; 87 | // ANCHOR: request 88 | let mut request = client.post(url.as_ref(), &headers)?; 89 | // ANCHOR_END: request 90 | 91 | request.write(head.as_bytes())?; 92 | request.write(data)?; 93 | request.write(tail.as_bytes())?; 94 | 95 | request.flush()?; 96 | 97 | //esp!(unsafe { esp_http_client_set_post_field(raw_handle, data.as_ptr() as *const i8, data.len() as i32) })?; 98 | 99 | // 4. Submit the request and check the status code of the response. 100 | // Successful http status codes are in the 200..=299 range. 101 | let mut response = request.submit()?; 102 | let status = response.status(); 103 | println!("Response code: {}\n", status); 104 | match status { 105 | 200..=299 => { 106 | let mut buf = Vec::new(); 107 | let mut output = Vec::new(); 108 | 109 | loop { 110 | match response.read(&mut buf)? { 111 | 0 => break, 112 | b => { 113 | output.extend_from_slice(&buf[..b]); 114 | buf.clear(); 115 | } 116 | } 117 | } 118 | 119 | Ok(output) 120 | } 121 | _ => { 122 | let mut buf = [0_u8; 256]; 123 | response.read(buf.as_mut())?; 124 | let resp_string = 125 | core::str::from_utf8(&buf).unwrap_or("invalid utf8 when parsing error"); 126 | 127 | log::error!("{}\n", resp_string); 128 | 129 | Err(EspBotError::Http(HttpError { 130 | _code: status, 131 | _message: format!("response code: {}", status), 132 | })) 133 | } 134 | } 135 | } 136 | 137 | use frankenstein::ErrorResponse; 138 | use frankenstein::TelegramApi; 139 | use std::path::PathBuf; 140 | use thiserror::Error; 141 | 142 | pub struct Esp32Api { 143 | pub api_url: String, 144 | } 145 | 146 | #[derive(Error, Debug)] 147 | pub enum EspBotError { 148 | #[error("HTTP error")] 149 | Http(HttpError), 150 | #[error("API error")] 151 | Api(ErrorResponse), 152 | #[error("ESP error")] 153 | Esp(#[from] esp_idf_sys::EspError), 154 | #[error("IO error")] 155 | Io(#[from] EspIOError), 156 | #[error("utf8 error")] 157 | Json(#[from] core::str::Utf8Error), 158 | #[error("serde error")] 159 | Serde(#[from] serde_json::Error), 160 | } 161 | 162 | #[derive(Debug)] 163 | pub struct HttpError { 164 | pub _code: u16, 165 | pub _message: String, 166 | } 167 | 168 | static BASE_API_URL: &str = "https://api.telegram.org/bot"; 169 | 170 | impl Esp32Api { 171 | #[must_use] 172 | pub fn new(api_key: &str) -> Self { 173 | let api_url = format!("{BASE_API_URL}{api_key}"); 174 | Self { api_url } 175 | } 176 | } 177 | 178 | impl From for EspBotError { 179 | fn from(error: std::io::Error) -> Self { 180 | let message = format!("{error:?}"); 181 | let error = HttpError { 182 | _code: 500, 183 | _message: message, 184 | }; 185 | Self::Http(error) 186 | } 187 | } 188 | 189 | impl TelegramApi for Esp32Api { 190 | type Error = EspBotError; 191 | 192 | fn request( 193 | &self, 194 | method: &str, 195 | params: Option, 196 | ) -> Result { 197 | let url = format!("{}/{method}", self.api_url); 198 | 199 | let response = match params { 200 | None => http_post(url, &[])?, 201 | Some(data) => { 202 | let json = serde_json::to_string(&data)?; 203 | http_post(url, json.as_bytes())? 204 | } 205 | }; 206 | 207 | let text = core::str::from_utf8(&response)?; 208 | 209 | let parsed_result: Result = serde_json::from_str(text); 210 | 211 | parsed_result.map_err(|_| { 212 | let parsed_error: Result = serde_json::from_str(text); 213 | 214 | match parsed_error { 215 | Ok(result) => EspBotError::Api(result), 216 | Err(error) => { 217 | let message = format!("{error:?}"); 218 | let error = HttpError { 219 | _code: 500, 220 | _message: message, 221 | }; 222 | EspBotError::Http(error) 223 | } 224 | } 225 | }) 226 | } 227 | 228 | // isahc doesn't support multipart uploads 229 | // https://github.com/sagebind/isahc/issues/14 230 | fn request_with_form_data( 231 | &self, 232 | _method: &str, 233 | _params: T1, 234 | _files: Vec<(&str, PathBuf)>, 235 | ) -> Result { 236 | let error = HttpError { 237 | _code: 500, 238 | _message: "isahc doesn't support form data requests".to_string(), 239 | }; 240 | 241 | Err(EspBotError::Http(error)) 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /examples/telegram_bot/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use bot_api::{telegram_post_multipart, Esp32Api}; 4 | use esp_idf_hal::{gpio::PinDriver, io::Write}; 5 | use esp_idf_svc::{ 6 | eventloop::EspSystemEventLoop, 7 | hal::peripherals::Peripherals, 8 | http::{server::EspHttpServer, Method}, 9 | }; 10 | use esp_idf_sys::esp_restart; 11 | use espcam::{config::get_config, espcam::Camera, wifi_handler::my_wifi}; 12 | use frankenstein::{ 13 | ForwardMessageParams, GetUpdatesParams, SendChatActionParams, SendMessageParams, TelegramApi, 14 | }; 15 | use log::{error, info}; 16 | 17 | mod bot_api; 18 | 19 | struct BotConfiguration { 20 | should_use_flash: bool, 21 | public_use: bool, 22 | } 23 | 24 | const DEFAULT_CONFIG: BotConfiguration = BotConfiguration { 25 | should_use_flash: true, 26 | public_use: true, 27 | }; 28 | 29 | struct BotState { 30 | config: BotConfiguration, 31 | owner_id: i64, 32 | bot_token: &'static str, 33 | } 34 | 35 | fn main() -> Result<()> { 36 | esp_idf_svc::sys::link_patches(); 37 | esp_idf_svc::log::EspLogger::initialize_default(); 38 | 39 | let sysloop = EspSystemEventLoop::take()?; 40 | 41 | let peripherals = Peripherals::take().unwrap(); 42 | 43 | let mut flash_led = PinDriver::output(peripherals.pins.gpio4).unwrap(); 44 | flash_led.set_low().unwrap(); 45 | 46 | let config = get_config(); 47 | 48 | let wifi = match my_wifi( 49 | config.wifi_ssid, 50 | config.wifi_psk, 51 | peripherals.modem, 52 | sysloop, 53 | ) { 54 | Ok(inner) => inner, 55 | Err(err) => { 56 | error!("Could not connect to Wi-Fi network: {:?}", err); 57 | 58 | for _ in 0..5 { 59 | flash_led.set_high().unwrap(); 60 | std::thread::sleep(std::time::Duration::from_millis(1)); 61 | flash_led.set_low().unwrap(); 62 | std::thread::sleep(std::time::Duration::from_millis(80)); 63 | } 64 | 65 | unsafe { esp_restart() }; 66 | } 67 | }; 68 | 69 | flash_led.set_high().unwrap(); 70 | std::thread::sleep(std::time::Duration::from_millis(1)); 71 | flash_led.set_low().unwrap(); 72 | 73 | let camera = Camera::new( 74 | peripherals.pins.gpio32, 75 | peripherals.pins.gpio0, 76 | peripherals.pins.gpio5, 77 | peripherals.pins.gpio18, 78 | peripherals.pins.gpio19, 79 | peripherals.pins.gpio21, 80 | peripherals.pins.gpio36, 81 | peripherals.pins.gpio39, 82 | peripherals.pins.gpio34, 83 | peripherals.pins.gpio35, 84 | peripherals.pins.gpio25, 85 | peripherals.pins.gpio23, 86 | peripherals.pins.gpio22, 87 | peripherals.pins.gpio26, 88 | peripherals.pins.gpio27, 89 | esp_idf_sys::camera::pixformat_t_PIXFORMAT_JPEG, 90 | esp_idf_sys::camera::framesize_t_FRAMESIZE_UXGA, 91 | ) 92 | .unwrap(); 93 | 94 | let camera = std::sync::Arc::new(camera); 95 | 96 | let mut server = EspHttpServer::new(&esp_idf_svc::http::server::Configuration::default())?; 97 | 98 | let camera2 = camera.clone(); 99 | 100 | server.fn_handler("/camera.jpg", Method::Get, move |request| { 101 | camera2.get_framebuffer(); 102 | // take two frames to get a fresh one 103 | let framebuffer = camera2.get_framebuffer(); 104 | 105 | if let Some(framebuffer) = framebuffer { 106 | let data = framebuffer.data(); 107 | 108 | let headers = [ 109 | ("Content-Type", "image/jpeg"), 110 | ("Content-Length", &data.len().to_string()), 111 | ]; 112 | let mut response = request.into_response(200, Some("OK"), &headers).unwrap(); 113 | response.write_all(data)?; 114 | } else { 115 | let mut response = request.into_ok_response()?; 116 | response.write_all("no framebuffer".as_bytes())?; 117 | } 118 | 119 | Ok::<(), esp_idf_hal::io::EspIOError>(()) 120 | })?; 121 | 122 | let mut bot_state = BotState { 123 | config: DEFAULT_CONFIG, 124 | owner_id: config.bot_owner_id, 125 | bot_token: config.bot_token, 126 | }; 127 | 128 | let api = Esp32Api::new(bot_state.bot_token); 129 | 130 | let send_owner_info = |bot_state: &BotState| { 131 | let mut rssi = 0; 132 | unsafe { 133 | esp_idf_sys::esp_wifi_sta_get_rssi(&mut rssi); 134 | } 135 | api.send_message( 136 | &SendMessageParams::builder() 137 | .chat_id(bot_state.owner_id) 138 | .text(format!( 139 | "Camera OK!\nUse /publish to toggle public use!\nIP: {}\nRSSI: {}\nflash: {}\npublic use: {}", 140 | wifi.sta_netif().get_ip_info().unwrap().ip, 141 | rssi, 142 | bot_state.config.should_use_flash, 143 | bot_state.config.public_use 144 | )) 145 | .build(), 146 | ) 147 | .ok(); 148 | }; 149 | 150 | send_owner_info(&bot_state); 151 | 152 | let updates = api 153 | .get_updates(&GetUpdatesParams::builder().limit(1u32).offset(-1).build()) 154 | .unwrap(); 155 | 156 | let mut offset = if let Some(update) = updates.result.first() { 157 | update.update_id as i64 + 1 158 | } else { 159 | 0 160 | }; 161 | 162 | loop { 163 | let updates = api 164 | .get_updates( 165 | &GetUpdatesParams::builder() 166 | .timeout(120u32) 167 | .limit(1u32) 168 | .offset(offset) 169 | .build(), 170 | ) 171 | .unwrap(); 172 | 173 | for update in updates.result { 174 | offset = update.update_id as i64 + 1; 175 | 176 | if let frankenstein::UpdateContent::Message(message) = update.content { 177 | info!( 178 | "message id {} from chat {}", 179 | message.message_id, message.chat.id 180 | ); 181 | 182 | match message.text.unwrap_or_default().as_str() { 183 | "/photo" => { 184 | if message.chat.id != bot_state.owner_id && !bot_state.config.public_use { 185 | continue; 186 | } 187 | 188 | api.send_chat_action( 189 | &SendChatActionParams::builder() 190 | .chat_id(message.chat.id) 191 | .action(frankenstein::ChatAction::UploadPhoto) 192 | .build(), 193 | ) 194 | .ok(); 195 | 196 | if bot_state.config.should_use_flash { 197 | flash_led.set_high().unwrap(); 198 | } 199 | 200 | camera.get_framebuffer(); 201 | let framebuffer = camera.get_framebuffer(); 202 | 203 | flash_led.set_low().unwrap(); 204 | 205 | if let Some(framebuffer) = framebuffer { 206 | let res = telegram_post_multipart( 207 | format!( 208 | "https://api.telegram.org/bot{}/sendPhoto", 209 | bot_state.bot_token 210 | ), 211 | framebuffer.data(), 212 | message.chat.id, 213 | ); 214 | 215 | match res { 216 | Ok(_) => {} 217 | Err(err) => { 218 | error!("http_get error: {:?}", err); 219 | } 220 | } 221 | } else { 222 | log::info!("no framebuffer"); 223 | } 224 | } 225 | 226 | "/flash" => { 227 | if message.chat.id != bot_state.owner_id && !bot_state.config.public_use { 228 | continue; 229 | } 230 | if bot_state.config.should_use_flash { 231 | bot_state.config.should_use_flash = false; 232 | 233 | api.send_message( 234 | &SendMessageParams::builder() 235 | .chat_id(message.chat.id) 236 | .text("Flash disabled!") 237 | .build(), 238 | ) 239 | .unwrap(); 240 | } else { 241 | bot_state.config.should_use_flash = true; 242 | 243 | api.send_message( 244 | &SendMessageParams::builder() 245 | .chat_id(message.chat.id) 246 | .text("Flash enabled!") 247 | .build(), 248 | ) 249 | .unwrap(); 250 | } 251 | } 252 | 253 | "/publish" => { 254 | if message.chat.id != bot_state.owner_id { 255 | continue; 256 | } 257 | if bot_state.config.public_use { 258 | bot_state.config.public_use = false; 259 | 260 | api.send_message( 261 | &SendMessageParams::builder() 262 | .chat_id(message.chat.id) 263 | .text("Public use disabled!") 264 | .build(), 265 | ) 266 | .unwrap(); 267 | } else { 268 | bot_state.config.public_use = true; 269 | 270 | api.send_message( 271 | &SendMessageParams::builder() 272 | .chat_id(message.chat.id) 273 | .text("Public use enabled!") 274 | .build(), 275 | ) 276 | .unwrap(); 277 | } 278 | } 279 | "/start" | "/help" => { 280 | api.send_message( 281 | &SendMessageParams::builder() 282 | .chat_id(message.chat.id) 283 | .text("Hello!\nUse /photo to take a photo!\nUse /flash to toggle flash!") 284 | .build(), 285 | ) 286 | .ok(); 287 | 288 | if message.chat.id == bot_state.owner_id { 289 | send_owner_info(&bot_state); 290 | } 291 | } 292 | _ => {} 293 | } 294 | 295 | if message.chat.type_field == frankenstein::ChatType::Private { 296 | api.forward_message( 297 | &ForwardMessageParams::builder() 298 | .chat_id(bot_state.owner_id) 299 | .from_chat_id(message.chat.id) 300 | .message_id(message.message_id) 301 | .build(), 302 | ) 303 | .ok(); 304 | } 305 | } 306 | } 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /examples/webserver.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | 3 | use esp_idf_hal::io::{EspIOError, Write}; 4 | use esp_idf_svc::{ 5 | eventloop::EspSystemEventLoop, 6 | hal::peripherals::Peripherals, 7 | http::{server::EspHttpServer, Method}, 8 | }; 9 | use espcam::{config::get_config, espcam::Camera, wifi_handler::my_wifi}; 10 | 11 | fn main() -> Result<()> { 12 | esp_idf_svc::sys::link_patches(); 13 | esp_idf_svc::log::EspLogger::initialize_default(); 14 | 15 | let sysloop = EspSystemEventLoop::take()?; 16 | 17 | let peripherals = Peripherals::take().unwrap(); 18 | 19 | let config = get_config(); 20 | 21 | let _wifi = match my_wifi( 22 | config.wifi_ssid, 23 | config.wifi_psk, 24 | peripherals.modem, 25 | sysloop, 26 | ) { 27 | Ok(inner) => inner, 28 | Err(err) => { 29 | bail!("Could not connect to Wi-Fi network: {:?}", err) 30 | } 31 | }; 32 | 33 | let camera = Camera::new( 34 | peripherals.pins.gpio32, 35 | peripherals.pins.gpio0, 36 | peripherals.pins.gpio5, 37 | peripherals.pins.gpio18, 38 | peripherals.pins.gpio19, 39 | peripherals.pins.gpio21, 40 | peripherals.pins.gpio36, 41 | peripherals.pins.gpio39, 42 | peripherals.pins.gpio34, 43 | peripherals.pins.gpio35, 44 | peripherals.pins.gpio25, 45 | peripherals.pins.gpio23, 46 | peripherals.pins.gpio22, 47 | peripherals.pins.gpio26, 48 | peripherals.pins.gpio27, 49 | esp_idf_sys::camera::pixformat_t_PIXFORMAT_JPEG, 50 | esp_idf_sys::camera::framesize_t_FRAMESIZE_UXGA, 51 | ) 52 | .unwrap(); 53 | 54 | let mut server = EspHttpServer::new(&esp_idf_svc::http::server::Configuration::default())?; 55 | 56 | server.fn_handler("/camera.jpg", Method::Get, move |request| { 57 | camera.get_framebuffer(); 58 | // take two frames to get a fresh one 59 | let framebuffer = camera.get_framebuffer(); 60 | 61 | if let Some(framebuffer) = framebuffer { 62 | let data = framebuffer.data(); 63 | 64 | let headers = [ 65 | ("Content-Type", "image/jpeg"), 66 | ("Content-Length", &data.len().to_string()), 67 | ]; 68 | let mut response = request.into_response(200, Some("OK"), &headers).unwrap(); 69 | response.write_all(data)?; 70 | } else { 71 | let mut response = request.into_ok_response()?; 72 | response.write_all("no framebuffer".as_bytes())?; 73 | } 74 | 75 | Ok::<(), EspIOError>(()) 76 | })?; 77 | 78 | server.fn_handler("/", Method::Get, |request| { 79 | let mut response = request.into_ok_response()?; 80 | response.write_all("ok".as_bytes())?; 81 | Ok::<(), EspIOError>(()) 82 | })?; 83 | 84 | loop { 85 | std::thread::sleep(std::time::Duration::from_millis(1000)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "esp" 3 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | # Rust often needs a bit of an extra main task stack size compared to C (the default is 3K) 2 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000 3 | 4 | # Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default). 5 | # This allows to use 1 ms granuality for thread sleeps (10 ms by default). 6 | #CONFIG_FREERTOS_HZ=1000 7 | 8 | # Workaround for https://github.com/espressif/esp-idf/issues/7631 9 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n 10 | #CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n 11 | 12 | CONFIG_ESP32_SPIRAM_SUPPORT=y 13 | 14 | CONFIG_BT_ENABLED=y 15 | CONFIG_BT_BLE_ENABLED=y 16 | CONFIG_BT_BLUEDROID_ENABLED=n 17 | CONFIG_BT_NIMBLE_ENABLED=y 18 | CONFIG_ESP_MAIN_TASK_STACK_SIZE=40960 19 | 20 | CONFIG_ESP_TLS_INSECURE=y 21 | CONFIG_ESP_TLS_SKIP_SERVER_CERT_VERIFY=y -------------------------------------------------------------------------------- /src/ble.rs: -------------------------------------------------------------------------------- 1 | use esp32_nimble::utilities::BleUuid; 2 | use esp32_nimble::{BLEAdvertising, BLEServer, NimbleProperties}; 3 | use lazy_static::lazy_static; 4 | use log::warn; 5 | 6 | pub static UUID_BLE_SERVICE_STR: &str = "io.test.ble"; // up-to 16 bytes 7 | pub static UUID_BLE_UPTIME_CHARA_STR: &str = "uptime"; // up-to 16 bytes 8 | 9 | lazy_static! { 10 | pub static ref UUID_BLE_SERVICE: BleUuid = str_to_uuid(UUID_BLE_SERVICE_STR); 11 | pub static ref UUID_BLE_UPTIME_CHARA: BleUuid = str_to_uuid(UUID_BLE_UPTIME_CHARA_STR); 12 | } 13 | 14 | pub fn str_to_uuid(s: &str) -> BleUuid { 15 | let mut arr = [0u8; 16]; 16 | for (idx, char) in s.as_bytes().iter().enumerate() { 17 | if idx < 16 { 18 | arr[idx] = *char 19 | } else { 20 | warn!("uuid string is longer than 16 bytes!"); 21 | break; 22 | } 23 | } 24 | BleUuid::from_uuid128(arr) 25 | } 26 | 27 | pub async fn ble_advertise_task( 28 | name: &str, 29 | server: &mut BLEServer, 30 | advertising: &mut BLEAdvertising, 31 | ) { 32 | server.on_connect(|server, desc| { 33 | server 34 | .update_conn_params(desc.conn_handle(), 24, 48, 0, 60) 35 | .expect("server.update_conn_params"); 36 | }); 37 | // server.on_disconnect(|desc, reason| { 38 | // info!("Client disconnected ({:X})", reason); 39 | // }); 40 | let service = server.create_service(*UUID_BLE_SERVICE); 41 | 42 | let notifying_characteristic = service.lock().create_characteristic( 43 | *UUID_BLE_UPTIME_CHARA, 44 | NimbleProperties::READ | NimbleProperties::NOTIFY, 45 | ); 46 | notifying_characteristic.lock().set_value(b"uptime: 0"); 47 | 48 | advertising 49 | .set_data( 50 | esp32_nimble::BLEAdvertisementData::new() 51 | .name(name) 52 | .add_service_uuid(*UUID_BLE_SERVICE), 53 | ) 54 | .unwrap(); 55 | 56 | // advertising 57 | // .set_data( 58 | // BLEAdvertisementData::new() 59 | // .name(name) 60 | // .add_service_uuid(*UUID_BLE_SERVICE), 61 | // ) 62 | // .unwrap(); 63 | 64 | advertising.start().expect("ble_advertising.start()"); 65 | 66 | let mut counter: u128 = 0; 67 | 68 | loop { 69 | tokio::time::sleep(std::time::Duration::from_secs(1)).await; 70 | 71 | let mut guard = notifying_characteristic.lock(); 72 | guard 73 | .set_value(format!("uptime: {counter}").as_bytes()) 74 | .notify(); 75 | drop(guard); 76 | 77 | counter += 1; 78 | } 79 | } 80 | 81 | // pub async fn do_ble_scan( 82 | // ble_scan: &mut BLEScan, 83 | // ) -> Result, anyhow::Error> { 84 | // let (tx, rx) = tokio::sync::oneshot::channel::<()>(); 85 | // let mut tx = Some(tx); 86 | 87 | // ble_scan 88 | // .active_scan(true) 89 | // .filter_duplicates(true) 90 | // .limited(false) 91 | // .interval(100) 92 | // .window(99) 93 | // .on_completed(move || { 94 | // let tx = tx.take(); 95 | // if let Some(tx) = tx { 96 | // let _ = tx.send(()); 97 | // } 98 | // }); 99 | // ble_scan 100 | // .start(10000) 101 | // .await 102 | // .map_err(|e| anyhow!("ble_scan.start: {:?}", e))?; 103 | 104 | // select! { 105 | // _ = sleep(Duration::from_secs(15)) => { 106 | // bail!("ble scan timed out!"); 107 | // } 108 | // _ = rx => { 109 | // info!("Scan finished"); 110 | // } 111 | // }; 112 | // let result = ble_scan.get_results().cloned().collect::>(); 113 | 114 | // Ok(result) 115 | // } 116 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct Config { 3 | pub wifi_ssid: &'static str, 4 | pub wifi_psk: &'static str, 5 | pub bot_token: &'static str, 6 | pub bot_owner_id: i64, 7 | } 8 | 9 | include!("wifi_config.rs"); 10 | -------------------------------------------------------------------------------- /src/espcam.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use esp_idf_hal::gpio::*; 4 | use esp_idf_hal::peripheral::Peripheral; 5 | use esp_idf_sys::*; 6 | 7 | pub struct FrameBuffer<'a> { 8 | fb: *mut camera::camera_fb_t, 9 | _p: PhantomData<&'a camera::camera_fb_t>, 10 | } 11 | 12 | impl<'a> FrameBuffer<'a> { 13 | pub fn data(&self) -> &'a [u8] { 14 | unsafe { std::slice::from_raw_parts((*self.fb).buf, (*self.fb).len) } 15 | } 16 | 17 | pub fn width(&self) -> usize { 18 | unsafe { (*self.fb).width } 19 | } 20 | 21 | pub fn height(&self) -> usize { 22 | unsafe { (*self.fb).height } 23 | } 24 | 25 | pub fn format(&self) -> camera::pixformat_t { 26 | unsafe { (*self.fb).format } 27 | } 28 | 29 | pub fn timestamp(&self) -> camera::timeval { 30 | unsafe { (*self.fb).timestamp } 31 | } 32 | 33 | pub fn fb_return(&self) { 34 | unsafe { camera::esp_camera_fb_return(self.fb) } 35 | } 36 | } 37 | 38 | impl Drop for FrameBuffer<'_> { 39 | fn drop(&mut self) { 40 | self.fb_return(); 41 | } 42 | } 43 | 44 | pub struct CameraSensor<'a> { 45 | sensor: *mut camera::sensor_t, 46 | _p: PhantomData<&'a camera::sensor_t>, 47 | } 48 | 49 | impl<'a> CameraSensor<'a> { 50 | pub fn init_status(&self) -> Result<(), EspError> { 51 | esp!(unsafe { (*self.sensor).init_status.unwrap()(self.sensor) }) 52 | } 53 | pub fn reset(&self) -> Result<(), EspError> { 54 | esp!(unsafe { (*self.sensor).reset.unwrap()(self.sensor) }) 55 | } 56 | pub fn set_pixformat(&self, format: camera::pixformat_t) -> Result<(), EspError> { 57 | esp!(unsafe { (*self.sensor).set_pixformat.unwrap()(self.sensor, format) }) 58 | } 59 | pub fn set_framesize(&self, framesize: camera::framesize_t) -> Result<(), EspError> { 60 | esp!(unsafe { (*self.sensor).set_framesize.unwrap()(self.sensor, framesize) }) 61 | } 62 | pub fn set_contrast(&self, level: i32) -> Result<(), EspError> { 63 | esp!(unsafe { (*self.sensor).set_contrast.unwrap()(self.sensor, level) }) 64 | } 65 | pub fn set_brightness(&self, level: i32) -> Result<(), EspError> { 66 | esp!(unsafe { (*self.sensor).set_brightness.unwrap()(self.sensor, level) }) 67 | } 68 | pub fn set_saturation(&self, level: i32) -> Result<(), EspError> { 69 | esp!(unsafe { (*self.sensor).set_saturation.unwrap()(self.sensor, level) }) 70 | } 71 | pub fn set_sharpness(&self, level: i32) -> Result<(), EspError> { 72 | esp!(unsafe { (*self.sensor).set_sharpness.unwrap()(self.sensor, level) }) 73 | } 74 | pub fn set_denoise(&self, level: i32) -> Result<(), EspError> { 75 | esp!(unsafe { (*self.sensor).set_denoise.unwrap()(self.sensor, level) }) 76 | } 77 | pub fn set_gainceiling(&self, gainceiling: camera::gainceiling_t) -> Result<(), EspError> { 78 | esp!(unsafe { (*self.sensor).set_gainceiling.unwrap()(self.sensor, gainceiling) }) 79 | } 80 | pub fn set_quality(&self, quality: i32) -> Result<(), EspError> { 81 | esp!(unsafe { (*self.sensor).set_quality.unwrap()(self.sensor, quality) }) 82 | } 83 | pub fn set_colorbar(&self, enable: bool) -> Result<(), EspError> { 84 | esp!(unsafe { 85 | (*self.sensor).set_colorbar.unwrap()(self.sensor, if enable { 1 } else { 0 }) 86 | }) 87 | } 88 | pub fn set_whitebal(&self, enable: bool) -> Result<(), EspError> { 89 | esp!(unsafe { 90 | (*self.sensor).set_whitebal.unwrap()(self.sensor, if enable { 1 } else { 0 }) 91 | }) 92 | } 93 | pub fn set_gain_ctrl(&self, enable: bool) -> Result<(), EspError> { 94 | esp!(unsafe { 95 | (*self.sensor).set_gain_ctrl.unwrap()(self.sensor, if enable { 1 } else { 0 }) 96 | }) 97 | } 98 | pub fn set_exposure_ctrl(&self, enable: bool) -> Result<(), EspError> { 99 | esp!(unsafe { 100 | (*self.sensor).set_exposure_ctrl.unwrap()(self.sensor, if enable { 1 } else { 0 }) 101 | }) 102 | } 103 | pub fn set_hmirror(&self, enable: bool) -> Result<(), EspError> { 104 | esp!(unsafe { 105 | (*self.sensor).set_hmirror.unwrap()(self.sensor, if enable { 1 } else { 0 }) 106 | }) 107 | } 108 | pub fn set_vflip(&self, enable: bool) -> Result<(), EspError> { 109 | esp!(unsafe { (*self.sensor).set_vflip.unwrap()(self.sensor, if enable { 1 } else { 0 }) }) 110 | } 111 | pub fn set_aec2(&self, enable: bool) -> Result<(), EspError> { 112 | esp!(unsafe { (*self.sensor).set_aec2.unwrap()(self.sensor, if enable { 1 } else { 0 }) }) 113 | } 114 | pub fn set_awb_gain(&self, enable: bool) -> Result<(), EspError> { 115 | esp!(unsafe { 116 | (*self.sensor).set_awb_gain.unwrap()(self.sensor, if enable { 1 } else { 0 }) 117 | }) 118 | } 119 | pub fn set_agc_gain(&self, gain: i32) -> Result<(), EspError> { 120 | esp!(unsafe { (*self.sensor).set_agc_gain.unwrap()(self.sensor, gain) }) 121 | } 122 | pub fn set_aec_value(&self, gain: i32) -> Result<(), EspError> { 123 | esp!(unsafe { (*self.sensor).set_aec_value.unwrap()(self.sensor, gain) }) 124 | } 125 | pub fn set_special_effect(&self, effect: i32) -> Result<(), EspError> { 126 | esp!(unsafe { (*self.sensor).set_special_effect.unwrap()(self.sensor, effect) }) 127 | } 128 | pub fn set_wb_mode(&self, mode: i32) -> Result<(), EspError> { 129 | esp!(unsafe { (*self.sensor).set_wb_mode.unwrap()(self.sensor, mode) }) 130 | } 131 | pub fn set_ae_level(&self, level: i32) -> Result<(), EspError> { 132 | esp!(unsafe { (*self.sensor).set_ae_level.unwrap()(self.sensor, level) }) 133 | } 134 | pub fn set_dcw(&self, enable: bool) -> Result<(), EspError> { 135 | esp!(unsafe { (*self.sensor).set_dcw.unwrap()(self.sensor, if enable { 1 } else { 0 }) }) 136 | } 137 | pub fn set_bpc(&self, enable: bool) -> Result<(), EspError> { 138 | esp!(unsafe { (*self.sensor).set_bpc.unwrap()(self.sensor, if enable { 1 } else { 0 }) }) 139 | } 140 | pub fn set_wpc(&self, enable: bool) -> Result<(), EspError> { 141 | esp!(unsafe { (*self.sensor).set_wpc.unwrap()(self.sensor, if enable { 1 } else { 0 }) }) 142 | } 143 | pub fn set_raw_gma(&self, enable: bool) -> Result<(), EspError> { 144 | esp!(unsafe { 145 | (*self.sensor).set_raw_gma.unwrap()(self.sensor, if enable { 1 } else { 0 }) 146 | }) 147 | } 148 | pub fn set_lenc(&self, enable: bool) -> Result<(), EspError> { 149 | esp!(unsafe { (*self.sensor).set_lenc.unwrap()(self.sensor, if enable { 1 } else { 0 }) }) 150 | } 151 | pub fn get_reg(&self, reg: i32, mask: i32) -> Result<(), EspError> { 152 | esp!(unsafe { (*self.sensor).get_reg.unwrap()(self.sensor, reg, mask) }) 153 | } 154 | pub fn set_reg(&self, reg: i32, mask: i32, value: i32) -> Result<(), EspError> { 155 | esp!(unsafe { (*self.sensor).set_reg.unwrap()(self.sensor, reg, mask, value) }) 156 | } 157 | pub fn set_res_raw( 158 | &self, 159 | start_x: i32, 160 | start_y: i32, 161 | end_x: i32, 162 | end_y: i32, 163 | offset_x: i32, 164 | offset_y: i32, 165 | total_x: i32, 166 | total_y: i32, 167 | output_x: i32, 168 | output_y: i32, 169 | scale: bool, 170 | binning: bool, 171 | ) -> Result<(), EspError> { 172 | esp!(unsafe { 173 | (*self.sensor).set_res_raw.unwrap()( 174 | self.sensor, 175 | start_x, 176 | start_y, 177 | end_x, 178 | end_y, 179 | offset_x, 180 | offset_y, 181 | total_x, 182 | total_y, 183 | output_x, 184 | output_y, 185 | scale, 186 | binning, 187 | ) 188 | }) 189 | } 190 | pub fn set_pll( 191 | &self, 192 | bypass: i32, 193 | mul: i32, 194 | sys: i32, 195 | root: i32, 196 | pre: i32, 197 | seld5: i32, 198 | pclken: i32, 199 | pclk: i32, 200 | ) -> Result<(), EspError> { 201 | esp!(unsafe { 202 | (*self.sensor).set_pll.unwrap()( 203 | self.sensor, 204 | bypass, 205 | mul, 206 | sys, 207 | root, 208 | pre, 209 | seld5, 210 | pclken, 211 | pclk, 212 | ) 213 | }) 214 | } 215 | pub fn set_xclk(&self, timer: i32, xclk: i32) -> Result<(), EspError> { 216 | esp!(unsafe { (*self.sensor).set_xclk.unwrap()(self.sensor, timer, xclk) }) 217 | } 218 | } 219 | 220 | pub struct Camera<'a> { 221 | _p: PhantomData<&'a ()>, 222 | } 223 | 224 | impl<'a> Camera<'a> { 225 | pub fn new( 226 | pin_pwdn: impl Peripheral

+ 'a, 227 | pin_xclk: impl Peripheral

+ 'a, 228 | pin_d0: impl Peripheral

+ 'a, 229 | pin_d1: impl Peripheral

+ 'a, 230 | pin_d2: impl Peripheral

+ 'a, 231 | pin_d3: impl Peripheral

+ 'a, 232 | pin_d4: impl Peripheral

+ 'a, 233 | pin_d5: impl Peripheral

+ 'a, 234 | pin_d6: impl Peripheral

+ 'a, 235 | pin_d7: impl Peripheral

+ 'a, 236 | pin_vsync: impl Peripheral

+ 'a, 237 | pin_href: impl Peripheral

+ 'a, 238 | pin_pclk: impl Peripheral

+ 'a, 239 | pin_sda: impl Peripheral

+ 'a, 240 | pin_scl: impl Peripheral

+ 'a, 241 | pixel_format: camera::pixformat_t, 242 | frame_size: camera::framesize_t, 243 | ) -> Result { 244 | esp_idf_hal::into_ref!( 245 | pin_pwdn, pin_xclk, pin_d0, pin_d1, pin_d2, pin_d3, pin_d4, pin_d5, pin_d6, pin_d7, 246 | pin_vsync, pin_href, pin_pclk, pin_sda, pin_scl 247 | ); 248 | let config = camera::camera_config_t { 249 | pin_pwdn: pin_pwdn.pin(), 250 | pin_xclk: pin_xclk.pin(), 251 | pin_reset: 0xff, 252 | 253 | pin_d0: pin_d0.pin(), 254 | pin_d1: pin_d1.pin(), 255 | pin_d2: pin_d2.pin(), 256 | pin_d3: pin_d3.pin(), 257 | pin_d4: pin_d4.pin(), 258 | pin_d5: pin_d5.pin(), 259 | pin_d6: pin_d6.pin(), 260 | pin_d7: pin_d7.pin(), 261 | pin_vsync: pin_vsync.pin(), 262 | pin_href: pin_href.pin(), 263 | pin_pclk: pin_pclk.pin(), 264 | 265 | xclk_freq_hz: 20000000, 266 | ledc_timer: esp_idf_sys::ledc_timer_t_LEDC_TIMER_0, 267 | ledc_channel: esp_idf_sys::ledc_channel_t_LEDC_CHANNEL_0, 268 | 269 | pixel_format, 270 | frame_size, 271 | 272 | jpeg_quality: 12, 273 | fb_count: 1, 274 | grab_mode: camera::camera_grab_mode_t_CAMERA_GRAB_WHEN_EMPTY, 275 | 276 | fb_location: camera::camera_fb_location_t_CAMERA_FB_IN_PSRAM, 277 | 278 | __bindgen_anon_1: camera::camera_config_t__bindgen_ty_1 { 279 | pin_sccb_sda: pin_sda.pin(), 280 | }, 281 | __bindgen_anon_2: camera::camera_config_t__bindgen_ty_2 { 282 | pin_sccb_scl: pin_scl.pin(), 283 | }, 284 | 285 | ..Default::default() 286 | }; 287 | 288 | esp_idf_sys::esp!(unsafe { camera::esp_camera_init(&config) })?; 289 | Ok(Self { _p: PhantomData }) 290 | } 291 | 292 | pub fn get_framebuffer(&self) -> Option { 293 | let fb = unsafe { camera::esp_camera_fb_get() }; 294 | if fb.is_null() { 295 | //unsafe { camera::esp_camera_fb_return(fb); } 296 | None 297 | } else { 298 | Some(FrameBuffer { 299 | fb, 300 | _p: PhantomData, 301 | }) 302 | } 303 | } 304 | 305 | pub fn sensor(&self) -> CameraSensor<'a> { 306 | CameraSensor { 307 | sensor: unsafe { camera::esp_camera_sensor_get() }, 308 | _p: PhantomData, 309 | } 310 | } 311 | } 312 | 313 | impl<'a> Drop for Camera<'a> { 314 | fn drop(&mut self) { 315 | esp!(unsafe { camera::esp_camera_deinit() }).expect("error during esp_camera_deinit") 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod ble; 2 | pub mod config; 3 | pub mod espcam; 4 | pub mod wifi_handler; 5 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use esp_idf_svc::hal::peripherals::Peripherals; 4 | use espcam::espcam::Camera; 5 | 6 | fn main() -> Result<()> { 7 | esp_idf_svc::sys::link_patches(); 8 | esp_idf_svc::log::EspLogger::initialize_default(); 9 | 10 | let peripherals = Peripherals::take().unwrap(); 11 | 12 | let camera = Camera::new( 13 | peripherals.pins.gpio32, 14 | peripherals.pins.gpio0, 15 | peripherals.pins.gpio5, 16 | peripherals.pins.gpio18, 17 | peripherals.pins.gpio19, 18 | peripherals.pins.gpio21, 19 | peripherals.pins.gpio36, 20 | peripherals.pins.gpio39, 21 | peripherals.pins.gpio34, 22 | peripherals.pins.gpio35, 23 | peripherals.pins.gpio25, 24 | peripherals.pins.gpio23, 25 | peripherals.pins.gpio22, 26 | peripherals.pins.gpio26, 27 | peripherals.pins.gpio27, 28 | esp_idf_sys::camera::pixformat_t_PIXFORMAT_JPEG, 29 | esp_idf_sys::camera::framesize_t_FRAMESIZE_UXGA, 30 | ) 31 | .unwrap(); 32 | 33 | loop { 34 | let framebuffer = camera.get_framebuffer(); 35 | 36 | if let Some(framebuffer) = framebuffer { 37 | println!("Got framebuffer!"); 38 | println!("width: {}", framebuffer.width()); 39 | println!("height: {}", framebuffer.height()); 40 | println!("len: {}", framebuffer.data().len()); 41 | println!("format: {}", framebuffer.format()); 42 | 43 | std::thread::sleep(std::time::Duration::from_millis(1000)); 44 | } else { 45 | log::info!("no framebuffer"); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/wifi_config.rs.example: -------------------------------------------------------------------------------- 1 | pub fn get_config() -> Config { 2 | Config { 3 | wifi_ssid: "my_wifi_ssid", 4 | wifi_psk: "my_wifi_psk", 5 | bot_token: "my_bot_token", 6 | bot_owner_id: 1234567890, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/wifi_handler.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use esp_idf_svc::{ 3 | eventloop::EspSystemEventLoop, 4 | hal::peripheral, 5 | wifi::{AuthMethod, BlockingWifi, ClientConfiguration, Configuration, EspWifi}, 6 | }; 7 | use log::info; 8 | 9 | pub fn my_wifi( 10 | ssid: &str, 11 | pass: &str, 12 | modem: impl peripheral::Peripheral

+ 'static, 13 | sysloop: EspSystemEventLoop, 14 | ) -> Result>> { 15 | let mut auth_method = AuthMethod::WPA2Personal; 16 | if ssid.is_empty() { 17 | bail!("Missing WiFi name") 18 | } 19 | if pass.is_empty() { 20 | auth_method = AuthMethod::None; 21 | info!("Wifi password is empty"); 22 | } 23 | let mut esp_wifi = EspWifi::new(modem, sysloop.clone(), None)?; 24 | 25 | let mut wifi = BlockingWifi::wrap(&mut esp_wifi, sysloop)?; 26 | 27 | wifi.set_configuration(&Configuration::Client(ClientConfiguration::default()))?; 28 | 29 | info!("Starting wifi..."); 30 | 31 | wifi.start()?; 32 | 33 | info!("Scanning..."); 34 | 35 | let ap_infos = wifi.scan()?; 36 | 37 | let ours = ap_infos.into_iter().find(|a| a.ssid == ssid); 38 | 39 | let channel = if let Some(ours) = ours { 40 | info!( 41 | "Found configured access point {} on channel {}", 42 | ssid, ours.channel 43 | ); 44 | Some(ours.channel) 45 | } else { 46 | return Err(anyhow::anyhow!( 47 | "Configured access point {} not found during scanning", 48 | ssid 49 | )); 50 | }; 51 | 52 | wifi.set_configuration(&Configuration::Client(ClientConfiguration { 53 | ssid: ssid 54 | .try_into() 55 | .expect("SSID could not be converted to heapless String"), 56 | password: pass 57 | .try_into() 58 | .expect("Password could not be converted to heapless String"), 59 | channel, 60 | auth_method, 61 | ..Default::default() 62 | }))?; 63 | 64 | info!("Connecting wifi..."); 65 | 66 | wifi.connect()?; 67 | 68 | info!("Waiting for DHCP lease..."); 69 | 70 | wifi.wait_netif_up()?; 71 | 72 | let ip_info = wifi.wifi().sta_netif().get_ip_info()?; 73 | 74 | info!("Wifi DHCP info: {:?}", ip_info); 75 | 76 | Ok(Box::new(esp_wifi)) 77 | } 78 | --------------------------------------------------------------------------------