diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 7affc7a..6b1e51b 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -40,7 +40,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -65,13 +65,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] -name = "basic-toml" -version = "0.1.10" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" -dependencies = [ - "serde", -] +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bincode" @@ -107,7 +104,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn", + "syn 2.0.117", ] [[package]] @@ -127,7 +124,7 @@ checksum = "f48d6ace212fdf1b45fd6b566bb40808415344642b76c3224c07c8df9da81e97" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -142,6 +139,15 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "bq34z100" version = "0.4.0" @@ -174,7 +180,7 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -252,6 +258,22 @@ dependencies = [ "libloading", ] +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + +[[package]] +name = "cordyceps" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "688d7fbb8092b8de775ef2536f36c8c31f2bc4006ece2e8d8ad2d17d00ce0a2a" +dependencies = [ + "loom", + "tracing", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -330,7 +352,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.117", ] [[package]] @@ -343,7 +365,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -356,7 +378,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.117", ] [[package]] @@ -367,7 +389,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -378,7 +400,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -389,48 +411,7 @@ checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" dependencies = [ "darling_core 0.23.0", "quote", - "syn", -] - -[[package]] -name = "defmt" -version = "0.3.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" -dependencies = [ - "defmt 1.0.1", -] - -[[package]] -name = "defmt" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" -dependencies = [ - "defmt-parser", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "defmt-parser" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" -dependencies = [ - "thiserror", + "syn 2.0.117", ] [[package]] @@ -441,7 +422,7 @@ checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -468,16 +449,37 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ + "block-buffer", "crypto-common", ] +[[package]] +name = "docsplay" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8547ea80db62c5bb9d7796fcce5e6e07d1136bdc1a02269095061e806758fab4" +dependencies = [ + "docsplay-macros", +] + +[[package]] +name = "docsplay-macros" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11772ed3eb3db124d826f3abeadf5a791a557f62c19b123e3f07288158a71fdd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "document-features" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ - "litrs 1.0.0", + "litrs", ] [[package]] @@ -492,32 +494,32 @@ dependencies = [ [[package]] name = "edge-dhcp" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ccd3a181a33c710e07c3f04623d7a11e9969b1e44a7276ead7873b049720cb" +checksum = "2e0b32c831ced877a78378312fe0b6f7cdd5759f3ba272578f582ff9bba5291d" dependencies = [ "edge-nal", "edge-raw", "embassy-futures", - "embassy-time 0.4.0", - "heapless 0.8.0", + "embassy-time", + "heapless 0.9.3", "num_enum", - "rand_core 0.6.4", + "rand_core 0.9.5", ] [[package]] name = "edge-http" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f9b24aa48b6e43e6316a02bed8291f8a856cb6d4e63a2f533d35d74d04141d" +checksum = "7b983fa8c1e7fa8a104583f0798fecdda484b9976b4638b0b6d309cd0a87720e" dependencies = [ - "base64", + "base64 0.22.1", "edge-nal", "embassy-futures", "embassy-sync 0.7.2", - "embassy-time 0.4.0", - "embedded-io-async 0.6.1", - "heapless 0.8.0", + "embassy-time", + "embedded-io-async 0.7.0", + "heapless 0.9.3", "httparse", "log", "sha1_smol", @@ -525,32 +527,32 @@ dependencies = [ [[package]] name = "edge-nal" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac19c3edcdad839c71cb919cd09a632d9915d630760b37f0b74290188c08f248" +checksum = "f3c7d7163586cb9d457a34561a644aa957ce870226729bf6c9c8beeaead7e0d8" dependencies = [ - "embassy-time 0.4.0", - "embedded-io-async 0.6.1", + "embassy-time", + "embedded-io-async 0.7.0", ] [[package]] name = "edge-nal-embassy" -version = "0.6.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "252f89adf4f0016631977bec3ba50d768263a3a9fa9f023b4087088a619568ce" +checksum = "7f66d0fa7b3b11c25646fae5dc15f3f3830c6127c39743cf2d54ba96110a5330" dependencies = [ "edge-nal", "embassy-futures", - "embassy-net", - "embedded-io-async 0.6.1", - "heapless 0.8.0", + "embassy-net 0.8.0", + "embedded-io-async 0.7.0", + "heapless 0.9.3", ] [[package]] name = "edge-raw" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6207c84e9bc8df8ef3c155196df290f2a51f010bd60c2e78366e51979988bdb5" +checksum = "466dfce9c2172a4e947b81b556f1f07a86029fbac679e323cfb66c738cc2faea" [[package]] name = "eeprom24x" @@ -570,48 +572,13 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "embassy-embedded-hal" -version = "0.3.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c62a3bf127e03832fb97d8b01a058775e617653bc89e2a12c256485a7fb54c1" -dependencies = [ - "embassy-embedded-hal 0.4.0", - "embassy-futures", - "embassy-sync 0.6.2", - "embassy-time 0.4.0", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-storage", - "embedded-storage-async", - "nb 1.1.0", -] - -[[package]] -name = "embassy-embedded-hal" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1611b7a7ab5d1fbed84c338df26d56fd9bded58006ebb029075112ed2c5e039" +checksum = "b0641612053b2f34fc250bb63f6630ae75de46e02ade7f457268447081d709ce" dependencies = [ "embassy-futures", "embassy-hal-internal", - "embassy-sync 0.7.2", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "embedded-storage", - "embedded-storage-async", - "nb 1.1.0", -] - -[[package]] -name = "embassy-embedded-hal" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8" -dependencies = [ - "embassy-futures", - "embassy-hal-internal", - "embassy-sync 0.7.2", + "embassy-sync 0.8.0", "embedded-hal 0.2.7", "embedded-hal 1.0.0", "embedded-hal-async", @@ -622,28 +589,36 @@ dependencies = [ [[package]] name = "embassy-executor" -version = "0.7.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90327bcc66333a507f89ecc4e2d911b265c45f5c9bc241f98eee076752d35ac6" +checksum = "5d0d3b15c9d7dc4fec1d8cb77112472fb008b3b28c51ad23838d83587a6d2f1e" dependencies = [ + "cordyceps", "critical-section", "document-features", "embassy-executor-macros", + "embassy-executor-timer-queue", "log", ] [[package]] name = "embassy-executor-macros" -version = "0.6.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3577b1e9446f61381179a330fc5324b01d511624c55f25e3c66c9e3c626dbecf" +checksum = "d11a246f53de5f97a387f40ac24726817cd0b6f833e7603baac784f29d6ff276" dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] +[[package]] +name = "embassy-executor-timer-queue" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" + [[package]] name = "embassy-futures" version = "0.1.2" @@ -652,9 +627,9 @@ checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" [[package]] name = "embassy-hal-internal" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95285007a91b619dc9f26ea8f55452aa6c60f7115a4edc05085cd2bd3127cd7a" +checksum = "7f10ce10a4dfdf6402d8e9bd63128986b96a736b1a0a6680547ed2ac55d55dba" dependencies = [ "num-traits", ] @@ -668,9 +643,26 @@ dependencies = [ "document-features", "embassy-net-driver", "embassy-sync 0.7.2", - "embassy-time 0.5.1", + "embassy-time", "embedded-io-async 0.6.1", - "embedded-nal-async", + "embedded-nal-async 0.8.0", + "heapless 0.8.0", + "managed", + "smoltcp", +] + +[[package]] +name = "embassy-net" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71f0aa32082b7df00164f485322d6edab59122c9718b363b07ec23424c2c06a0" +dependencies = [ + "document-features", + "embassy-net-driver", + "embassy-sync 0.7.2", + "embassy-time", + "embedded-io-async 0.7.0", + "embedded-nal-async 0.9.0", "heapless 0.8.0", "log", "managed", @@ -709,7 +701,6 @@ dependencies = [ "futures-core", "futures-sink", "heapless 0.8.0", - "log", ] [[package]] @@ -724,22 +715,7 @@ dependencies = [ "futures-core", "futures-sink", "heapless 0.9.3", -] - -[[package]] -name = "embassy-time" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f820157f198ada183ad62e0a66f554c610cdcd1a9f27d4b316358103ced7a1f8" -dependencies = [ - "cfg-if", - "critical-section", - "document-features", - "embassy-time-driver", - "embedded-hal 0.2.7", - "embedded-hal 1.0.0", - "embedded-hal-async", - "futures-util", + "log", ] [[package]] @@ -770,12 +746,12 @@ dependencies = [ [[package]] name = "embassy-time-queue-utils" -version = "0.1.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc55c748d16908a65b166d09ce976575fb8852cf60ccd06174092b41064d8f83" +checksum = "168297bf80aaf114b3c9ad589bf38b01b3009b9af7f97cd18086c5bbf96f5693" dependencies = [ - "embassy-executor", - "heapless 0.8.0", + "embassy-executor-timer-queue", + "heapless 0.9.3", ] [[package]] @@ -812,16 +788,6 @@ dependencies = [ "embedded-hal 1.0.0", ] -[[package]] -name = "embedded-hal-bus" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "513e0b3a8fb7d3013a8ae17a834283f170deaf7d0eeab0a7c1a36ad4dd356d22" -dependencies = [ - "critical-section", - "embedded-hal 1.0.0", -] - [[package]] name = "embedded-io" version = "0.6.1" @@ -871,6 +837,16 @@ dependencies = [ "embedded-nal", ] +[[package]] +name = "embedded-nal-async" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5a1bd585135d302f8f6d7de329310938093da6271b37a6c94b8798795c0c6d" +dependencies = [ + "embedded-io-async 0.7.0", + "embedded-nal", +] + [[package]] name = "embedded-storage" version = "0.3.1" @@ -904,7 +880,7 @@ dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -915,63 +891,72 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "esp-alloc" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e95f1de57ce5a6600368f3d3c931b0dfe00501661e96f5ab83bc5cdee031784" +checksum = "46ced060d4085858283df950b80a4da2348e1707d7d07b1e966308582dae79f5" dependencies = [ "allocator-api2", "cfg-if", - "critical-section", "document-features", "enumset", + "esp-config", + "esp-sync", "linked_list_allocator", + "rlsf", ] [[package]] name = "esp-backtrace" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f270a29a3c4e492399b13e157b10151a7616cba69c4d554076ea93ed1bd2916" +checksum = "37950e24b2dfd98f1581102d1798281d4d9547af881e6bffc2c2b534c026ec8f" dependencies = [ "cfg-if", + "document-features", "esp-config", + "esp-metadata-generated", "esp-println", - "heapless 0.8.0", + "heapless 0.9.3", + "riscv", + "xtensa-lx", ] [[package]] name = "esp-bootloader-esp-idf" -version = "0.2.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a093dbdc64b0288baacc214c2e8c2f3f13ecbf979c36ee2f63797ecf22538f1" +checksum = "35ffc117c3a9859835d89d0e90f5ee9886ce2264a71a849a7a22ab5308f6653c" dependencies = [ "cfg-if", "document-features", "embedded-storage", "esp-config", + "esp-hal-procmacros", + "esp-metadata-generated", "esp-rom-sys", "jiff", + "log", "strum", ] [[package]] name = "esp-config" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abd4a8db4b72794637a25944bc8d361c3cc271d4f03987ce8741312b6b61529c" +checksum = "4d9b92fd9cfb0b4f8f1b6219b9763269a335571e307b014903b8201619374b80" dependencies = [ "document-features", - "esp-metadata-generated 0.1.0", - "evalexpr", + "esp-metadata-generated", "serde", "serde_yaml", + "somni-expr", ] [[package]] name = "esp-hal" -version = "1.0.0-rc.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3887eda2917deef3d99e7a5c324f9190714e99055361ad36890dffd0a995b49" +checksum = "6af8fa8216bc126941bd43b5a200a50eab16e43881ccd0dd0b6792f4a82805f0" dependencies = [ "bitfield", "bitflags 2.11.1", @@ -981,24 +966,27 @@ dependencies = [ "delegate", "digest", "document-features", - "embassy-embedded-hal 0.3.2", + "embassy-embedded-hal", "embassy-futures", - "embassy-sync 0.6.2", + "embassy-sync 0.8.0", "embedded-can", "embedded-hal 1.0.0", "embedded-hal-async", "embedded-io 0.6.1", + "embedded-io 0.7.1", "embedded-io-async 0.6.1", + "embedded-io-async 0.7.0", "enumset", "esp-config", "esp-hal-procmacros", - "esp-metadata-generated 0.1.0", + "esp-metadata-generated", "esp-riscv-rt", "esp-rom-sys", + "esp-sync", "esp32", "esp32c2", "esp32c3", - "esp32c6 0.21.0", + "esp32c6", "esp32h2", "esp32s2", "esp32s3", @@ -1008,79 +996,31 @@ dependencies = [ "nb 1.1.0", "paste", "portable-atomic", + "rand_core 0.10.1", "rand_core 0.6.4", "rand_core 0.9.5", "riscv", - "serde", "strum", "ufmt-write", "xtensa-lx", "xtensa-lx-rt", ] -[[package]] -name = "esp-hal-embassy" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be6d5d08adc5d26d8071450c76e62027906dce2795afaed76e9809e596f8e33" -dependencies = [ - "cfg-if", - "critical-section", - "document-features", - "embassy-executor", - "embassy-sync 0.6.2", - "embassy-time 0.4.0", - "embassy-time-driver", - "embassy-time-queue-utils", - "esp-config", - "esp-hal", - "esp-hal-procmacros", - "esp-metadata-generated 0.1.0", - "log", - "portable-atomic", - "static_cell", -] - [[package]] name = "esp-hal-procmacros" -version = "0.19.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbece384edaf0d1eabfa45afa96d910634d4158638ef983b2d419a8dec832246" +checksum = "6aebfabb2c21bec45e575e4f6cb6bb7aa8e1b33e7ac45b5dffa0f9d33ff59105" dependencies = [ "document-features", - "litrs 0.4.2", "object", "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "termcolor", ] -[[package]] -name = "esp-metadata" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6fbc1d166be84c0750f121e95c8989ddebd7e7bdd86af3594a6cfb34f039650" -dependencies = [ - "anyhow", - "basic-toml", - "indexmap", - "proc-macro2", - "quote", - "serde", - "strum", -] - -[[package]] -name = "esp-metadata-generated" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "189d36b8c8a752bdebec67fd02a15ebb1432feea345553749bca7ce2393cc795" -dependencies = [ - "esp-metadata", -] - [[package]] name = "esp-metadata-generated" version = "0.4.0" @@ -1088,27 +1028,98 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42c2ee95b945a4780796e4359e72c033aed3b45073880e8029458f538532db8a" [[package]] -name = "esp-println" -version = "0.15.0" +name = "esp-phy" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e7e3ab41e96093d7fd307e93bfc88bd646a8ff23036ebf809e116b18869f719" +checksum = "e7c0a29815cd105ae1a02f3d0c6e7aafda9504a41effae17fac4c3f827719228" dependencies = [ - "critical-section", + "cfg-if", "document-features", - "esp-metadata-generated 0.1.0", + "embassy-sync 0.8.0", + "esp-config", + "esp-hal", + "esp-metadata-generated", + "esp-sync", + "esp-wifi-sys-esp32c6", + "esp32c6", + "log", +] + +[[package]] +name = "esp-println" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42dee1e9ac7c3539bf6464db1707b0edd7557168f98278cf3c84fe70e63c6ce6" +dependencies = [ + "document-features", + "esp-metadata-generated", + "esp-sync", "log", "portable-atomic", ] [[package]] -name = "esp-riscv-rt" -version = "0.12.0" +name = "esp-radio" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a00370dfcb0ccc01c6b2540076379c6efd6890a27f584de217c38e3239e19d5" +checksum = "23fbff98b06a96b6ce3791ecec5c668524052a068e23aacd23afe17ddba844ce" +dependencies = [ + "allocator-api2", + "cfg-if", + "docsplay", + "document-features", + "embassy-net-driver", + "embassy-sync 0.8.0", + "embedded-io 0.6.1", + "embedded-io 0.7.1", + "embedded-io-async 0.6.1", + "embedded-io-async 0.7.0", + "enumset", + "esp-alloc", + "esp-config", + "esp-hal", + "esp-hal-procmacros", + "esp-metadata-generated", + "esp-phy", + "esp-radio-rtos-driver", + "esp-sync", + "esp-wifi-sys-esp32", + "esp-wifi-sys-esp32c2", + "esp-wifi-sys-esp32c3", + "esp-wifi-sys-esp32c6", + "esp-wifi-sys-esp32h2", + "esp-wifi-sys-esp32s2", + "esp-wifi-sys-esp32s3", + "esp32c6", + "heapless 0.9.3", + "instability", + "log", + "num-derive", + "num-traits", + "portable-atomic", + "portable_atomic_enum", +] + +[[package]] +name = "esp-radio-rtos-driver" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd75cd9073a90ffaa53db0bf17df7dc14164f2407a6ff36c725d2d1f78ff494" +dependencies = [ + "cfg-if", + "esp-sync", + "portable-atomic", +] + +[[package]] +name = "esp-riscv-rt" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66a814ae91452de56a5e74f69aebfee40579511756837d3774a56fd24cf0ab79" dependencies = [ "document-features", "riscv", - "riscv-rt-macros", + "riscv-rt", ] [[package]] @@ -1119,97 +1130,153 @@ checksum = "eae852ccb08971155023d1371c96d5490cbc26860f06aee2d629ef73f1a890c3" dependencies = [ "cfg-if", "document-features", - "esp-metadata-generated 0.4.0", - "esp32c6 0.23.2", + "esp-metadata-generated", + "esp32c6", +] + +[[package]] +name = "esp-rtos" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551f90766e1527edaa0c91e8d559e9e2a60397b545e93357ac61fb31845e5712" +dependencies = [ + "allocator-api2", + "cfg-if", + "document-features", + "embassy-executor", + "embassy-sync 0.8.0", + "embassy-time-driver", + "embassy-time-queue-utils", + "esp-config", + "esp-hal", + "esp-hal-procmacros", + "esp-metadata-generated", + "esp-radio-rtos-driver", + "esp-rom-sys", + "esp-sync", + "portable-atomic", + "riscv", + "xtensa-lx", ] [[package]] name = "esp-storage" -version = "0.7.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f276ad8a3bdc6b47cd92a3e91013f2e42dce9b3fc5023392063387a1ce2ed69a" +checksum = "4cf2b3f00c9f94b27c62a4f5296b19470c3a083c687c8146e554a459f9bee596" dependencies = [ - "critical-section", "document-features", "embedded-storage", - "esp-rom-sys", -] - -[[package]] -name = "esp-wifi" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69336f3f30938a644077d2a9747e3cd325436dcaf57d9dc8d58f139e02104889" -dependencies = [ - "allocator-api2", - "cfg-if", - "critical-section", - "document-features", - "embassy-net-driver", - "embedded-io 0.6.1", - "embedded-io-async 0.6.1", - "enumset", - "esp-alloc", - "esp-config", "esp-hal", - "esp-metadata-generated 0.1.0", - "esp-wifi-sys", - "log", - "num-derive", - "num-traits", - "portable-atomic", - "portable_atomic_enum", - "rand_core 0.9.5", - "smoltcp", + "esp-hal-procmacros", + "esp-metadata-generated", + "esp-rom-sys", + "esp-sync", ] [[package]] -name = "esp-wifi-sys" -version = "0.7.1" +name = "esp-sync" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6b5438361891c431970194a733415006fb3d00b6eb70b3dcb66fd58f04d9b39" +checksum = "b4736bfbbb9e3f6353344e14fc61b6d18d3b877c3286914cf8c0a037be0ed224" +dependencies = [ + "cfg-if", + "document-features", + "embassy-sync 0.6.2", + "embassy-sync 0.7.2", + "embassy-sync 0.8.0", + "esp-metadata-generated", + "riscv", + "xtensa-lx", +] + +[[package]] +name = "esp-wifi-sys-esp32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2556f38f5292d9735d4e156e276815fc001c9a0a2be0544a575c5fb867129d24" +dependencies = [ + "log", +] + +[[package]] +name = "esp-wifi-sys-esp32c2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4b3eb3435dae84de611d4384639e61eed862818223e93fa70c525cb6a70127f" +dependencies = [ + "log", +] + +[[package]] +name = "esp-wifi-sys-esp32c3" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b290846b53db9a3965866964260220b67f2c41cc2dbc0b377e7136239fe168a7" +dependencies = [ + "log", +] + +[[package]] +name = "esp-wifi-sys-esp32c6" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57649401fc2f906a16e2268de88693724a125adcd0eba89b594a157affcee2d5" +dependencies = [ + "log", +] + +[[package]] +name = "esp-wifi-sys-esp32h2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cf1be2e311f4b4e75b5507c6b007ffc3fcb8c6cb57f83cc6a9569ecdcf58484" +dependencies = [ + "log", +] + +[[package]] +name = "esp-wifi-sys-esp32s2" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a9a7f1bc51e7026c3012cda4f11d8799e5e0c0bb3be85797462219def6c26e" +dependencies = [ + "log", +] + +[[package]] +name = "esp-wifi-sys-esp32s3" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8321f44b57d8112cbd8607cc83b85783b9195289acb45532728e3e229e7786" dependencies = [ - "anyhow", "log", ] [[package]] name = "esp32" -version = "0.38.0" +version = "0.40.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7680f79e3a4770e59c2dc25b17dcd852921ee57ffae9a4c4806c9ca5001d54d" +checksum = "5726e07689249d1a2cb7c492077bc424837fb68a64f7eb5d46569325352e9428" dependencies = [ - "critical-section", "vcell", ] [[package]] name = "esp32c2" -version = "0.27.0" +version = "0.29.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da1bcf86fca83543e0e95561cba27bbcc6b6e7adc5428f49187f5868bc0c3ed2" +checksum = "5ef0b623533bbaa37e348c18b6b41cfd5b47c3cb64a4b9e44f0295941d62aa2e" dependencies = [ - "critical-section", "vcell", ] [[package]] name = "esp32c3" -version = "0.30.0" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce2c5a33d4377f974cbe8cadf8307f04f2c39755704cb09e81852c63ee4ac7b8" +checksum = "21e89ed62cf6c043a6d29c520b02a13b359ec8a75d67b65d4330ed717d15fe97" dependencies = [ - "critical-section", - "vcell", -] - -[[package]] -name = "esp32c6" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ca8fc81b7164df58b5e04aaac9e987459312e51903cca807317990293973a6e" -dependencies = [ - "critical-section", "vcell", ] @@ -1224,40 +1291,31 @@ dependencies = [ [[package]] name = "esp32h2" -version = "0.17.0" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80171d08c17d8c63b53334c60ca654786a7593481531d19b639c4e5c76d276de" +checksum = "c5bab026020ed4606ce113b6fde598dbc48f7eefcc46e9469ece77cc2b1aa4be" dependencies = [ - "critical-section", "vcell", ] [[package]] name = "esp32s2" -version = "0.29.0" +version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c90d347480fca91f4be3e94b576af9c6c7987795c58dc3c5a7c108b6b3966dc" +checksum = "d0ad6f21cdf6ec7b06b7f7e0fbe51f0d975fd6a5fa67c3f8a5a910d3981af531" dependencies = [ - "critical-section", "vcell", ] [[package]] name = "esp32s3" -version = "0.33.0" +version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3769c56222c4548833f236c7009f1f8b3f2387af26366f6bd1cea456666a49d" +checksum = "8b4b8c4e4d9f187553ecdb7173edec7b2deb2beea106eedefecdb1654b8ee25a" dependencies = [ - "critical-section", "vcell", ] -[[package]] -name = "evalexpr" -version = "12.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae893d2d5e908b78f151ed89de3bfc272cdf6d368c7ed866942f98e24dea208a" - [[package]] name = "fastrand" version = "2.4.1" @@ -1321,6 +1379,21 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" +[[package]] +name = "generator" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f04ae4152da20c76fe800fa48659201d5cf627c5149ca0b707b69d7eef6cf9" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1461,8 +1534,6 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", - "serde", - "serde_core", ] [[package]] @@ -1484,7 +1555,7 @@ dependencies = [ "indoc", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1523,7 +1594,7 @@ checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1538,6 +1609,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lib-bms-protocol" +version = "0.1.0" +source = "git+https://gitea.wlandt.de/judge/ch32-bms.git#58b3a97d73dc2461362125a91fb18a24b7f564bd" +dependencies = [ + "chrono", + "embedded-hal 1.0.0", + "thiserror", +] + [[package]] name = "libc" version = "0.2.186" @@ -1566,15 +1653,6 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897" -[[package]] -name = "litrs" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" -dependencies = [ - "proc-macro2", -] - [[package]] name = "litrs" version = "1.0.0" @@ -1629,20 +1707,42 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + [[package]] name = "managed" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "mcutie" version = "3.0.0" dependencies = [ "embassy-futures", - "embassy-net", + "embassy-net 0.8.0", "embassy-sync 0.8.0", - "embassy-time 0.5.1", + "embassy-time", "embedded-io 0.7.1", "embedded-io-async 0.7.0", "heapless 0.7.17", @@ -1708,6 +1808,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + [[package]] name = "num-conv" version = "0.2.1" @@ -1722,7 +1831,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1752,7 +1861,7 @@ checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1766,9 +1875,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.7" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] @@ -1875,7 +1984,7 @@ checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1902,38 +2011,37 @@ dependencies = [ "edge-nal", "edge-nal-embassy", "eeprom24x", - "embassy-embedded-hal 0.5.0", + "embassy-embedded-hal", "embassy-executor", - "embassy-net", - "embassy-sync 0.7.2", - "embassy-time 0.5.1", + "embassy-net 0.8.0", + "embassy-sync 0.8.0", + "embassy-time", "embedded-hal 1.0.0", - "embedded-hal-bus", - "embedded-io 0.6.1", - "embedded-io-async 0.6.1", "embedded-storage", "esp-alloc", "esp-backtrace", "esp-bootloader-esp-idf", "esp-hal", - "esp-hal-embassy", "esp-println", + "esp-radio", + "esp-rtos", "esp-storage", - "esp-wifi", + "esp32c6", "heapless 0.7.17", "ina219", + "lib-bms-protocol", "littlefs2", "littlefs2-core", "log", "mcutie", "measurements", + "nb 1.1.0", "onewire", "option-lock", "pca9535", "portable-atomic", "serde", "serde_json", - "smoltcp", "sntpc", "static_cell", "strum_macros", @@ -1974,7 +2082,7 @@ checksum = "a33fa6ec7f2047f572d49317cca19c87195de99c6e5b6ee492da701cfe02b053" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1992,28 +2100,6 @@ dependencies = [ "toml_edit", ] -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -2032,12 +2118,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r0" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7a31eed1591dcbc95d92ad7161908e72f4677f8fabf2a32ca49b4237cbf211" - [[package]] name = "rand_core" version = "0.6.4" @@ -2050,6 +2130,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "regex" version = "1.12.3" @@ -2081,9 +2167,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "riscv" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea8ff73d3720bdd0a97925f0bf79ad2744b6da8ff36be3840c48ac81191d7a7" +checksum = "b05cfa3f7b30c84536a9025150d44d26b8e1cc20ddf436448d74cd9591eefb25" dependencies = [ "critical-section", "embedded-hal 1.0.0", @@ -2094,13 +2180,13 @@ dependencies = [ [[package]] name = "riscv-macros" -version = "0.1.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25" +checksum = "7d323d13972c1b104aa036bc692cd08b822c8bbf23d79a27c526095856499799" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2110,14 +2196,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" [[package]] -name = "riscv-rt-macros" -version = "0.4.0" +name = "riscv-rt" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc71814687c45ba4cd1e47a54e03a2dbc62ca3667098fbae9cc6b423956758fa" +checksum = "3d07b9f3a0eff773fc4df11f44ada4fa302e529bff4b7fe7e6a4b98a65ce9174" +dependencies = [ + "riscv", + "riscv-pac", + "riscv-rt-macros", + "riscv-target-parser", +] + +[[package]] +name = "riscv-rt-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def519ddeeb5e43c2b4fc3952c27b3a86782fc05192f322b2309125cd85b1fc3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "riscv-target-parser" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1376b15f3ff160e9b1e8ea564ce427f2f6fcf77528cc0a8bf405cb476f9cea7" + +[[package]] +name = "rlsf" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1646a59a9734b8b7a0ac51689388a60fe1625d4b956348e9de07591a1478457a" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "rustversion", + "svgbobdoc", ] [[package]] @@ -2156,6 +2273,12 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -2195,7 +2318,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2230,6 +2353,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -2248,6 +2380,12 @@ version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "smoltcp" version = "0.12.0" @@ -2257,9 +2395,7 @@ dependencies = [ "bitflags 1.3.2", "byteorder", "cfg-if", - "defmt 0.3.100", "heapless 0.8.0", - "log", "managed", ] @@ -2270,10 +2406,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13d84c42dedf8b01d52b62272684741866938260e74b9e58851f680b2a92854b" dependencies = [ "cfg-if", - "embassy-net", + "embassy-net 0.7.1", "log", ] +[[package]] +name = "somni-expr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed9b7648d5e8b2df6c5e49940c54bcdd2b4dd71eafc6e8f1c714eb4581b0f53" +dependencies = [ + "somni-parser", +] + +[[package]] +name = "somni-parser" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0f368519fc6c85fc1afdb769fb5a51123f6158013e143656e25a3485a0d401c" + [[package]] name = "spin" version = "0.9.8" @@ -2322,7 +2473,31 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "svgbobdoc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2c04b93fc15d79b39c63218f15e3fdffaa4c227830686e3b7c5f41244eb3e50" +dependencies = [ + "base64 0.13.1", + "proc-macro2", + "quote", + "syn 1.0.109", + "unicode-width", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] @@ -2362,7 +2537,16 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", ] [[package]] @@ -2428,6 +2612,67 @@ dependencies = [ "winnow", ] +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "typenum" version = "1.20.0" @@ -2446,6 +2691,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unit-enum" version = "1.5.0" @@ -2454,7 +2705,7 @@ checksum = "ecc0c52f714caea15de105fdf9f174fcf8c8b95bd806fb81b389cb60a4d0d742" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2469,6 +2720,12 @@ version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcell" version = "0.1.3" @@ -2537,7 +2794,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -2580,7 +2837,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2591,7 +2848,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2638,34 +2895,33 @@ dependencies = [ [[package]] name = "xtensa-lx" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a564fffeb3cd773a524e8d8a5c66ca5e9739ea7450e36a3e6a54dd31f1e652f" +checksum = "e012d667b0aa6d2592ace8ef145a98bff3e76cca7a644f4181ecd7a916ed289b" dependencies = [ "critical-section", ] [[package]] name = "xtensa-lx-rt" -version = "0.20.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520a8fb0121eb6868f4f5ff383e262dc863f9042496724e01673a98a9b7e6c2b" +checksum = "409a9b4629d429e995cde4dfbd9fe562ccae66f7624514e200733fc5d0ea8905" dependencies = [ "document-features", - "r0", "xtensa-lx", "xtensa-lx-rt-proc-macros", ] [[package]] name = "xtensa-lx-rt-proc-macros" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a56a616147f5947ceb673790dd618d77b30e26e677f4a896df049d73059438" +checksum = "96fb42cd29c42f8744c74276e9f5bee7b06685bbe5b88df891516d72cb320450" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index bbc38cf..3eee304 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -52,77 +52,41 @@ partition_table = "partitions.csv" [dependencies] #ESP stuff -esp-bootloader-esp-idf = { version = "0.2.0", features = ["esp32c6"] } -esp-hal = { version = "=1.0.0-rc.0", features = [ - "esp32c6", - "log-04", - "unstable", - "rt" -] } -log = "0.4.27" +log = "0.4.28" +esp-bootloader-esp-idf = { version = "0.5.0", features = ["esp32c6", "log-04"] } +esp-hal = { version = "1.1.0", features = ["esp32c6", "log-04"] } +esp-rtos = { version = "0.3.0", features = ["esp32c6", "embassy", "esp-radio"] } +esp-backtrace = { version = "0.19.0", features = ["esp32c6", "panic-handler", "println", "colors", "custom-halt"] } +esp-println = { version = "0.17.0", features = ["esp32c6", "log-04", "auto"] } +esp-storage = { version = "0.9.0", features = ["esp32c6"] } +esp-radio = { version = "0.18.0", features = ["esp32c6", "log-04", "wifi", "unstable"] } +esp-alloc = { version = "0.10.0", features = ["esp32c6", "internal-heap-stats"] } -embassy-net = { version = "0.7.1", default-features = false, features = [ - "dhcpv4", - "log", - "medium-ip", - "medium-ethernet", - "tcp", - "udp", - "proto-ipv4", - "dns" -] } -embedded-io = "0.6.1" -embedded-io-async = "0.6.1" -esp-alloc = "0.8.0" -esp-backtrace = { version = "0.17.0", features = [ - "esp32c6", - "exception-handler", - "panic-handler", - "println", - "colors", - "custom-halt" -] } -esp-println = { version = "0.15.0", features = ["esp32c6", "log-04"] } -# for more networking protocol support see https://crates.io/crates/edge-net -embassy-executor = { version = "0.7.0", features = [ - "log", - "task-arena-size-64", - "nightly" -] } -embassy-time = { version = "0.5.0", features = ["log"], default-features = false } -esp-hal-embassy = { version = "0.9.0", features = ["esp32c6", "log-04"] } -esp-storage = { version = "0.7.0", features = ["esp32c6"] } +# Async runtime (Embassy core) +embassy-executor = { version = "0.10.0", features = ["log", "nightly"] } +embassy-time = { version = "0.5.1", features = ["log"], default-features = false } +embassy-sync = { version = "0.8.0", features = ["log"] } -esp-wifi = { version = "0.15.0", features = [ - "builtin-scheduler", - "esp-alloc", - "esp32c6", - "log-04", - "smoltcp", - "wifi", -] } -smoltcp = { version = "0.12.0", default-features = false, features = [ - "alloc", - "log", - "medium-ethernet", - "multicast", - "proto-dhcpv4", - "proto-ipv6", - "proto-dns", - "proto-ipv4", - "socket-dns", - "socket-icmp", - "socket-raw", - "socket-tcp", - "socket-udp", -] } -#static_cell = "2.1.1" +# Networking and protocol stacks +embassy-net = { version = "0.8.0", features = ["dhcpv4", "log", "medium-ethernet", "tcp", "udp", "proto-ipv4", "dns", "proto-ipv6"] } +sntpc = { version = "0.6.1", default-features = false, features = ["log", "embassy-socket", "embassy-socket-ipv6"] } +edge-dhcp = "0.7.0" +edge-nal = "0.6.0" +edge-nal-embassy = "0.8.1" +edge-http = { version = "0.7.0", features = ["log"] } + +esp32c6 = { version = "0.23.2" } + +# Hardware abstraction traits and HAL adapters embedded-hal = "1.0.0" -embedded-hal-bus = { version = "0.3.0" } +embedded-storage = "0.3.1" +embassy-embedded-hal = "0.6.0" +nb = "1.1.0" #Hardware additional driver #bq34z100 = { version = "0.3.0", default-features = false } +lib-bms-protocol = { git = "https://gitea.wlandt.de/judge/ch32-bms.git", default-features = false } onewire = "0.4.0" #strum = { version = "0.27.0", default-feature = false, features = ["derive"] } ds323x = "0.6.0" @@ -139,23 +103,15 @@ strum_macros = "0.27.0" unit-enum = "1.4.1" pca9535 = { version = "2.0.0" } ina219 = { version = "0.2.0" } -embedded-storage = "=0.3.1" portable-atomic = "1.11.1" -embassy-sync = { version = "0.7.2", features = ["log"] } async-trait = "0.1.89" bq34z100 = { version = "0.4.0", default-features = false } -edge-dhcp = "0.6.0" -edge-nal = "0.5.0" -edge-nal-embassy = "0.6.0" static_cell = "2.1.1" -edge-http = { version = "0.6.1", features = ["log"] } littlefs2 = { version = "0.6.1", features = ["c-stubs", "alloc"] } littlefs2-core = "0.1.1" bytemuck = { version = "1.23.2", features = ["derive", "min_const_generics", "pod_saturating", "extern_crate_alloc"] } deranged = "0.5.3" -embassy-embedded-hal = "0.5.0" bincode = { version = "2.0.1", default-features = false, features = ["derive"] } -sntpc = { version = "0.6.0", default-features = false, features = ["log", "embassy-socket", "embassy-socket-ipv6"] } option-lock = { version = "0.3.1", default-features = false } measurements = "0.11.1" heapless = { version = "0.7.17", features = ["serde"] } diff --git a/rust/bootloader.bin b/rust/bootloader.bin new file mode 100644 index 0000000..6bb98ef Binary files /dev/null and b/rust/bootloader.bin differ diff --git a/rust/src/fat_error.rs b/rust/src/fat_error.rs index b769029..d43bd61 100644 --- a/rust/src/fat_error.rs +++ b/rust/src/fat_error.rs @@ -8,7 +8,7 @@ use embassy_executor::SpawnError; use embassy_sync::mutex::TryLockError; use esp_hal::i2c::master::ConfigError; use esp_hal::pcnt::unit::{InvalidHighLimit, InvalidLowLimit}; -use esp_wifi::wifi::WifiError; +use esp_radio::wifi::WifiError; use ina219::errors::{BusVoltageReadError, ShuntVoltageReadError}; use littlefs2_core::PathError; use onewire::Error; @@ -45,6 +45,7 @@ pub enum FatError { SpawnError { error: SpawnError, }, + OTAError, PartitionError { error: esp_bootloader_esp_idf::partitions::Error, }, @@ -60,6 +61,9 @@ pub enum FatError { ExpanderError { error: String, }, + SNTPError { + error: sntpc::Error, + }, } pub type FatResult = Result; @@ -88,6 +92,10 @@ impl fmt::Display for FatError { FatError::DS323 { error } => write!(f, "DS323 {:?}", error), FatError::Eeprom24x { error } => write!(f, "Eeprom24x {:?}", error), FatError::ExpanderError { error } => write!(f, "ExpanderError {:?}", error), + FatError::SNTPError { error } => write!(f, "SNTPError {error:?}"), + FatError::OTAError => { + write!(f, "OTA missing partition") + } } } } @@ -127,6 +135,24 @@ impl ContextExt for Option { } } +impl ContextExt for Result +where + E: fmt::Debug, +{ + fn context(self, context: C) -> Result + where + C: AsRef, + { + match self { + Ok(value) => Ok(value), + Err(err) => Err(FatError::String { + error: format!("{}: {:?}", context.as_ref(), err), + }), + } + } +} + + impl From> for FatError { fn from(error: Error) -> Self { FatError::OneWireError { error } @@ -168,6 +194,12 @@ impl From for FatError { } } +impl From for FatError { + fn from(value: sntpc::Error) -> Self { + FatError::SNTPError { error: value } + } +} + impl From for FatError { fn from(value: esp_bootloader_esp_idf::partitions::Error) -> Self { FatError::PartitionError { error: value } @@ -279,3 +311,11 @@ impl From for FatError { } } } + +impl From for FatError { + fn from(value: chrono::format::ParseError) -> Self { + FatError::String { + error: format!("Parsing error: {value:?}"), + } + } +} diff --git a/rust/src/hal/esp.rs b/rust/src/hal/esp.rs index 8172eea..7816591 100644 --- a/rust/src/hal/esp.rs +++ b/rust/src/hal/esp.rs @@ -1,8 +1,11 @@ use crate::bail; use crate::config::{NetworkConfig, PlantControllerConfig}; -use crate::hal::{PLANT_COUNT, TIME_ACCESS}; -use crate::log::{LogMessage, LOG_ACCESS}; +use crate::hal::PLANT_COUNT; +use crate::log::{log, LogMessage}; +use alloc::vec; use chrono::{DateTime, Utc}; +use esp_hal::Blocking; +use esp_hal::uart::Uart; use serde::Serialize; use crate::fat_error::{ContextExt, FatError, FatResult}; @@ -14,15 +17,17 @@ use core::net::{IpAddr, Ipv4Addr, SocketAddr}; use core::str::FromStr; use core::sync::atomic::Ordering; use embassy_executor::Spawner; -use embassy_net::udp::UdpSocket; -use embassy_net::{DhcpConfig, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4}; +use embassy_net::dns::DnsQueryType; +use embassy_net::udp::{PacketMetadata, UdpSocket}; +use embassy_net::{DhcpConfig, IpAddress, Ipv4Cidr, Runner, Stack, StackResources, StaticConfigV4}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::{Mutex, MutexGuard}; use embassy_sync::once_lock::OnceLock; -use embassy_time::{Duration, Timer}; -use embedded_storage::nor_flash::ReadNorFlash; +use embassy_time::{Duration, Timer, WithTimeout}; +use embedded_storage::nor_flash::{check_erase, NorFlash, ReadNorFlash, RmwNorFlashStorage}; +use esp_bootloader_esp_idf::ota::OtaImageState::Valid; use esp_bootloader_esp_idf::ota::{Ota, OtaImageState}; -use esp_bootloader_esp_idf::partitions::FlashRegion; +use esp_bootloader_esp_idf::partitions::{AppPartitionSubType, FlashRegion}; use esp_hal::gpio::{Input, RtcPinWithResistors}; use esp_hal::rng::Rng; use esp_hal::rtc_cntl::{ @@ -31,31 +36,34 @@ use esp_hal::rtc_cntl::{ }; use esp_hal::system::software_reset; use esp_println::println; +use esp_radio::wifi::ap::{AccessPointConfig, AccessPointInfo}; +use esp_radio::wifi::scan::{ScanConfig, ScanTypeConfig}; +use esp_radio::wifi::sta::StationConfig; +use esp_radio::wifi::{AuthenticationMethod, Config, Interface, WifiController}; use esp_storage::FlashStorage; -use esp_wifi::wifi::{ - AccessPointConfiguration, AccessPointInfo, AuthMethod, ClientConfiguration, Configuration, - ScanConfig, ScanTypeConfig, WifiController, WifiDevice, WifiState, -}; use littlefs2::fs::Filesystem; use littlefs2_core::{FileType, PathBuf, SeekFrom}; -use log::{info, warn}; +use log::{info, warn, error}; use mcutie::{ Error, McutieBuilder, McutieReceiver, McutieTask, MqttMessage, PublishDisplay, Publishable, QoS, Topic, }; use portable_atomic::AtomicBool; -use smoltcp::socket::udp::PacketMetadata; -use smoltcp::wire::DnsQueryType; -use sntpc::{get_time, NtpContext, NtpTimestampGenerator}; +use sntpc::{NtpContext, NtpTimestampGenerator, NtpUdpSocket, get_time}; -#[esp_hal::ram(rtc_fast, persistent)] +use super::shared_flash::MutexFlashStorage; + +#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))] static mut LAST_WATERING_TIMESTAMP: [i64; PLANT_COUNT] = [0; PLANT_COUNT]; -#[esp_hal::ram(rtc_fast, persistent)] +#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))] static mut CONSECUTIVE_WATERING_PLANT: [u32; PLANT_COUNT] = [0; PLANT_COUNT]; -#[esp_hal::ram(rtc_fast, persistent)] +#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))] static mut LOW_VOLTAGE_DETECTED: i8 = 0; -#[esp_hal::ram(rtc_fast, persistent)] +#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))] static mut RESTART_TO_CONF: i8 = 0; +#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))] +static mut LAST_CORROSION_PROTECTION_CHECK_DAY: i8 = -1; + const CONFIG_FILE: &str = "config.json"; const NTP_SERVER: &str = "pool.ntp.org"; @@ -112,22 +120,61 @@ impl NtpTimestampGenerator for Timestamp { self.stamp.timestamp_subsec_micros() } } +struct EmbassyNtpSocket<'a, 'b> { + socket: &'a UdpSocket<'b>, +} + +impl<'a, 'b> EmbassyNtpSocket<'a, 'b> { + fn new(socket: &'a UdpSocket<'b>) -> Self { + Self { socket } + } +} + +impl NtpUdpSocket for EmbassyNtpSocket<'_, '_> { + async fn send_to(&self, buf: &[u8], addr: SocketAddr) -> sntpc::Result { + self.socket + .send_to(buf, addr) + .await + .map_err(|_| sntpc::Error::Network)?; + Ok(buf.len()) + } + + async fn recv_from(&self, buf: &mut [u8]) -> sntpc::Result<(usize, SocketAddr)> { + let (len, metadata) = self + .socket + .recv_from(buf) + .await + .map_err(|_| sntpc::Error::Network)?; + let addr = match metadata.endpoint.addr { + IpAddress::Ipv4(ip) => IpAddr::V4(ip), + IpAddress::Ipv6(ip) => IpAddr::V6(ip), + }; + Ok((len, SocketAddr::new(addr, metadata.endpoint.port))) + } +} + pub struct Esp<'a> { pub fs: Arc>>, pub rng: Rng, //first starter (ap or sta will take these) - pub interface_sta: Option>, - pub interface_ap: Option>, + pub interface_sta: Option>, + pub interface_ap: Option>, pub controller: Arc>>, pub boot_button: Input<'a>, // RTC-capable GPIO used as external wake source (store the raw peripheral) pub wake_gpio1: esp_hal::peripherals::GPIO1<'static>, + pub uart0: Uart<'a, Blocking>, - pub ota: Ota<'static, FlashStorage>, - pub ota_next: &'static mut FlashRegion<'static, FlashStorage>, + pub rtc: Rtc<'a>, + + pub ota: Ota<'static, RmwNorFlashStorage<'static, &'static mut MutexFlashStorage>>, + pub ota_target: &'static mut FlashRegion<'static, MutexFlashStorage>, + pub current: AppPartitionSubType, + pub slot0_state: OtaImageState, + pub slot1_state: OtaImageState, } // SAFETY: On this target we never move Esp across OS threads; the firmware runs single-core @@ -148,6 +195,47 @@ macro_rules! mk_static { } impl Esp<'_> { + pub fn get_time(&self) -> DateTime { + DateTime::from_timestamp_micros(self.rtc.current_time_us() as i64) + .unwrap_or(DateTime::UNIX_EPOCH) + } + + pub fn set_time(&mut self, time: DateTime) { + self.rtc.set_current_time_us(time.timestamp_micros() as u64); + } + + pub(crate) async fn read_serial_line(&mut self) -> FatResult> { + let mut buf = [0u8; 1]; + let mut line = String::new(); + loop { + match self.uart0.read_buffered(&mut buf) { + Ok(read) => { + if read == 0 { + return Ok(None); + } + let c = buf[0] as char; + if c == '\n' { + return Ok(Some(line)); + } + line.push(c); + } + Err(error) => { + if line.is_empty() { + return Ok(None); + } else { + error!("Error reading serial line: {error:?}"); + // If we already have some data, we should probably wait a bit or just return what we have? + // But the protocol expects a full line or message. + // For simplicity in config mode, we can block here or just return None if nothing is there yet. + // However, if we started receiving, we should probably finish or timeout. + continue; + } + } + } + } + } + + pub(crate) async fn delete_file(&self, filename: String) -> FatResult<()> { let file = PathBuf::try_from(filename.as_str())?; let access = self.fs.lock().await; @@ -212,26 +300,47 @@ impl Esp<'_> { Ok((buf, read)) } - pub(crate) fn get_ota_slot(&mut self) -> String { - match self.ota.current_slot() { - Ok(slot) => { - format!("{:?}", slot) - } - Err(err) => { - format!("{:?}", err) - } + pub(crate) async fn write_ota(&mut self, offset: u32, buf: &[u8]) -> Result<(), FatError> { + let _ = check_erase(self.ota_target, offset, offset + 4096); + info!("erasing and writing block 0x{offset:x}"); + self.ota_target.erase(offset, offset + 4096)?; + + let mut temp = vec![0; buf.len()]; + let read_back = temp.as_mut_slice(); + //change to nor flash, align writes! + self.ota_target.write(offset, buf)?; + self.ota_target.read(offset, read_back)?; + if buf != read_back { + info!("Expected {buf:?} but got {read_back:?}"); + bail!( + "Flash error, read back does not match write buffer at offset {:x}", + offset + ) } + Ok(()) } - pub(crate) fn get_ota_state(&mut self) -> String { - match self.ota.current_ota_state() { - Ok(state) => { - format!("{:?}", state) - } - Err(err) => { - format!("{:?}", err) - } + pub(crate) async fn finalize_ota(&mut self) -> Result<(), FatError> { + let current = self.ota.current_app_partition()?; + if self.ota.current_ota_state()? != Valid { + info!("Validating current slot {current:?} as it was able to ota"); + self.ota.set_current_ota_state(Valid)?; } + let next = match current { + AppPartitionSubType::Ota0 => AppPartitionSubType::Ota1, + AppPartitionSubType::Ota1 => AppPartitionSubType::Ota0, + _ => { + bail!("Invalid current slot {current:?} for ota"); + } + }; + self.ota.set_current_app_partition(next)?; + info!("switched slot"); + self.ota.set_current_ota_state(OtaImageState::New)?; + info!("switched state for new partition"); + let state_new = self.ota.current_ota_state()?; + info!("state on new partition now {state_new:?}"); + self.set_restart_to_conf(true); + Ok(()) } // let current = ota.current_slot()?; @@ -264,31 +373,38 @@ impl Esp<'_> { &mut tx_meta, &mut tx_buffer, ); - socket.bind(123).unwrap(); + socket.bind(123).context("Could not bind UDP socket")?; let context = NtpContext::new(Timestamp::default()); + let ntp_socket = EmbassyNtpSocket::new(&socket); let ntp_addrs = stack .dns_query(NTP_SERVER, DnsQueryType::A) .await - .expect("Failed to resolve DNS"); + .context("Failed to resolve DNS")?; + if ntp_addrs.is_empty() { - bail!("Failed to resolve DNS"); + bail!("No IP addresses found for NTP server"); } + let ntp = ntp_addrs[0]; + info!("NTP server: {ntp:?}"); let mut counter = 0; loop { - let addr: IpAddr = ntp_addrs[0].into(); - let result = get_time(SocketAddr::from((addr, 123)), &socket, context).await; + let addr: IpAddr = ntp.into(); + let timeout = get_time(SocketAddr::from((addr, 123)), &ntp_socket, context) + .with_timeout(Duration::from_millis((_max_wait_ms / 10) as u64)) + .await; - match result { - Ok(time) => { - info!("Time: {:?}", time); + match timeout { + Ok(result) => { + let time = result?; + info!("Time: {time:?}"); return DateTime::from_timestamp(time.seconds as i64, 0) .context("Could not convert Sntp result"); } - Err(e) => { - warn!("Error: {:?}", e); + Err(err) => { + warn!("sntp timeout, retry: {err:?}"); counter += 1; if counter > 10 { bail!("Failed to get time from NTP server"); @@ -299,27 +415,15 @@ impl Esp<'_> { } } - pub async fn flash_ota(&mut self) -> FatResult<()> { - let capacity = self.ota_next.capacity(); - - bail!("not implemented") - } - pub(crate) async fn wifi_scan(&mut self) -> FatResult> { info!("start wifi scan"); let mut lock = self.controller.try_lock()?; info!("start wifi scan lock"); - let scan_config = ScanConfig { - ssid: None, - bssid: None, - channel: None, - show_hidden: false, - scan_type: ScanTypeConfig::Active { - min: Default::default(), - max: Default::default(), - }, - }; - let rv = lock.scan_with_config_async(scan_config).await?; + let scan_config = ScanConfig::default().with_scan_type(ScanTypeConfig::Active { + min: esp_hal::time::Duration::from_millis(0), + max: esp_hal::time::Duration::from_millis(0), + }); + let rv = lock.scan_async(&scan_config).await?; info!("end wifi scan lock"); Ok(rv) } @@ -368,17 +472,17 @@ impl Esp<'_> { } } - pub(crate) async fn wifi_ap(&mut self) -> FatResult> { + pub(crate) async fn wifi_ap(&mut self, spawner: Spawner) -> FatResult> { let ssid = match self.load_config().await { Ok(config) => config.network.ap_ssid.as_str().to_string(), Err(_) => "PlantCtrl Emergency Mode".to_string(), }; - let spawner = Spawner::for_current_executor().await; - - let device = self.interface_ap.take().unwrap(); - let gw_ip_addr_str = "192.168.71.1"; - let gw_ip_addr = Ipv4Addr::from_str(gw_ip_addr_str).expect("failed to parse gateway ip"); + let device = self + .interface_ap + .take() + .context("AP interface already taken")?; + let gw_ip_addr = Ipv4Addr::new(192, 168, 71, 1); let config = embassy_net::Config::ipv4_static(StaticConfigV4 { address: Ipv4Cidr::new(gw_ip_addr, 24), @@ -398,22 +502,14 @@ impl Esp<'_> { ); let stack = mk_static!(Stack, stack); - let client_config = Configuration::AccessPoint(AccessPointConfiguration { - ssid: ssid.clone(), - ..Default::default() - }); + let client_config = + Config::AccessPoint(AccessPointConfig::default().with_ssid(ssid.clone())); + self.controller.lock().await.set_config(&client_config)?; - self.controller - .lock() - .await - .set_configuration(&client_config)?; - - println!("start new"); - self.controller.lock().await.start()?; println!("start net task"); - spawner.spawn(net_task(runner)).ok(); + spawner.spawn(net_task(runner)?); println!("run dhcp"); - spawner.spawn(run_dhcp(stack.clone(), gw_ip_addr_str)).ok(); + spawner.spawn(run_dhcp(*stack, gw_ip_addr)?); loop { if stack.is_link_up() { @@ -424,31 +520,31 @@ impl Esp<'_> { while !stack.is_config_up() { Timer::after(Duration::from_millis(100)).await } - println!("Connect to the AP `${ssid}` and point your browser to http://{gw_ip_addr_str}/"); + println!("Connect to the AP `${ssid}` and point your browser to http://{gw_ip_addr}/"); stack .config_v4() .inspect(|c| println!("ipv4 config: {c:?}")); - Ok(stack.clone()) + Ok(*stack) } pub(crate) async fn wifi( &mut self, network_config: &NetworkConfig, + spawner: Spawner, ) -> FatResult> { - esp_wifi::wifi_set_log_verbose(); - let ssid = network_config.ssid.clone(); - match &ssid { + esp_radio::wifi_set_log_verbose(); + let ssid = match &network_config.ssid { Some(ssid) => { if ssid.is_empty() { bail!("Wifi ssid was empty") } + ssid.to_string() } None => { bail!("Wifi ssid was empty") } - } - let ssid = ssid.unwrap().to_string(); + }; info!("attempting to connect wifi {ssid}"); let password = match network_config.password { Some(ref password) => password.to_string(), @@ -456,9 +552,10 @@ impl Esp<'_> { }; let max_wait = network_config.max_wait; - let spawner = Spawner::for_current_executor().await; - - let device = self.interface_sta.take().unwrap(); + let device = self + .interface_sta + .take() + .context("STA interface already taken")?; let config = embassy_net::Config::dhcpv4(DhcpConfig::default()); let seed = (self.rng.random() as u64) << 32 | self.rng.random() as u64; @@ -472,122 +569,80 @@ impl Esp<'_> { ); let stack = mk_static!(Stack, stack); - let client_config = Configuration::Client(ClientConfiguration { - ssid, - bssid: None, - auth_method: AuthMethod::WPA2Personal, //FIXME read from config, fill via scan - password, - channel: None, - }); + let auth_method = if password.is_empty() { + AuthenticationMethod::None + } else { + AuthenticationMethod::Wpa2Personal + }; + let client_config = StationConfig::default() + .with_ssid(ssid) + .with_auth_method(auth_method) + .with_password(password); + self.controller .lock() .await - .set_configuration(&client_config)?; - spawner.spawn(net_task(runner)).ok(); - self.controller.lock().await.start_async().await?; + .set_config(&Config::Station(client_config))?; + spawner.spawn(net_task(runner)?); + self.controller + .lock() + .await + .connect_async() + .with_timeout(Duration::from_millis(max_wait as u64 * 1000)) + .await + .context("Timeout waiting for wifi sta connected")??; - let timeout = { - let guard = TIME_ACCESS.get().await.lock().await; - guard.current_time_us() - } + max_wait as u64 * 1000; - loop { - let state = esp_wifi::wifi::sta_state(); - match state { - WifiState::StaStarted => { - self.controller.lock().await.connect()?; - break; - } - _ => {} + let res = async { + while !stack.is_link_up() { + Timer::after(Duration::from_millis(500)).await; } - if { - let guard = TIME_ACCESS.get().await.lock().await; - guard.current_time_us() - } > timeout - { - bail!("Timeout waiting for wifi sta ready") - } - Timer::after(Duration::from_millis(500)).await; + Ok::<(), FatError>(()) } - let timeout = { - let guard = TIME_ACCESS.get().await.lock().await; - guard.current_time_us() - } + max_wait as u64 * 1000; - loop { - let state = esp_wifi::wifi::sta_state(); - match state { - WifiState::StaConnected => { - break; - } - _ => {} - } - if { - let guard = TIME_ACCESS.get().await.lock().await; - guard.current_time_us() - } > timeout - { - bail!("Timeout waiting for wifi sta connected") - } - Timer::after(Duration::from_millis(500)).await; + .with_timeout(Duration::from_millis(max_wait as u64 * 1000)) + .await; + + if res.is_err() { + bail!("Timeout waiting for wifi link up") } - let timeout = { - let guard = TIME_ACCESS.get().await.lock().await; - guard.current_time_us() - } + max_wait as u64 * 1000; - while !stack.is_link_up() { - if { - let guard = TIME_ACCESS.get().await.lock().await; - guard.current_time_us() - } > timeout - { - bail!("Timeout waiting for wifi link up") + + let res = async { + while !stack.is_config_up() { + Timer::after(Duration::from_millis(100)).await } - Timer::after(Duration::from_millis(500)).await; + Ok::<(), FatError>(()) } - let timeout = { - let guard = TIME_ACCESS.get().await.lock().await; - guard.current_time_us() - } + max_wait as u64 * 1000; - while !stack.is_config_up() { - if { - let guard = TIME_ACCESS.get().await.lock().await; - guard.current_time_us() - } > timeout - { - bail!("Timeout waiting for wifi config up") - } - Timer::after(Duration::from_millis(100)).await + .with_timeout(Duration::from_millis(max_wait as u64 * 1000)) + .await; + + if res.is_err() { + bail!("Timeout waiting for wifi config up") } info!("Connected WIFI, dhcp: {:?}", stack.config_v4()); - Ok(stack.clone()) + Ok(*stack) } - pub fn deep_sleep( - &mut self, - duration_in_ms: u64, - mut rtc: MutexGuard, - ) -> ! { - // Configure and enter deep sleep using esp-hal. Also keep prior behavior where - // duration_in_ms == 0 triggers an immediate reset. - + pub fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { // Mark the current OTA image as valid if we reached here while in pending verify. if let Ok(cur) = self.ota.current_ota_state() { if cur == OtaImageState::PendingVerify { - self.ota - .set_current_ota_state(OtaImageState::Valid) - .expect("Could not set image to valid"); + info!("Marking OTA image as valid"); + if let Err(err) = self.ota.set_current_ota_state(Valid) { + error!("Could not set image to valid: {:?}", err); + } } + } else { + info!("No OTA image to mark as valid"); } if duration_in_ms == 0 { software_reset(); } else { - ///let timer = TimerWakeupSource::new(core::time::Duration::from_millis(duration_in_ms)); - let timer = TimerWakeupSource::new(core::time::Duration::from_millis(5000)); + let timer = TimerWakeupSource::new(core::time::Duration::from_millis(duration_in_ms)); let mut wake_pins: [(&mut dyn RtcPinWithResistors, WakeupLevel); 1] = [(&mut self.wake_gpio1, WakeupLevel::Low)]; let ext1 = esp_hal::rtc_cntl::sleep::Ext1WakeupSource::new(&mut wake_pins); - rtc.sleep_deep(&[&timer, &ext1]); + self.rtc.sleep_deep(&[&timer, &ext1]); } } @@ -646,47 +701,36 @@ impl Esp<'_> { } else { RESTART_TO_CONF = 0; } + LAST_CORROSION_PROTECTION_CHECK_DAY = -1; }; } else { unsafe { if to_config_mode { RESTART_TO_CONF = 1; } - LOG_ACCESS - .lock() - .await - .log( - LogMessage::RestartToConfig, - RESTART_TO_CONF as u32, - 0, - "", - "", - ) - .await; - LOG_ACCESS - .lock() - .await - .log( - LogMessage::LowVoltage, - LOW_VOLTAGE_DETECTED as u32, - 0, - "", - "", - ) - .await; - for i in 0..PLANT_COUNT { - log::info!( - "LAST_WATERING_TIMESTAMP[{}] = UTC {}", - i, - LAST_WATERING_TIMESTAMP[i] - ); + log( + LogMessage::RestartToConfig, + RESTART_TO_CONF as u32, + 0, + "", + "", + ); + log( + LogMessage::LowVoltage, + LOW_VOLTAGE_DETECTED as u32, + 0, + "", + "", + ); + // is executed before main, no other code will alter these values during printing + #[allow(static_mut_refs)] + for (i, time) in LAST_WATERING_TIMESTAMP.iter().enumerate() { + info!("LAST_WATERING_TIMESTAMP[{i}] = UTC {time}"); } - for i in 0..PLANT_COUNT { - log::info!( - "CONSECUTIVE_WATERING_PLANT[{}] = {}", - i, - CONSECUTIVE_WATERING_PLANT[i] - ); + // is executed before main, no other code will alter these values during printing + #[allow(static_mut_refs)] + for (i, item) in CONSECUTIVE_WATERING_PLANT.iter().enumerate() { + info!("CONSECUTIVE_WATERING_PLANT[{i}] = {item}"); } } } @@ -696,6 +740,7 @@ impl Esp<'_> { &mut self, network_config: &'static NetworkConfig, stack: Stack<'static>, + spawner: Spawner, ) -> FatResult<()> { let base_topic = network_config .base_topic @@ -718,17 +763,17 @@ impl Esp<'_> { bail!("Mqtt url was empty") } - let last_will_topic = format!("{}/state", base_topic); - let round_trip_topic = format!("{}/internal/roundtrip", base_topic); - let stay_alive_topic = format!("{}/stay_alive", base_topic); + let last_will_topic = format!("{base_topic}/state"); + let round_trip_topic = format!("{base_topic}/internal/roundtrip"); + let stay_alive_topic = format!("{base_topic}/stay_alive"); let mut builder: McutieBuilder<'_, String, PublishDisplay, 0> = McutieBuilder::new(stack, "plant ctrl", mqtt_url); - if network_config.mqtt_user.is_some() && network_config.mqtt_password.is_some() { - builder = builder.with_authentication( - network_config.mqtt_user.as_ref().unwrap().as_str(), - network_config.mqtt_password.as_ref().unwrap().as_str(), - ); + if let (Some(mqtt_user), Some(mqtt_password)) = ( + network_config.mqtt_user.as_ref(), + network_config.mqtt_password.as_ref(), + ) { + builder = builder.with_authentication(mqtt_user, mqtt_password); info!("With authentification"); } @@ -748,57 +793,51 @@ impl Esp<'_> { let keep_alive = Duration::from_secs(60 * 60 * 2).as_secs() as u16; let (receiver, task) = builder.build(keep_alive); - let spawner = Spawner::for_current_executor().await; spawner.spawn(mqtt_incoming_task( receiver, round_trip_topic.clone(), stay_alive_topic.clone(), - ))?; - spawner.spawn(mqtt_runner(task))?; + )?); + spawner.spawn(mqtt_runner(task)?); - LOG_ACCESS - .lock() - .await - .log(LogMessage::StayAlive, 0, 0, "", &stay_alive_topic) - .await; + log(LogMessage::StayAlive, 0, 0, "", &stay_alive_topic); - LOG_ACCESS - .lock() - .await - .log(LogMessage::MqttInfo, 0, 0, "", mqtt_url) - .await; + log(LogMessage::MqttInfo, 0, 0, "", mqtt_url); let mqtt_timeout = 15000; - let timeout = { - let guard = TIME_ACCESS.get().await.lock().await; - guard.current_time_us() - } + mqtt_timeout as u64 * 1000; - while !MQTT_CONNECTED_EVENT_RECEIVED.load(Ordering::Relaxed) { - let cur = TIME_ACCESS.get().await.lock().await.current_time_us(); - if cur > timeout { - bail!("Timeout waiting MQTT connect event") + let res = async { + while !MQTT_CONNECTED_EVENT_RECEIVED.load(Ordering::Relaxed) { + crate::hal::PlantHal::feed_watchdog(); + Timer::after(Duration::from_millis(100)).await; } - Timer::after(Duration::from_millis(100)).await; + Ok::<(), FatError>(()) + } + .with_timeout(Duration::from_millis(mqtt_timeout as u64)) + .await; + + if res.is_err() { + bail!("Timeout waiting MQTT connect event") } - Topic::General(round_trip_topic.clone()) + let _ = Topic::General(round_trip_topic.clone()) .with_display("online_text") .publish() - .await - .unwrap(); + .await; - let timeout = { - let guard = TIME_ACCESS.get().await.lock().await; - guard.current_time_us() - } + mqtt_timeout as u64 * 1000; - while !MQTT_ROUND_TRIP_RECEIVED.load(Ordering::Relaxed) { - let cur = TIME_ACCESS.get().await.lock().await.current_time_us(); - if cur > timeout { - //ensure we do not further try to publish - MQTT_CONNECTED_EVENT_RECEIVED.store(false, Ordering::Relaxed); - bail!("Timeout waiting MQTT roundtrip") + let res = async { + while !MQTT_ROUND_TRIP_RECEIVED.load(Ordering::Relaxed) { + crate::hal::PlantHal::feed_watchdog(); + Timer::after(Duration::from_millis(100)).await; } - Timer::after(Duration::from_millis(100)).await; + Ok::<(), FatError>(()) + } + .with_timeout(Duration::from_millis(mqtt_timeout as u64)) + .await; + + if res.is_err() { + //ensure we do not further try to publish + MQTT_CONNECTED_EVENT_RECEIVED.store(false, Ordering::Relaxed); + bail!("Timeout waiting MQTT roundtrip") } Ok(()) } @@ -864,8 +903,7 @@ impl Esp<'_> { Ok(()) => {} Err(err) => { info!( - "Error during mqtt send on topic {} with message {:#?} error is {:?}", - subtopic, message, err + "Error during mqtt send on topic {subtopic} with message {message:#?} error is {err:?}" ); } }; @@ -893,8 +931,8 @@ async fn mqtt_incoming_task( MQTT_CONNECTED_EVENT_RECEIVED.store(true, Ordering::Relaxed); } MqttMessage::Publish(topic, payload) => match topic { - Topic::DeviceType(type_topic) => {} - Topic::Device(device_topic) => {} + Topic::DeviceType(_type_topic) => {} + Topic::Device(_device_topic) => {} Topic::General(topic) => { let subtopic = topic.as_str(); @@ -907,18 +945,10 @@ async fn mqtt_incoming_task( true => 1, false => 0, }; - LOG_ACCESS - .lock() - .await - .log(LogMessage::MqttStayAliveRec, a, 0, "", "") - .await; + log(LogMessage::MqttStayAliveRec, a, 0, "", ""); MQTT_STAY_ALIVE.store(value, Ordering::Relaxed); } else { - LOG_ACCESS - .lock() - .await - .log(LogMessage::UnknownTopic, 0, 0, "", &*topic) - .await; + log(LogMessage::UnknownTopic, 0, 0, "", &topic); } } }, @@ -931,13 +961,13 @@ async fn mqtt_incoming_task( } #[embassy_executor::task(pool_size = 2)] -async fn net_task(mut runner: Runner<'static, WifiDevice<'static>>) { +async fn net_task(mut runner: Runner<'static, Interface<'static>>) { runner.run().await; } #[embassy_executor::task] -async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) { - use core::net::{Ipv4Addr, SocketAddrV4}; +async fn run_dhcp(stack: Stack<'static>, ip: Ipv4Addr) { + use core::net::SocketAddrV4; use edge_dhcp::{ io::{self, DEFAULT_SERVER_PORT}, @@ -946,21 +976,25 @@ async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) { use edge_nal::UdpBind; use edge_nal_embassy::{Udp, UdpBuffers}; - let ip = Ipv4Addr::from_str(gw_ip_addr).expect("dhcp task failed to parse gw ip"); - let mut buf = [0u8; 1500]; let mut gw_buf = [Ipv4Addr::UNSPECIFIED]; let buffers = UdpBuffers::<3, 1024, 1024, 10>::new(); let unbound_socket = Udp::new(stack, &buffers); - let mut bound_socket = unbound_socket + let mut bound_socket = match unbound_socket .bind(SocketAddr::V4(SocketAddrV4::new( Ipv4Addr::UNSPECIFIED, DEFAULT_SERVER_PORT, ))) .await - .unwrap(); + { + Ok(s) => s, + Err(e) => { + error!("dhcp task failed to bind socket: {:?}", e); + return; + } + }; loop { _ = io::server::run( @@ -970,7 +1004,7 @@ async fn run_dhcp(stack: Stack<'static>, gw_ip_addr: &'static str) { &mut buf, ) .await - .inspect_err(|e| log::warn!("DHCP server error: {e:?}")); + .inspect_err(|e| warn!("DHCP server error: {e:?}")); Timer::after(Duration::from_millis(500)).await; } } diff --git a/rust/src/hal/initial_hal.rs b/rust/src/hal/initial_hal.rs index 6cec44b..38d986b 100644 --- a/rust/src/hal/initial_hal.rs +++ b/rust/src/hal/initial_hal.rs @@ -3,14 +3,14 @@ use crate::fat_error::{FatError, FatResult}; use crate::hal::esp::Esp; use crate::hal::rtc::{BackupHeader, RTCModuleInteraction}; use crate::hal::water::TankSensor; -use crate::hal::{BoardInteraction, FreePeripherals, Sensor, TIME_ACCESS}; +use crate::hal::{BoardInteraction, FreePeripherals, Sensor}; use crate::{ bail, config::PlantControllerConfig, hal::battery::{BatteryInteraction, NoBatteryMonitor}, }; use async_trait::async_trait; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, FixedOffset, Utc}; use esp_hal::gpio::{Level, Output, OutputConfig}; use measurements::{Current, Voltage}; @@ -90,13 +90,22 @@ impl<'a> BoardInteraction<'a> for Initial<'a> { &mut self.rtc } + async fn get_time(&mut self) -> DateTime { + self.esp.get_time() + } + + async fn set_time(&mut self, time: &DateTime) -> FatResult<()> { + self.rtc.set_rtc_time(&time.to_utc()).await?; + self.esp.set_time(time.to_utc()); + Ok(()) + } + async fn set_charge_indicator(&mut self, _charging: bool) -> Result<(), FatError> { bail!("Please configure board revision") } async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { - let rtc = TIME_ACCESS.get().await.lock().await; - self.esp.deep_sleep(duration_in_ms, rtc); + self.esp.deep_sleep(duration_in_ms); } fn is_day(&self) -> bool { false diff --git a/rust/src/hal/little_fs2storage_adapter.rs b/rust/src/hal/little_fs2storage_adapter.rs index 41e660a..c3b7a80 100644 --- a/rust/src/hal/little_fs2storage_adapter.rs +++ b/rust/src/hal/little_fs2storage_adapter.rs @@ -1,7 +1,7 @@ -use embedded_storage::{ReadStorage, Storage}; +use crate::hal::shared_flash::MutexFlashStorage; +use embedded_storage::nor_flash::{check_erase, NorFlash, ReadNorFlash}; use esp_bootloader_esp_idf::partitions::FlashRegion; -use esp_storage::FlashStorage; -use littlefs2::consts::U512 as lfsCache; +use littlefs2::consts::U4096 as lfsCache; use littlefs2::consts::U512 as lfsLookahead; use littlefs2::driver::Storage as lfs2Storage; use littlefs2::io::Error as lfs2Error; @@ -9,26 +9,32 @@ use littlefs2::io::Result as lfs2Result; use log::error; pub struct LittleFs2Filesystem { - pub(crate) storage: &'static mut FlashRegion<'static, FlashStorage>, + pub(crate) storage: &'static mut FlashRegion<'static, MutexFlashStorage>, } impl lfs2Storage for LittleFs2Filesystem { - const READ_SIZE: usize = 256; - const WRITE_SIZE: usize = 512; - const BLOCK_SIZE: usize = 512; //usually optimal for flash access - const BLOCK_COUNT: usize = 8 * 1024 * 1024 / 512; //8mb in 32kb blocks + const READ_SIZE: usize = 4096; + const WRITE_SIZE: usize = 4096; + const BLOCK_SIZE: usize = 4096; //usually optimal for flash access + const BLOCK_COUNT: usize = 8 * 1000 * 1000 / 4096; //8Mb in 4k blocks + a little space for stupid calculation errors const BLOCK_CYCLES: isize = 100; type CACHE_SIZE = lfsCache; type LOOKAHEAD_SIZE = lfsLookahead; fn read(&mut self, off: usize, buf: &mut [u8]) -> lfs2Result { let read_size: usize = Self::READ_SIZE; - assert_eq!(off % read_size, 0); - assert_eq!(buf.len() % read_size, 0); + if off % read_size != 0 { + error!("Littlefs2Filesystem read error: offset not aligned to read size offset: {off} read_size: {read_size}"); + return Err(lfs2Error::IO); + } + if buf.len() % read_size != 0 { + error!("Littlefs2Filesystem read error: length not aligned to read size length: {} read_size: {}", buf.len(), read_size); + return Err(lfs2Error::IO); + } match self.storage.read(off as u32, buf) { Ok(..) => Ok(buf.len()), Err(err) => { - error!("Littlefs2Filesystem read error: {:?}", err); + error!("Littlefs2Filesystem read error: {err:?}"); Err(lfs2Error::IO) } } @@ -36,12 +42,18 @@ impl lfs2Storage for LittleFs2Filesystem { fn write(&mut self, off: usize, data: &[u8]) -> lfs2Result { let write_size: usize = Self::WRITE_SIZE; - assert_eq!(off % write_size, 0); - assert_eq!(data.len() % write_size, 0); + if off % write_size != 0 { + error!("Littlefs2Filesystem write error: offset not aligned to write size offset: {off} write_size: {write_size}"); + return Err(lfs2Error::IO); + } + if data.len() % write_size != 0 { + error!("Littlefs2Filesystem write error: length not aligned to write size length: {} write_size: {}", data.len(), write_size); + return Err(lfs2Error::IO); + } match self.storage.write(off as u32, data) { Ok(..) => Ok(data.len()), Err(err) => { - error!("Littlefs2Filesystem write error: {:?}", err); + error!("Littlefs2Filesystem write error: {err:?}"); Err(lfs2Error::IO) } } @@ -49,15 +61,28 @@ impl lfs2Storage for LittleFs2Filesystem { fn erase(&mut self, off: usize, len: usize) -> lfs2Result { let block_size: usize = Self::BLOCK_SIZE; - debug_assert!(off % block_size == 0); - debug_assert!(len % block_size == 0); - //match self.storage.erase(off as u32, len as u32) { - //anyhow::Result::Ok(..) => lfs2Result::Ok(len), - //Err(err) => { - //error!("Littlefs2Filesystem erase error: {:?}", err); - //Err(lfs2Error::IO) - // } - //} - lfs2Result::Ok(len) + if off % block_size != 0 { + error!("Littlefs2Filesystem erase error: offset not aligned to block size offset: {off} block_size: {block_size}"); + return Err(lfs2Error::IO); + } + if len % block_size != 0 { + error!("Littlefs2Filesystem erase error: length not aligned to block size length: {len} block_size: {block_size}"); + return Err(lfs2Error::IO); + } + + match check_erase(self.storage, off as u32, (off + len) as u32) { + Ok(_) => {} + Err(err) => { + error!("Littlefs2Filesystem check erase error: {err:?}"); + return Err(lfs2Error::IO); + } + } + match self.storage.erase(off as u32, (off + len) as u32) { + Ok(..) => Ok(len), + Err(err) => { + error!("Littlefs2Filesystem erase error: {err:?}"); + Err(lfs2Error::IO) + } + } } } diff --git a/rust/src/hal/mod.rs b/rust/src/hal/mod.rs index 0e83fb0..83e0f07 100644 --- a/rust/src/hal/mod.rs +++ b/rust/src/hal/mod.rs @@ -3,6 +3,7 @@ pub mod esp; mod initial_hal; mod little_fs2storage_adapter; pub(crate) mod rtc; +mod shared_flash; mod v3_hal; mod v3_shift_register; mod v4_hal; @@ -11,6 +12,7 @@ mod water; use crate::alloc::string::ToString; use crate::hal::rtc::{DS3231Module, RTCModuleInteraction}; +use esp_hal::interrupt::software::SoftwareInterruptControl; use esp_hal::peripherals::Peripherals; use esp_hal::peripherals::ADC1; use esp_hal::peripherals::APB_SARADC; @@ -43,6 +45,7 @@ use esp_hal::peripherals::GPIO7; use esp_hal::peripherals::GPIO8; use esp_hal::peripherals::PCNT; use esp_hal::peripherals::TWAI0; +use portable_atomic::AtomicBool; use crate::{ bail, @@ -71,23 +74,29 @@ use eeprom24x::{Eeprom24x, SlaveAddr, Storage}; use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::blocking_mutex::CriticalSectionMutex; +use embedded_storage::nor_flash::RmwNorFlashStorage; +use embedded_storage::ReadStorage; use esp_bootloader_esp_idf::partitions::{ - AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry, + AppPartitionSubType, DataPartitionSubType, FlashRegion, PartitionEntry, PartitionTable, + PartitionType, }; use esp_hal::clock::CpuClock; use esp_hal::gpio::{Input, InputConfig, Pull}; +use esp_hal::uart::{Config as UartConfig, Uart}; +use esp_storage::FlashStorage; +use lib_bms_protocol::{BmsReadable, ProtocolVersion}; use measurements::{Current, Voltage}; -use crate::fat_error::{FatError, FatResult}; +use crate::fat_error::{ContextExt, FatError, FatResult}; use crate::hal::battery::{print_battery_bq34z100, BQ34Z100G1}; use crate::hal::little_fs2storage_adapter::LittleFs2Filesystem; use crate::hal::water::TankSensor; -use crate::log::LOG_ACCESS; +use crate::log::log; use embassy_sync::mutex::Mutex; use embassy_sync::once_lock::OnceLock; use esp_alloc as _; use esp_backtrace as _; -use esp_bootloader_esp_idf::ota::Slot; +use esp_bootloader_esp_idf::ota::{OtaImageState, Ota}; use esp_hal::delay::Delay; use esp_hal::i2c::master::{BusTimeout, Config, I2c}; use esp_hal::pcnt::unit::Unit; @@ -96,19 +105,25 @@ use esp_hal::rng::Rng; use esp_hal::rtc_cntl::{Rtc, SocResetReason}; use esp_hal::system::reset_reason; use esp_hal::time::Rate; -use esp_hal::timer::timg::TimerGroup; +use esp_hal::timer::timg::{TimerGroup, Wdt}; use esp_hal::Blocking; -use esp_storage::FlashStorage; -use esp_wifi::{init, EspWifiController}; use littlefs2::fs::{Allocation, Filesystem as lfs2Filesystem}; use littlefs2::object_safe::DynStorage; -use log::{info, warn}; +use log::{error, info, warn}; +use shared_flash::MutexFlashStorage; -pub static TIME_ACCESS: OnceLock> = OnceLock::new(); +pub static PROGRESS_ACTIVE: AtomicBool = AtomicBool::new(false); //Only support for 8 right now! pub const PLANT_COUNT: usize = 8; +pub static WATCHDOG: OnceLock< + embassy_sync::blocking_mutex::Mutex< + CriticalSectionRawMutex, + RefCell>, + >, +> = OnceLock::new(); + const TANK_MULTI_SAMPLE: usize = 11; pub static I2C_DRIVER: OnceLock< embassy_sync::blocking_mutex::Mutex>>, @@ -126,6 +141,70 @@ pub struct HAL<'a> { pub board_hal: Box + Send>, } +fn ota_state( + slot: AppPartitionSubType, + ota_data: &mut FlashRegion>, +) -> OtaImageState { + // Read and log OTA states for both slots before constructing Ota + // Each OTA select entry is 32 bytes: [seq:4][label:20][state:4][crc:4] + // Offsets within the OTA data partition: slot0 @ 0x0000, slot1 @ 0x1000 + let mut slot_buf = [0u8; 32]; + if slot == AppPartitionSubType::Ota0 { + let _ = ReadStorage::read(ota_data, 0x0000, &mut slot_buf); + } else { + let _ = ReadStorage::read(ota_data, 0x1000, &mut slot_buf); + } + let raw_state = u32::from_le_bytes(slot_buf[24..28].try_into().unwrap_or([0xff; 4])); + + OtaImageState::try_from(raw_state).unwrap_or(OtaImageState::Undefined) +} +fn get_current_slot( + pt: &PartitionTable, + ota: &mut Ota>, +) -> Result { + let booted = pt.booted_partition()?.ok_or(FatError::OTAError)?; + let booted_type = booted.partition_type(); + let booted_ota_type = match booted_type { + PartitionType::App(subtype) => subtype, + _ => { + bail!("Booted partition is not an app partition"); + } + }; + + let expected_partition = ota.current_app_partition()?; + if expected_partition == booted_ota_type { + info!("Booted partition matches expected partition"); + } else { + info!("Booted partition does not match expected partition, fixing ota entry"); + ota.set_current_app_partition(booted_ota_type)?; + } + + let fixed = ota.current_app_partition()?; + let state = ota.current_ota_state(); + info!("Expected partition: {expected_partition:?}, current partition: {booted_ota_type:?}, state: {state:?}"); + + if fixed != booted_ota_type { + bail!( + "Could not fix ota entry, booted partition is still not correct: {:?} != {:?}", + booted_ota_type, + fixed + ); + } + + Ok(booted_ota_type) +} + +pub fn next_partition(current: AppPartitionSubType) -> FatResult { + let next = match current { + AppPartitionSubType::Ota0 => AppPartitionSubType::Ota1, + AppPartitionSubType::Ota1 => AppPartitionSubType::Ota0, + _ => { + bail!("Current slot is not ota0 or ota1"); + } + }; + Ok(next) +} + #[async_trait] pub trait BoardInteraction<'a> { fn get_tank_sensor(&mut self) -> Result<&mut TankSensor<'a>, FatError>; @@ -133,6 +212,8 @@ pub trait BoardInteraction<'a> { fn get_config(&mut self) -> &PlantControllerConfig; fn get_battery_monitor(&mut self) -> &mut Box; fn get_rtc_module(&mut self) -> &mut Box; + async fn get_time(&mut self) -> DateTime; + async fn set_time(&mut self, time: &DateTime) -> FatResult<()>; async fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError>; async fn deep_sleep(&mut self, duration_in_ms: u64) -> !; @@ -194,13 +275,7 @@ pub struct FreePeripherals<'a> { pub gpio21: GPIO21<'a>, pub gpio22: GPIO22<'a>, pub gpio23: GPIO23<'a>, - pub gpio24: GPIO24<'a>, - pub gpio25: GPIO25<'a>, - pub gpio26: GPIO26<'a>, pub gpio27: GPIO27<'a>, - pub gpio28: GPIO28<'a>, - pub gpio29: GPIO29<'a>, - pub gpio30: GPIO30<'a>, pub twai: TWAI0<'a>, pub pcnt0: Unit<'a, 0>, pub pcnt1: Unit<'a, 1>, @@ -224,14 +299,12 @@ impl PlantHal { esp_alloc::heap_allocator!(size: 64 * 1024); esp_alloc::heap_allocator!(#[link_section = ".dram2_uninit"] size: 64000); - let rtc: Rtc = Rtc::new(peripherals.LPWR); - TIME_ACCESS - .init(Mutex::new(rtc)) - .map_err(|_| FatError::String { - error: "Init error rct".to_string(), - })?; + let mut rtc_peripheral: Rtc = Rtc::new(peripherals.LPWR); + rtc_peripheral.rwdt.disable(); - let systimer = SystemTimer::new(peripherals.SYSTIMER); + let timg0 = TimerGroup::new(peripherals.TIMG0); + let sw_int = SoftwareInterruptControl::new(peripherals.SW_INTERRUPT); + esp_rtos::start(timg0.timer0, sw_int.software_interrupt0); let boot_button = Input::new( peripherals.GPIO9, @@ -241,29 +314,13 @@ impl PlantHal { // Reserve GPIO1 for deep sleep wake (configured just before entering sleep) let wake_gpio1 = peripherals.GPIO1; - let rng = Rng::new(peripherals.RNG); - let timg0 = TimerGroup::new(peripherals.TIMG0); - let esp_wifi_ctrl = &*mk_static!( - EspWifiController<'static>, - init(timg0.timer0, rng.clone()).expect("Could not init wifi controller") - ); - - let (controller, interfaces) = - esp_wifi::wifi::new(&esp_wifi_ctrl, peripherals.WIFI).expect("Could not init wifi"); - - use esp_hal::timer::systimer::SystemTimer; - esp_hal_embassy::init(systimer.alarm0); - - //let mut adc1 = Adc::new(peripherals.ADC1, adc1_config); - // + let rng = Rng::new(); + let (controller, interfaces) = esp_radio::wifi::new(peripherals.WIFI, Default::default()) + .expect("Could not init wifi"); let pcnt_module = Pcnt::new(peripherals.PCNT); let free_pins = FreePeripherals { - // can: peripherals.can, - // adc1: peripherals.adc1, - // pcnt0: peripherals.pcnt0, - // pcnt1: peripherals.pcnt1, gpio0: peripherals.GPIO0, gpio2: peripherals.GPIO2, gpio3: peripherals.GPIO3, @@ -284,13 +341,7 @@ impl PlantHal { gpio21: peripherals.GPIO21, gpio22: peripherals.GPIO22, gpio23: peripherals.GPIO23, - gpio24: peripherals.GPIO24, - gpio25: peripherals.GPIO25, - gpio26: peripherals.GPIO26, gpio27: peripherals.GPIO27, - gpio28: peripherals.GPIO28, - gpio29: peripherals.GPIO29, - gpio30: peripherals.GPIO30, twai: peripherals.TWAI0, pcnt0: pcnt_module.unit0, pcnt1: pcnt_module.unit1, @@ -301,14 +352,19 @@ impl PlantHal { [u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN], [0u8; esp_bootloader_esp_idf::partitions::PARTITION_TABLE_MAX_LEN] ); - let storage_ota = mk_static!(FlashStorage, FlashStorage::new()); - let pt = - esp_bootloader_esp_idf::partitions::read_partition_table(storage_ota, tablebuffer)?; - // List all partitions - this is just FYI - for i in 0..pt.len() { - info!("{:?}", pt.get_partition(i)); - } + let bullshit = MutexFlashStorage { + inner: Arc::new(CriticalSectionMutex::new(RefCell::new(FlashStorage::new( + peripherals.FLASH, + )))), + }; + let flash_storage = mk_static!(MutexFlashStorage, bullshit.clone()); + let flash_storage_2 = mk_static!(MutexFlashStorage, bullshit.clone()); + let flash_storage_3 = mk_static!(MutexFlashStorage, bullshit.clone()); + + let pt = + esp_bootloader_esp_idf::partitions::read_partition_table(flash_storage, tablebuffer)?; + let ota_data = mk_static!( PartitionEntry, pt.find_partition(esp_bootloader_esp_idf::partitions::PartitionType::Data( @@ -317,34 +373,39 @@ impl PlantHal { .expect("No OTA data partition found") ); - let ota_data = mk_static!( - FlashRegion, - ota_data.as_embedded_storage(storage_ota) - ); + let mut ota_data = ota_data.as_embedded_storage(mk_static!( + RmwNorFlashStorage<&mut MutexFlashStorage>, + RmwNorFlashStorage::new(flash_storage_2, mk_static!([u8; 4096], [0_u8; 4096])) + )); - let mut ota = esp_bootloader_esp_idf::ota::Ota::new(ota_data)?; + let state_0 = ota_state(AppPartitionSubType::Ota0, &mut ota_data); + let state_1 = ota_state(AppPartitionSubType::Ota1, &mut ota_data); + let mut ota = Ota::new(ota_data, 2)?; + let running = get_current_slot(&pt, &mut ota)?; + let target = next_partition(running)?; - let ota_partition = match ota.current_slot()? { - Slot::None => { - panic!("No OTA slot active?"); + info!("Currently running OTA slot: {running:?}"); + info!("Updates will be stored in OTA slot: {target:?}"); + info!("Slot0 state: {state_0:?}"); + info!("Slot1 state: {state_1:?}"); + + //get current_state and next_state here! + let ota_target = match target { + AppPartitionSubType::Ota0 => pt + .find_partition(PartitionType::App(AppPartitionSubType::Ota0))? + .context("Partition table invalid no ota0")?, + AppPartitionSubType::Ota1 => pt + .find_partition(PartitionType::App(AppPartitionSubType::Ota1))? + .context("Partition table invalid no ota1")?, + _ => { + bail!("Invalid target partition"); } - Slot::Slot0 => pt - .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App( - AppPartitionSubType::Ota0, - ))? - .expect("No OTA slot0 found"), - Slot::Slot1 => pt - .find_partition(esp_bootloader_esp_idf::partitions::PartitionType::App( - AppPartitionSubType::Ota1, - ))? - .expect("No OTA slot1 found"), }; - let ota_next = mk_static!(PartitionEntry, ota_partition); - let storage_ota = mk_static!(FlashStorage, FlashStorage::new()); - let ota_next = mk_static!( - FlashRegion, - ota_next.as_embedded_storage(storage_ota) + let ota_target = mk_static!(PartitionEntry, ota_target); + let ota_target = mk_static!( + FlashRegion, + ota_target.as_embedded_storage(flash_storage) ); let data_partition = pt @@ -354,32 +415,38 @@ impl PlantHal { .expect("Data partition with littlefs not found"); let data_partition = mk_static!(PartitionEntry, data_partition); - let storage_data = mk_static!(FlashStorage, FlashStorage::new()); let data = mk_static!( - FlashRegion, - data_partition.as_embedded_storage(storage_data) + FlashRegion, + data_partition.as_embedded_storage(flash_storage_3) ); let lfs2filesystem = mk_static!(LittleFs2Filesystem, LittleFs2Filesystem { storage: data }); let alloc = mk_static!(Allocation, lfs2Filesystem::allocate()); if lfs2filesystem.is_mountable() { - log::info!("Littlefs2 filesystem is mountable"); + info!("Littlefs2 filesystem is mountable"); } else { match lfs2filesystem.format() { - Result::Ok(..) => { - log::info!("Littlefs2 filesystem is formatted"); + Ok(..) => { + info!("Littlefs2 filesystem is formatted"); } Err(err) => { - bail!("Littlefs2 filesystem could not be formatted: {:?}", err); + error!("Littlefs2 filesystem could not be formatted: {err:?}"); } } } + #[allow(clippy::arc_with_non_send_sync)] let fs = Arc::new(Mutex::new( lfs2Filesystem::mount(alloc, lfs2filesystem).expect("Could not mount lfs2 filesystem"), )); - let ap = interfaces.ap; - let sta = interfaces.sta; + + let uart0 = + Uart::new(peripherals.UART0, UartConfig::default()).map_err(|_| FatError::String { + error: "Uart creation failed".to_string(), + })?; + + let ap = interfaces.access_point; + let sta = interfaces.station; let mut esp = Esp { fs, rng, @@ -389,7 +456,12 @@ impl PlantHal { boot_button, wake_gpio1, ota, - ota_next, + ota_target, + current: running, + slot0_state: state_0, + slot1_state: state_1, + uart0, + rtc: rtc_peripheral, }; //init,reset rtc memory depending on cause @@ -425,24 +497,21 @@ impl PlantHal { SocResetReason::Cpu0JtagCpu => "cpu0 jtag cpu", }, }; - LOG_ACCESS - .lock() - .await - .log( - LogMessage::ResetReason, - init_rtc_store as u32, - to_config_mode as u32, - "", - &format!("{reasons:?}"), - ) - .await; + log( + LogMessage::ResetReason, + init_rtc_store as u32, + to_config_mode as u32, + "", + &format!("{reasons:?}"), + ); + esp.init_rtc_deepsleep_memory(init_rtc_store, to_config_mode) .await; let config = esp.load_config().await; - log::info!("Init rtc driver"); + info!("Init rtc driver"); let sda = peripherals.GPIO20; let scl = peripherals.GPIO19; @@ -460,26 +529,30 @@ impl PlantHal { RefCell>, > = CriticalSectionMutex::new(RefCell::new(i2c)); + I2C_DRIVER.init(i2c_bus).expect("Could not init i2c driver"); let i2c_bus = I2C_DRIVER.get().await; - let rtc_device = I2cDevice::new(&i2c_bus); - let eeprom_device = I2cDevice::new(&i2c_bus); + let rtc_device = I2cDevice::new(i2c_bus); + let mut bms_device = I2cDevice::new(i2c_bus); + let eeprom_device = I2cDevice::new(i2c_bus); + let mut rtc: Ds323x< I2cInterface>>, DS3231, > = Ds323x::new_ds3231(rtc_device); + info!("Init rtc eeprom driver"); let eeprom = Eeprom24x::new_24x32(eeprom_device, SlaveAddr::Alternative(true, true, true)); let rtc_time = rtc.datetime(); match rtc_time { Ok(tt) => { - log::info!("Rtc Module reports time at UTC {}", tt); + info!("Rtc Module reports time at UTC {tt}"); } Err(err) => { - log::info!("Rtc Module could not be read {:?}", err); + info!("Rtc Module could not be read {err:?}"); } } @@ -494,40 +567,26 @@ impl PlantHal { Box::new(DS3231Module { rtc, storage }) as Box; let hal = match config { - Result::Ok(config) => { + Ok(config) => { let battery_interaction: Box = match config.hardware.battery { BatteryBoardVersion::Disabled => Box::new(NoBatteryMonitor {}), - BatteryBoardVersion::BQ34Z100G1 => { - let battery_device = I2cDevice::new(I2C_DRIVER.get().await); - let mut battery_driver = Bq34z100g1Driver { - i2c: battery_device, - delay: Delay::new(), - flash_block_data: [0; 32], - }; - let status = print_battery_bq34z100(&mut battery_driver); - match status { - Ok(_) => {} - Err(err) => { - LOG_ACCESS - .lock() - .await - .log( - LogMessage::BatteryCommunicationError, - 0u32, - 0, - "", - &format!("{err:?})"), - ) - .await; - } - } - Box::new(BQ34Z100G1 { battery_driver }) - } BatteryBoardVersion::WchI2cSlave => { - // TODO use correct implementation once availible - Box::new(NoBatteryMonitor {}) + let version = ProtocolVersion::read_from_i2c(&mut bms_device); + let version_val = match version { + Ok(v) => unsafe { core::mem::transmute::(v) }, + Err(_) => 0, + }; + if version_val == 1 { + //Box::new(WCHI2CSlave { i2c: bms_device }) + // todo fix the type above + Box::new(NoBatteryMonitor {}) + } else { + //todo should be an error variant instead? + Box::new(NoBatteryMonitor {}) + } } + BatteryBoardVersion::BQ34Z100G1 => Box::new(NoBatteryMonitor {}), }; let board_hal: Box = match config.hardware.board { @@ -546,17 +605,13 @@ impl PlantHal { HAL { board_hal } } Err(err) => { - LOG_ACCESS - .lock() - .await - .log( - LogMessage::ConfigModeMissingConfig, - 0, - 0, - "", - &err.to_string(), - ) - .await; + log( + LogMessage::ConfigModeMissingConfig, + 0, + 0, + "", + &err.to_string(), + ); HAL { board_hal: initial_hal::create_initial_board( free_pins, @@ -569,25 +624,13 @@ impl PlantHal { Ok(Mutex::new(hal)) } -} -pub async fn esp_time() -> DateTime { - let guard = TIME_ACCESS.get().await.lock().await; - DateTime::from_timestamp_micros(guard.current_time_us() as i64).unwrap() -} - -pub async fn esp_set_time(time: DateTime) -> FatResult<()> { - { - let guard = TIME_ACCESS.get().await.lock().await; - guard.set_current_time_us(time.timestamp_micros() as u64); + /// Feed the watchdog timer to prevent system reset + pub fn feed_watchdog() { + if let Some(wdt_mutex) = WATCHDOG.try_get() { + wdt_mutex.lock(|cell| { + cell.borrow_mut().feed(); + }); + } } - BOARD_ACCESS - .get() - .await - .lock() - .await - .board_hal - .get_rtc_module() - .set_rtc_time(&time.to_utc()) - .await } diff --git a/rust/src/hal/shared_flash.rs b/rust/src/hal/shared_flash.rs new file mode 100644 index 0000000..0bd37c8 --- /dev/null +++ b/rust/src/hal/shared_flash.rs @@ -0,0 +1,65 @@ +use alloc::sync::Arc; +use core::cell::RefCell; +use core::ops::{Deref, DerefMut}; +use embassy_sync::blocking_mutex::CriticalSectionMutex; +use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; +use embedded_storage::ReadStorage; +use esp_storage::{FlashStorage, FlashStorageError}; +use log::info; + +#[derive(Clone)] +pub struct MutexFlashStorage { + pub(crate) inner: Arc>>>, +} + +impl ReadStorage for MutexFlashStorage { + type Error = FlashStorageError; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), FlashStorageError> { + self.inner + .lock(|f| ReadStorage::read(f.borrow_mut().deref_mut(), offset, bytes)) + } + + fn capacity(&self) -> usize { + self.inner + .lock(|f| ReadStorage::capacity(f.borrow().deref())) + } +} + +impl embedded_storage::Storage for MutexFlashStorage { + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + NorFlash::write(self, offset, bytes) + } +} + +impl ErrorType for MutexFlashStorage { + type Error = FlashStorageError; +} + +impl ReadNorFlash for MutexFlashStorage { + const READ_SIZE: usize = 1; + + fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { + ReadStorage::read(self, offset, bytes) + } + + fn capacity(&self) -> usize { + ReadStorage::capacity(self) + } +} + +impl NorFlash for MutexFlashStorage { + const WRITE_SIZE: usize = 1; + const ERASE_SIZE: usize = 4096; + + fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { + info!("Erasing flash from 0x{:x} to 0x{:x}", from, to); + self.inner + .lock(|f| NorFlash::erase(f.borrow_mut().deref_mut(), from, to)) + } + + fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { + self.inner + .lock(|f| NorFlash::write(f.borrow_mut().deref_mut(), offset, bytes)) + } +} diff --git a/rust/src/hal/v3_hal.rs b/rust/src/hal/v3_hal.rs index 5135323..f9c0437 100644 --- a/rust/src/hal/v3_hal.rs +++ b/rust/src/hal/v3_hal.rs @@ -1,11 +1,11 @@ use crate::bail; -use crate::fat_error::FatError; +use crate::fat_error::{FatError, FatResult}; use crate::hal::esp::{hold_disable, hold_enable}; use crate::hal::rtc::RTCModuleInteraction; use crate::hal::v3_shift_register::ShiftRegister40; use crate::hal::water::TankSensor; -use crate::hal::{BoardInteraction, FreePeripherals, Sensor, PLANT_COUNT, TIME_ACCESS}; -use crate::log::{LogMessage, LOG_ACCESS}; +use crate::hal::{BoardInteraction, FreePeripherals, Sensor, PLANT_COUNT}; +use crate::log::{log, LogMessage, LOG_ACCESS}; use crate::{ config::PlantControllerConfig, hal::{battery::BatteryInteraction, esp::Esp}, @@ -14,6 +14,7 @@ use alloc::boxed::Box; use alloc::format; use alloc::string::ToString; use async_trait::async_trait; +use chrono::{DateTime, FixedOffset, Utc}; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::Mutex; use embassy_time::Timer; @@ -195,6 +196,17 @@ impl<'a> BoardInteraction<'a> for V3<'a> { fn get_rtc_module(&mut self) -> &mut Box { &mut self.rtc_module } + + async fn get_time(&mut self) -> DateTime { + self.esp.get_time() + } + + async fn set_time(&mut self, time: &DateTime) -> FatResult<()> { + self.rtc_module.set_rtc_time(&time.to_utc()).await?; + self.esp.set_time(time.to_utc()); + Ok(()) + } + async fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError> { let shift_register = self.shift_register.lock().await; if charging { @@ -207,8 +219,7 @@ impl<'a> BoardInteraction<'a> for V3<'a> { async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { let _ = self.shift_register.lock().await.decompose()[AWAKE].set_low(); - let guard = TIME_ACCESS.get().await.lock().await; - self.esp.deep_sleep(duration_in_ms, guard) + self.esp.deep_sleep(duration_in_ms) } fn is_day(&self) -> bool { @@ -362,17 +373,13 @@ impl<'a> BoardInteraction<'a> for V3<'a> { Timer::after_millis(10).await; let unscaled = self.signal_counter.value(); let hz = unscaled as f32 * factor; - LOG_ACCESS - .lock() - .await - .log( - LogMessage::RawMeasure, - unscaled as u32, - hz as u32, - &plant.to_string(), - &format!("{sensor:?}"), - ) - .await; + log( + LogMessage::RawMeasure, + unscaled as u32, + hz as u32, + &plant.to_string(), + &format!("{sensor:?}"), + ); results[repeat] = hz; } results.sort_by(|a, b| a.partial_cmp(b).unwrap()); // floats don't seem to implement total_ord @@ -425,11 +432,7 @@ impl<'a> BoardInteraction<'a> for V3<'a> { Ok(b) => b as u32, Err(_) => u32::MAX, }; - LOG_ACCESS - .lock() - .await - .log(LogMessage::TestSensor, aa, bb, &plant.to_string(), "") - .await; + log(LogMessage::TestSensor, aa, bb, &plant.to_string(), ""); } Timer::after_millis(10).await; Ok(()) diff --git a/rust/src/hal/v4_hal.rs b/rust/src/hal/v4_hal.rs index adafa83..0a85231 100644 --- a/rust/src/hal/v4_hal.rs +++ b/rust/src/hal/v4_hal.rs @@ -3,10 +3,11 @@ use crate::hal::battery::BatteryInteraction; use crate::hal::esp::{hold_disable, hold_enable, Esp}; use crate::hal::rtc::RTCModuleInteraction; use crate::hal::water::TankSensor; -use crate::hal::{BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT, TIME_ACCESS}; +use crate::hal::{BoardInteraction, FreePeripherals, Sensor, I2C_DRIVER, PLANT_COUNT}; use alloc::boxed::Box; use alloc::string::ToString; use async_trait::async_trait; +use chrono::{DateTime, FixedOffset, Utc}; use embassy_embedded_hal::shared_bus::blocking::i2c::I2cDevice; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_time::Timer; @@ -326,6 +327,16 @@ impl<'a> BoardInteraction<'a> for V4<'a> { &mut self.rtc_module } + async fn get_time(&mut self) -> DateTime { + self.esp.get_time() + } + + async fn set_time(&mut self, time: &DateTime) -> FatResult<()> { + self.rtc_module.set_rtc_time(&time.to_utc()).await?; + self.esp.set_time(time.to_utc()); + Ok(()) + } + async fn set_charge_indicator(&mut self, charging: bool) -> Result<(), FatError> { self.charger.set_charge_indicator(charging) } @@ -333,8 +344,7 @@ impl<'a> BoardInteraction<'a> for V4<'a> { async fn deep_sleep(&mut self, duration_in_ms: u64) -> ! { self.awake.set_low(); self.charger.power_save(); - let rtc = TIME_ACCESS.get().await.lock().await; - self.esp.deep_sleep(duration_in_ms, rtc); + self.esp.deep_sleep(duration_in_ms); } fn is_day(&self) -> bool { diff --git a/rust/src/hal/water.rs b/rust/src/hal/water.rs index b1fb5a3..5f3b457 100644 --- a/rust/src/hal/water.rs +++ b/rust/src/hal/water.rs @@ -2,22 +2,25 @@ use crate::bail; use crate::fat_error::FatError; use crate::hal::{ADC1, TANK_MULTI_SAMPLE}; use embassy_time::Timer; -use esp_hal::analog::adc::{Adc, AdcConfig, AdcPin, Attenuation}; +use esp_hal::analog::adc::{Adc, AdcCalLine, AdcConfig, AdcPin, Attenuation}; use esp_hal::delay::Delay; -use esp_hal::gpio::{Flex, Input, Output, OutputConfig, Pull}; +use esp_hal::gpio::{DriveMode, Flex, Input, InputConfig, Output, OutputConfig, Pull}; +use esp_hal::pcnt::channel::CtrlMode::Keep; +use esp_hal::pcnt::channel::EdgeMode::{Hold, Increment}; use esp_hal::pcnt::unit::Unit; use esp_hal::peripherals::GPIO5; -use esp_hal::Blocking; +use esp_hal::Async; use esp_println::println; use onewire::{ds18b20, Device, DeviceSearch, OneWire, DS18B20}; +unsafe impl Send for TankSensor<'_> {} + pub struct TankSensor<'a> { one_wire_bus: OneWire>, - tank_channel: Adc<'a, ADC1<'a>, Blocking>, + tank_channel: Adc<'a, ADC1<'a>, Async>, tank_power: Output<'a>, - tank_pin: AdcPin, ADC1<'a>>, - // flow_counter: PcntDriver<'a>, - // delay: Delay, + tank_pin: AdcPin, ADC1<'a>, AdcCalLine>>, + flow_counter: Unit<'a, 1>, } impl<'a> TankSensor<'a> { @@ -29,62 +32,55 @@ impl<'a> TankSensor<'a> { flow_sensor: Input, pcnt1: Unit<'a, 1>, ) -> Result, FatError> { - one_wire_pin.apply_output_config(&OutputConfig::default().with_pull(Pull::None)); + one_wire_pin.apply_output_config( + &OutputConfig::default() + .with_drive_mode(DriveMode::OpenDrain) + .with_pull(Pull::None), + ); + one_wire_pin.apply_input_config(&InputConfig::default().with_pull(Pull::None)); + one_wire_pin.set_high(); + one_wire_pin.set_input_enable(true); + one_wire_pin.set_output_enable(true); let mut adc1_config = AdcConfig::new(); - let tank_pin = adc1_config.enable_pin(gpio5, Attenuation::_11dB); - let tank_channel = Adc::new(adc1, adc1_config); + let tank_pin = + adc1_config.enable_pin_with_cal::<_, AdcCalLine<_>>(gpio5, Attenuation::_11dB); + let tank_channel = Adc::new(adc1, adc1_config).into_async(); let one_wire_bus = OneWire::new(one_wire_pin, false); - // - // let mut flow_counter = PcntDriver::new( - // pcnt1, - // Some(flow_sensor_pin), - // Option::::None, - // Option::::None, - // Option::::None, - // )?; - // - // flow_counter.channel_config( - // PcntChannel::Channel1, - // PinIndex::Pin0, - // PinIndex::Pin1, - // &PcntChannelConfig { - // lctrl_mode: PcntControlMode::Keep, - // hctrl_mode: PcntControlMode::Keep, - // pos_mode: PcntCountMode::Increment, - // neg_mode: PcntCountMode::Hold, - // counter_h_lim: i16::MAX, - // counter_l_lim: 0, - // }, - // )?; - // + pcnt1.set_high_limit(Some(i16::MAX))?; + + let ch0 = &pcnt1.channel0; + ch0.set_edge_signal(flow_sensor.peripheral_input()); + ch0.set_input_mode(Hold, Increment); + ch0.set_ctrl_mode(Keep, Keep); + pcnt1.listen(); + Ok(TankSensor { one_wire_bus, tank_channel, tank_power, - tank_pin, // flow_counter, - // delay: Default::default(), + tank_pin, + flow_counter: pcnt1, }) } pub fn reset_flow_meter(&mut self) { - // self.flow_counter.counter_pause().unwrap(); - // self.flow_counter.counter_clear().unwrap(); + self.flow_counter.pause(); + self.flow_counter.clear(); } pub fn start_flow_meter(&mut self) { - //self.flow_counter.counter_resume().unwrap(); + self.flow_counter.resume(); } pub fn get_flow_meter_value(&mut self) -> i16 { - //self.flow_counter.get_counter_value().unwrap() - 5_i16 + self.flow_counter.value() } pub fn stop_flow_meter(&mut self) -> i16 { - //self.flow_counter.counter_pause().unwrap(); + self.flow_counter.pause(); self.get_flow_meter_value() } @@ -92,15 +88,33 @@ impl<'a> TankSensor<'a> { //multisample should be moved to water_temperature_c let mut attempt = 1; let mut delay = Delay::new(); - self.one_wire_bus.reset(&mut delay)?; + + let presence = self.one_wire_bus.reset(&mut delay)?; + println!("OneWire: reset presence pulse = {}", presence); + if !presence { + println!("OneWire: no device responded to reset — check pull-up resistor and wiring"); + } + let mut search = DeviceSearch::new(); let mut water_temp_sensor: Option = None; + let mut devices_found = 0u8; while let Some(device) = self.one_wire_bus.search_next(&mut search, &mut delay)? { + devices_found += 1; + println!( + "OneWire: found device #{} family=0x{:02X} addr={:02X?}", + devices_found, device.address[0], device.address + ); if device.address[0] == ds18b20::FAMILY_CODE { water_temp_sensor = Some(device); break; + } else { + println!("OneWire: skipping device — not a DS18B20 (family 0x{:02X} != 0x{:02X})", device.address[0], ds18b20::FAMILY_CODE); } } + if devices_found == 0 { + println!("OneWire: search found zero devices on the bus"); + } + match water_temp_sensor { Some(device) => { println!("Found one wire device: {:?}", device); @@ -152,17 +166,15 @@ impl<'a> TankSensor<'a> { Timer::after_millis(100).await; let mut store = [0_u16; TANK_MULTI_SAMPLE]; - for multisample in 0..TANK_MULTI_SAMPLE { - let value = self.tank_channel.read_oneshot(&mut self.tank_pin); - //force yield + for sample in store.iter_mut() { + *sample = self.tank_channel.read_oneshot(&mut self.tank_pin).await; + //force yield between successful samples Timer::after_millis(10).await; - store[multisample] = value.unwrap(); } self.tank_power.set_low(); store.sort(); - //TODO probably wrong? check! - let median_mv = store[6] as f32 * 3300_f32 / 4096_f32; - Ok(median_mv) + let median_mv = store[TANK_MULTI_SAMPLE / 2] as f32; + Ok(median_mv / 1000.0) } } diff --git a/rust/src/log/interceptor.rs b/rust/src/log/interceptor.rs new file mode 100644 index 0000000..adeb034 --- /dev/null +++ b/rust/src/log/interceptor.rs @@ -0,0 +1,108 @@ +use alloc::string::String; +use alloc::vec::Vec; +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::blocking_mutex::Mutex as BlockingMutex; +use log::{LevelFilter, Log, Metadata, Record}; + +const MAX_LIVE_LOG_ENTRIES: usize = 64; + +struct LiveLogBuffer { + entries: Vec<(u64, String)>, + next_seq: u64, +} + +impl LiveLogBuffer { + const fn new() -> Self { + Self { + entries: Vec::new(), + next_seq: 0, + } + } + + fn push(&mut self, text: String) { + if self.entries.len() >= MAX_LIVE_LOG_ENTRIES { + self.entries.remove(0); + } + self.entries.push((self.next_seq, text)); + self.next_seq += 1; + } + + fn get_after(&self, after: Option) -> (Vec<(u64, String)>, bool, u64) { + let next_seq = self.next_seq; + match after { + None => (self.entries.clone(), false, next_seq), + Some(after_seq) => { + let result: Vec<_> = self.entries + .iter() + .filter(|(seq, _)| *seq > after_seq) + .cloned() + .collect(); + + // Dropped if there are entries that should exist (seq > after_seq) but + // the oldest retained entry has a higher seq than after_seq + 1. + let dropped = if next_seq > after_seq.saturating_add(1) { + if let Some((oldest_seq, _)) = self.entries.first() { + *oldest_seq > after_seq.saturating_add(1) + } else { + // Buffer empty but entries were written — all dropped + true + } + } else { + false + }; + + (result, dropped, next_seq) + } + } + } +} + +pub struct InterceptorLogger { + live_log: BlockingMutex>, +} + +impl InterceptorLogger { + pub const fn new() -> Self { + Self { + live_log: BlockingMutex::new(core::cell::RefCell::new(LiveLogBuffer::new())), + } + } + + /// Returns (entries_after, dropped, next_seq). + /// Pass `after = None` to retrieve the entire current buffer. + /// Pass `after = Some(seq)` to retrieve only entries with seq > that value. + pub fn get_live_logs(&self, after: Option) -> (Vec<(u64, String)>, bool, u64) { + self.live_log.lock(|buf| buf.borrow().get_after(after)) + } + + pub fn init(&'static self) { + match log::set_logger(self).map(|()| log::set_max_level(LevelFilter::Info)) { + Ok(()) => {} + Err(_e) => { + esp_println::println!("ERROR: Logger already set"); + } + } + } +} + +impl Log for InterceptorLogger { + fn enabled(&self, metadata: &Metadata) -> bool { + metadata.level() <= log::Level::Info + } + + fn log(&self, record: &Record) { + if self.enabled(record.metadata()) { + let message = alloc::format!("{}: {}", record.level(), record.args()); + + // Print to serial + esp_println::println!("{}", message); + + // Store in live log ring buffer + self.live_log.lock(|buf| { + buf.borrow_mut().push(message); + }); + } + } + + fn flush(&self) {} +} diff --git a/rust/src/log/mod.rs b/rust/src/log/mod.rs index 7d9a7cb..a1df0dd 100644 --- a/rust/src/log/mod.rs +++ b/rust/src/log/mod.rs @@ -1,20 +1,21 @@ -use crate::hal::TIME_ACCESS; use crate::vec; +use crate::BOARD_ACCESS; use alloc::string::ToString; use alloc::vec::Vec; use bytemuck::{AnyBitPattern, Pod, Zeroable}; use deranged::RangedU8; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; use embassy_sync::mutex::Mutex; use esp_hal::Persistable; -use log::info; +use log::{info, warn}; use serde::Serialize; use strum_macros::IntoStaticStr; use unit_enum::UnitEnum; const LOG_ARRAY_SIZE: u8 = 220; const MAX_LOG_ARRAY_INDEX: u8 = LOG_ARRAY_SIZE - 1; -#[esp_hal::ram(rtc_fast, persistent)] +#[esp_hal::ram(unstable(rtc_fast), unstable(persistent))] static mut LOG_ARRAY: LogArray = LogArray { buffer: [LogEntryInner { timestamp: 0, @@ -26,8 +27,45 @@ static mut LOG_ARRAY: LogArray = LogArray { }; LOG_ARRAY_SIZE as usize], head: 0, }; + +// this is the only reference created for LOG_ARRAY and the only way to access it +#[allow(static_mut_refs)] pub static LOG_ACCESS: Mutex = - unsafe { Mutex::new(&mut *&raw mut LOG_ARRAY) }; + unsafe { Mutex::new(&mut LOG_ARRAY) }; + +mod interceptor; + +pub use interceptor::InterceptorLogger; + +pub static INTERCEPTOR: InterceptorLogger = InterceptorLogger::new(); + +pub struct LogRequest { + pub message_key: LogMessage, + pub number_a: u32, + pub number_b: u32, + pub txt_short: heapless::String, + pub txt_long: heapless::String, +} + +static LOG_CHANNEL: Channel = Channel::new(); + +#[embassy_executor::task] +pub async fn log_task() { + loop { + let request = LOG_CHANNEL.receive().await; + LOG_ACCESS + .lock() + .await + .log( + request.message_key, + request.number_a, + request.number_b, + request.txt_short.as_str(), + request.txt_long.as_str(), + ) + .await; + } +} const TXT_SHORT_LENGTH: usize = 8; const TXT_LONG_LENGTH: usize = 32; @@ -77,10 +115,31 @@ impl From for LogEntry { } } +pub fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_short: &str, txt_long: &str) { + let mut txt_short_stack: heapless::String = heapless::String::new(); + let mut txt_long_stack: heapless::String = heapless::String::new(); + + limit_length(txt_short, &mut txt_short_stack); + limit_length(txt_long, &mut txt_long_stack); + + match LOG_CHANNEL.try_send(LogRequest { + message_key, + number_a, + number_b, + txt_short: txt_short_stack, + txt_long: txt_long_stack, + }) { + Ok(_) => {} + Err(_) => { + warn!("Log channel full, dropping log entry"); + } + } +} + impl LogArray { pub fn get(&mut self) -> Vec { let head: RangedU8<0, MAX_LOG_ARRAY_INDEX> = - RangedU8::new(self.head).unwrap_or(RangedU8::new(0).unwrap()); + RangedU8::new(self.head).unwrap_or(RangedU8::new_saturating(0)); let mut rv: Vec = Vec::new(); let mut index = head.wrapping_sub(1); @@ -103,17 +162,11 @@ impl LogArray { txt_long: &str, ) { let mut head: RangedU8<0, MAX_LOG_ARRAY_INDEX> = - RangedU8::new(self.head).unwrap_or(RangedU8::new(0).unwrap()); - - let mut txt_short_stack: heapless::String = heapless::String::new(); - let mut txt_long_stack: heapless::String = heapless::String::new(); - - limit_length(txt_short, &mut txt_short_stack); - limit_length(txt_long, &mut txt_long_stack); + RangedU8::new(self.head).unwrap_or(RangedU8::new_saturating(0)); let time = { - let guard = TIME_ACCESS.get().await.lock().await; - guard.current_time_us() + let mut guard = BOARD_ACCESS.get().await.lock().await; + guard.board_hal.get_esp().rtc.current_time_us() } / 1000; let ordinal = message_key.ordinal() as u16; @@ -124,19 +177,15 @@ impl LogArray { template_string = template_string.replace("${txt_long}", txt_long); template_string = template_string.replace("${txt_short}", txt_short); - info!("{}", template_string); + info!("{template_string}"); let to_modify = &mut self.buffer[head.get() as usize]; to_modify.timestamp = time; to_modify.message_id = ordinal; to_modify.a = number_a; to_modify.b = number_b; - to_modify - .txt_short - .clone_from_slice(&txt_short_stack.as_bytes()); - to_modify - .txt_long - .clone_from_slice(&txt_long_stack.as_bytes()); + to_modify.txt_short.clone_from_slice(txt_short.as_bytes()); + to_modify.txt_long.clone_from_slice(txt_long.as_bytes()); head = head.wrapping_add(1); self.head = head.get(); } @@ -148,18 +197,37 @@ fn limit_length(input: &str, target: &mut heapless::String {} //continue adding chars Err(_) => { //clear space for two asci chars + info!("pushing char {char} to limit {LIMIT} current value {target} input {input}"); while target.len() + 2 >= LIMIT { - target.pop().unwrap(); + target.pop(); } //add .. to shortened strings - target.push('.').unwrap(); - target.push('.').unwrap(); - return; + match target.push('.') { + Ok(_) => {} + Err(_) => { + warn!( + "Error pushin . to limit {LIMIT} current value {target} input {input}" + ) + } + } + match target.push('.') { + Ok(_) => {} + Err(_) => { + warn!( + "Error pushin . to limit {LIMIT} current value {target} input {input}" + ) + } + } } } } while target.len() < LIMIT { - target.push(' ').unwrap(); + match target.push(' ') { + Ok(_) => {} + Err(_) => { + warn!("Error pushing space to limit {LIMIT} current value {target} input {input}") + } + } } } @@ -243,6 +311,20 @@ pub enum LogMessage { PumpOpenLoopCurrent, #[strum(serialize = "Pump Open current sensor required but did not work: ${number_a}")] PumpMissingSensorCurrent, + #[strum( + serialize = "Fertilizer applied for ${number_a}s on plant ${number_b} (last application ${txt_short} minutes ago)" + )] + FertilizerApplied, + #[strum(serialize = "MPPT Current sensor could not be reached")] + MPPTError, + #[strum( + serialize = "Trace: a: ${number_a} b: ${number_b} txt_s ${txt_short} long ${txt_long}" + )] + Trace, + #[strum(serialize = "Parsing error reading message")] + UnknownMessage, + #[strum(serialize = "Going to deep sleep for ${number_a} minutes")] + DeepSleep, } #[derive(Serialize)] @@ -261,9 +343,9 @@ impl From<&LogMessage> for MessageTranslation { } impl LogMessage { - pub fn to_log_localisation_config() -> Vec { + pub fn log_localisation_config() -> Vec { Vec::from_iter((0..LogMessage::len()).map(|i| { - let msg_type = LogMessage::from_ordinal(i).unwrap(); + let msg_type = LogMessage::from_ordinal(i).unwrap_or(LogMessage::UnknownMessage); (&msg_type).into() })) } diff --git a/rust/src/main.rs b/rust/src/main.rs index af62b8a..78bf202 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -11,20 +11,22 @@ //TODO insert version here and read it in other parts, also read this for the ota webview esp_bootloader_esp_idf::esp_app_desc!(); +use alloc::vec::Vec; +use config::PlantControllerConfig; use esp_backtrace as _; +use hal::PROGRESS_ACTIVE; use crate::config::{NetworkConfig, PlantConfig}; use crate::fat_error::FatResult; use crate::hal::esp::MQTT_STAY_ALIVE; -use crate::hal::{esp_time, TIME_ACCESS}; -use crate::log::LOG_ACCESS; +use crate::log::log; use crate::tank::{determine_tank_state, TankError, TankState, WATER_FROZEN_THRESH}; use crate::webserver::http_server; use crate::{ config::BoardVersion::INITIAL, hal::{PlantHal, HAL, PLANT_COUNT}, }; -use ::log::{info, warn}; +use ::log::{info, warn, error}; use alloc::borrow::ToOwned; use alloc::string::{String, ToString}; use alloc::sync::Arc; @@ -37,7 +39,7 @@ use embassy_net::Stack; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; use embassy_sync::mutex::{Mutex, MutexGuard}; use embassy_sync::once_lock::OnceLock; -use embassy_time::Timer; +use embassy_time::{Duration, Instant, Timer}; use esp_hal::rom::ets_delay_us; use esp_hal::system::software_reset; use esp_println::{logger, println}; @@ -165,35 +167,28 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { let cur = match board.board_hal.get_rtc_module().get_rtc_time().await { Ok(value) => { { - let guard = TIME_ACCESS.get().await.lock().await; - guard.set_current_time_us(value.timestamp_micros() as u64); + board.board_hal.set_time(&value.fixed_offset()).await; } value } Err(err) => { info!("rtc module error: {:?}", err); board.board_hal.general_fault(true).await; - esp_time().await + board.board_hal.get_time().await } }; + info!("Step 1"); + //check if we know the time current > 2020 (plausibility checks, this code is newer than 2020) if cur.year() < 2020 { to_config = true; - LOG_ACCESS - .lock() - .await - .log(LogMessage::YearInplausibleForceConfig, 0, 0, "", "") - .await; + log(LogMessage::YearInplausibleForceConfig, 0, 0, "", ""); } info!("cur is {}", cur); update_charge_indicator(&mut board).await; if board.board_hal.get_esp().get_restart_to_conf() { - LOG_ACCESS - .lock() - .await - .log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", "") - .await; + log(LogMessage::ConfigModeSoftwareOverride, 0, 0, "", ""); for _i in 0..2 { board.board_hal.general_fault(true).await; Timer::after_millis(100).await; @@ -205,11 +200,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { board.board_hal.get_esp().set_restart_to_conf(false); } else if board.board_hal.get_esp().mode_override_pressed() { board.board_hal.general_fault(true).await; - LOG_ACCESS - .lock() - .await - .log(LogMessage::ConfigModeButtonOverride, 0, 0, "", "") - .await; + log(LogMessage::ConfigModeButtonOverride, 0, 0, "", ""); for _i in 0..5 { board.board_hal.general_fault(true).await; Timer::after_millis(100).await; @@ -232,18 +223,18 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { { info!("No wifi configured, starting initial config mode"); - let stack = board.board_hal.get_esp().wifi_ap().await?; + let stack = board.board_hal.get_esp().wifi_ap(spawner).await?; let reboot_now = Arc::new(AtomicBool::new(false)); println!("starting webserver"); - spawner.spawn(http_server(reboot_now.clone(), stack))?; - wait_infinity(board, WaitType::MissingConfig, reboot_now.clone()).await; + spawner.spawn(http_server(reboot_now.clone(), stack)?); + wait_infinity(board, WaitType::MissingConfig, reboot_now.clone(), UTC).await; } let mut stack: OptionLock = OptionLock::empty(); let network_mode = if board.board_hal.get_config().network.ssid.is_some() { - try_connect_wifi_sntp_mqtt(&mut board, &mut stack).await + try_connect_wifi_sntp_mqtt(&mut board, &mut stack, spawner).await } else { info!("No wifi configured"); //the current sensors require this amount to stabilize, in the case of Wi-Fi this is already handled due to connect timings; @@ -256,7 +247,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { let res = { let esp = board.board_hal.get_esp(); - esp.wifi_ap().await + esp.wifi_ap(spawner).await }; match res { Ok(ap_stack) => { @@ -291,39 +282,36 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { let _ = publish_mppt_state(&mut board).await; } - LOG_ACCESS - .lock() - .await - .log( - LogMessage::StartupInfo, - matches!(network_mode, NetworkMode::WIFI { .. }) as u32, - matches!( - network_mode, - NetworkMode::WIFI { - sntp: SntpMode::SYNC { .. }, - .. - } - ) as u32, - matches!(network_mode, NetworkMode::WIFI { mqtt: true, .. }) - .to_string() - .as_str(), - "", - ) - .await; + log( + LogMessage::StartupInfo, + matches!(network_mode, NetworkMode::WIFI { .. }) as u32, + matches!( + network_mode, + NetworkMode::WIFI { + sntp: SntpMode::SYNC { .. }, + .. + } + ) as u32, + matches!(network_mode, NetworkMode::WIFI { mqtt: true, .. }) + .to_string() + .as_str(), + "", + ); if to_config { //check if client or ap mode and init Wi-Fi info!("executing config mode override"); //config upload will trigger reboot! let reboot_now = Arc::new(AtomicBool::new(false)); - spawner.spawn(http_server(reboot_now.clone(), stack.take().unwrap()))?; - wait_infinity(board, WaitType::ConfigButton, reboot_now.clone()).await; + let stack_val = stack.take(); + if let Some(s) = stack_val { + spawner.spawn(http_server(reboot_now.clone(), s)?); + } else { + bail!("Network stack missing, hard abort") + } + wait_infinity(board, WaitType::ConfigButton, reboot_now.clone(), UTC).await; } else { - LOG_ACCESS - .lock() - .await - .log(LogMessage::NormalRun, 0, 0, "", "") - .await; + log(LogMessage::NormalRun, 0, 0, "", ""); } let _dry_run = false; @@ -334,38 +322,22 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { if let Some(err) = tank_state.got_error(&board.board_hal.get_config().tank) { match err { TankError::SensorDisabled => { /* unreachable */ } - TankError::SensorMissing(raw_value_mv) => { - LOG_ACCESS - .lock() - .await - .log( - LogMessage::TankSensorMissing, - raw_value_mv as u32, - 0, - "", - "", - ) - .await - } - TankError::SensorValueError { value, min, max } => { - LOG_ACCESS - .lock() - .await - .log( - LogMessage::TankSensorValueRangeError, - min as u32, - max as u32, - &format!("{}", value), - "", - ) - .await - } + TankError::SensorMissing(raw_value_mv) => log( + LogMessage::TankSensorMissing, + raw_value_mv as u32, + 0, + "", + "", + ), + TankError::SensorValueError { value, min, max } => log( + LogMessage::TankSensorValueRangeError, + min as u32, + max as u32, + &format!("{value}"), + "", + ), TankError::BoardError(err) => { - LOG_ACCESS - .lock() - .await - .log(LogMessage::TankSensorBoardError, 0, 0, "", &err.to_string()) - .await + log(LogMessage::TankSensorBoardError, 0, 0, "", &err.to_string()) } } // disabled cannot trigger this because of wrapping if is_enabled @@ -374,11 +346,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { .warn_level(&board.board_hal.get_config().tank) .is_ok_and(|warn| warn) { - LOG_ACCESS - .lock() - .await - .log(LogMessage::TankWaterLevelLow, 0, 0, "", "") - .await; + log(LogMessage::TankWaterLevelLow, 0, 0, "", ""); board.board_hal.general_fault(true).await; } } @@ -620,7 +588,7 @@ async fn safe_main(spawner: Spawner) -> FatResult<()> { if stay_alive { let reboot_now = Arc::new(AtomicBool::new(false)); let _webserver = http_server(reboot_now.clone(), stack.take().unwrap()); - wait_infinity(board, WaitType::MqttConfig, reboot_now.clone()).await; + wait_infinity(board, WaitType::MqttConfig, reboot_now.clone(), UTC).await; } else { //TODO wait for all mqtt publishes? Timer::after_millis(5000).await; @@ -671,17 +639,13 @@ pub async fn do_secure_pump( let high_current = current_ma > plant_config.max_pump_current_ma; if high_current { if first_error { - LOG_ACCESS - .lock() - .await - .log( - LogMessage::PumpOverCurrent, - plant_id as u32 + 1, - current_ma as u32, - plant_config.max_pump_current_ma.to_string().as_str(), - step.to_string().as_str(), - ) - .await; + log( + LogMessage::PumpOverCurrent, + plant_id as u32 + 1, + current_ma as u32, + plant_config.max_pump_current_ma.to_string().as_str(), + step.to_string().as_str(), + ); board.board_hal.general_fault(true).await; board.board_hal.fault(plant_id, true).await?; if !plant_config.ignore_current_error { @@ -694,17 +658,13 @@ pub async fn do_secure_pump( let low_current = current_ma < plant_config.min_pump_current_ma; if low_current { if first_error { - LOG_ACCESS - .lock() - .await - .log( - LogMessage::PumpOpenLoopCurrent, - plant_id as u32 + 1, - current_ma as u32, - plant_config.min_pump_current_ma.to_string().as_str(), - step.to_string().as_str(), - ) - .await; + log( + LogMessage::PumpOpenLoopCurrent, + plant_id as u32 + 1, + current_ma as u32, + plant_config.min_pump_current_ma.to_string().as_str(), + step.to_string().as_str(), + ); board.board_hal.general_fault(true).await; board.board_hal.fault(plant_id, true).await?; if !plant_config.ignore_current_error { @@ -718,17 +678,13 @@ pub async fn do_secure_pump( Err(err) => { if !plant_config.ignore_current_error { info!("Error getting pump current: {}", err); - LOG_ACCESS - .lock() - .await - .log( - LogMessage::PumpMissingSensorCurrent, - plant_id as u32, - 0, - "", - "", - ) - .await; + log( + LogMessage::PumpMissingSensorCurrent, + plant_id as u32, + 0, + "", + "", + ); error = true; break; } else { @@ -761,25 +717,15 @@ pub async fn do_secure_pump( async fn update_charge_indicator( board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>, -) { +) -> FatResult<()> { + //FIXME add config and code to allow power supply mode, in this case this is a nop //we have mppt controller, ask it for charging current - if let Ok(current) = board.board_hal.get_mptt_current().await { - let _ = board - .board_hal - .set_charge_indicator(current.as_milliamperes() > 20_f64); - } - //fallback to battery controller and ask it instead - else if let Ok(charging) = board + let current = board.board_hal.get_mptt_current().await?; + board .board_hal - .get_battery_monitor() - .average_current_milli_ampere() - .await - { - let _ = board.board_hal.set_charge_indicator(charging > 20); - } else { - //who knows - let _ = board.board_hal.set_charge_indicator(false); - } + .set_charge_indicator(current.as_milliamperes() > 20_f64) + .await?; + Ok(()) } async fn publish_tank_state( @@ -818,25 +764,16 @@ async fn publish_plant_states( async fn publish_firmware_info( board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, version: VersionInfo, - ip_address: &String, - timezone_time: &String, + ip_address: &str, + timezone_time: &str, ) { let esp = board.board_hal.get_esp(); - let _ = esp.mqtt_publish("/firmware/address", ip_address).await; - let _ = esp - .mqtt_publish("/firmware/githash", &version.git_hash) + esp.mqtt_publish("/firmware/address", ip_address).await; + esp.mqtt_publish("/firmware/state", format!("{:?}", &version).as_str()) .await; - let _ = esp - .mqtt_publish("/firmware/buildtime", &version.build_time) + esp.mqtt_publish("/firmware/last_online", timezone_time) .await; - let _ = esp.mqtt_publish("/firmware/last_online", timezone_time); - let state = esp.get_ota_state(); - let _ = esp.mqtt_publish("/firmware/ota_state", &state).await; - let slot = esp.get_ota_slot(); - let _ = esp - .mqtt_publish("/firmware/ota_slot", &format!("slot{slot}")) - .await; - let _ = esp.mqtt_publish("/state", "online").await; + esp.mqtt_publish("/state", "online").await; } macro_rules! mk_static { ($t:ty,$val:expr) => {{ @@ -849,25 +786,25 @@ macro_rules! mk_static { async fn try_connect_wifi_sntp_mqtt( board: &mut MutexGuard<'static, CriticalSectionRawMutex, HAL<'static>>, stack_store: &mut OptionLock>, + spawner: Spawner, ) -> NetworkMode { let nw_conf = &board.board_hal.get_config().network.clone(); - match board.board_hal.get_esp().wifi(nw_conf).await { + match board.board_hal.get_esp().wifi(nw_conf, spawner).await { Ok(stack) => { stack_store.replace(stack); - let sntp_mode: SntpMode = match board - .board_hal - .get_esp() - .sntp(1000 * 10, stack.clone()) - .await - { + let sntp_mode: SntpMode = match board.board_hal.get_esp().sntp(1000 * 10, stack).await { Ok(new_time) => { info!("Using time from sntp {}", new_time.to_rfc3339()); - let _ = board.board_hal.get_rtc_module().set_rtc_time(&new_time); + let _ = board + .board_hal + .get_rtc_module() + .set_rtc_time(&new_time) + .await; SntpMode::SYNC { current: new_time } } Err(err) => { - warn!("sntp error: {}", err); + warn!("sntp error: {err}"); board.board_hal.general_fault(true).await; SntpMode::OFFLINE } @@ -876,27 +813,40 @@ async fn try_connect_wifi_sntp_mqtt( let mqtt_connected = if board.board_hal.get_config().network.mqtt_url.is_some() { let nw_config = board.board_hal.get_config().network.clone(); let nw_config = mk_static!(NetworkConfig, nw_config); - match board.board_hal.get_esp().mqtt(nw_config, stack).await { + match board + .board_hal + .get_esp() + .mqtt(nw_config, stack, spawner) + .await + { Ok(_) => { info!("Mqtt connection ready"); true } Err(err) => { - warn!("Could not connect mqtt due to {}", err); + warn!("Could not connect mqtt due to {err}"); false } } } else { false }; + + let ip = match stack.config_v4() { + Some(config) => config.address.address().to_string(), + None => match stack.config_v6() { + Some(config) => config.address.address().to_string(), + None => String::from("No IP"), + }, + }; NetworkMode::WIFI { sntp: sntp_mode, mqtt: mqtt_connected, - ip_address: stack.hardware_address().to_string(), + ip_address: ip, } } Err(err) => { - info!("Offline mode due to {}", err); + info!("Offline mode due to {err}"); board.board_hal.general_fault(true).await; NetworkMode::OFFLINE } @@ -985,6 +935,7 @@ async fn wait_infinity( board: MutexGuard<'_, CriticalSectionRawMutex, HAL<'static>>, wait_type: WaitType, reboot_now: Arc, + timezone: Tz, ) -> ! { //since we force to have the lock when entering, we can release it to ensure the caller does not forget to dispose of it drop(board); @@ -992,55 +943,155 @@ async fn wait_infinity( let delay = wait_type.blink_pattern(); let mut led_count = 8; let mut pattern_step = 0; + let serial_config_receive = AtomicBool::new(false); + let mut suppress_further_mppt_error = false; + let mut last_mqtt_update: Option = None; + + // Long-press exit (for webserver config modes): hold boot button for 5 seconds. + let mut exit_hold_started: Option = None; + let exit_hold_duration = Duration::from_secs(5); + let mut exit_hold_blink = false; loop { + // While in config webserver mode, allow exiting via long-press. + if matches!(wait_type, WaitType::MissingConfig | WaitType::ConfigButton) { + let mut board = BOARD_ACCESS.get().await.lock().await; + let pressed = board.board_hal.get_esp().mode_override_pressed(); + + match (pressed, exit_hold_started) { + (true, None) => { + exit_hold_started = Some(Instant::now()); + PROGRESS_ACTIVE.store(true, Ordering::Relaxed); + } + (false, Some(_)) => { + exit_hold_started = None; + // Clear any interim hold display. + board.board_hal.clear_progress().await; + } + _ => {} + } + + if let Some(started) = exit_hold_started { + let elapsed = Instant::now() - started; + // Visible countdown: fill LEDs progressively during the hold. + // Also toggle general fault LED to match the "enter config" visibility. + exit_hold_blink = !exit_hold_blink; + + let progress = core::cmp::min(elapsed, exit_hold_duration); + let lit = ((progress.as_millis() * 8) / exit_hold_duration.as_millis()) + .saturating_add(1) + .min(8) as usize; + + for i in 0..8 { + let _ = board.board_hal.fault(i, i < lit).await; + } + board.board_hal.general_fault(exit_hold_blink).await; + + if elapsed >= exit_hold_duration { + info!("Exiting config mode due to 5s button hold"); + board.board_hal.get_esp().set_restart_to_conf(false); + // ensure clean http answer / visible confirmation + Timer::after_millis(500).await; + board.board_hal.deep_sleep(0).await; + } + + // Short tick while holding so the pattern updates smoothly. + drop(board); + Timer::after_millis(100).await; + continue; + } + // Release lock and continue with normal wait blinking. + drop(board); + } + { let mut board = BOARD_ACCESS.get().await.lock().await; - update_charge_indicator(&mut board).await; + match update_charge_indicator(&mut board).await { + Ok(_) => {} + Err(error) => { + if !suppress_further_mppt_error { + error!("Error updating charge indicator: {error}"); + suppress_further_mppt_error = true; + } + } + }; - match wait_type { - WaitType::MissingConfig => { - // Keep existing behavior: circular filling pattern - led_count %= 8; - led_count += 1; - for i in 0..8 { - let _ = board.board_hal.fault(i, i < led_count); - } - } - WaitType::ConfigButton => { - // Alternating pattern: 1010 1010 -> 0101 0101 - pattern_step = (pattern_step + 1) % 2; - for i in 0..8 { - let _ = board.board_hal.fault(i, (i + pattern_step) % 2 == 0); - } - } - WaitType::MqttConfig => { - // Moving dot pattern - pattern_step = (pattern_step + 1) % 8; - for i in 0..8 { - let _ = board.board_hal.fault(i, i == pattern_step); - } + match handle_serial_config(&mut board, &serial_config_receive, &reboot_now).await { + Ok(_) => {} + Err(e) => { + error!("Error handling serial config: {e}"); } } - board.board_hal.general_fault(true).await; + + // MQTT updates in config mode + let now = Instant::now(); + if last_mqtt_update.is_none() + || now.duration_since(last_mqtt_update.unwrap_or(Instant::from_secs(0))) + >= Duration::from_secs(60) + { + let cur = board.board_hal.get_time().await; + let timezone_time = cur.with_timezone(&timezone); + + let esp = board.board_hal.get_esp(); + esp.mqtt_publish("/state", "config").await; + esp.mqtt_publish("/firmware/last_online", &timezone_time.to_rfc3339()) + .await; + last_mqtt_update = Some(now); + } + + // Skip default blink code when a progress display is active + if !PROGRESS_ACTIVE.load(Ordering::Relaxed) { + match wait_type { + WaitType::MissingConfig => { + // Keep existing behavior: circular filling pattern + led_count %= 8; + led_count += 1; + for i in 0..8 { + let _ = board.board_hal.fault(i, i < led_count).await; + } + } + WaitType::ConfigButton => { + // Alternating pattern: 1010 1010 -> 0101 0101 + pattern_step = (pattern_step + 1) % 2; + for i in 0..8 { + let _ = board.board_hal.fault(i, (i + pattern_step) % 2 == 0).await; + } + } + WaitType::MqttConfig => { + // Moving dot pattern + pattern_step = (pattern_step + 1) % 8; + for i in 0..8 { + let _ = board.board_hal.fault(i, i == pattern_step).await; + } + } + } + board.board_hal.general_fault(true).await; + } } Timer::after_millis(delay).await; { let mut board = BOARD_ACCESS.get().await.lock().await; - board.board_hal.general_fault(false).await; - // Clear all LEDs - for i in 0..8 { - let _ = board.board_hal.fault(i, false); + // Skip clearing LEDs when progress is active to avoid interrupting the progress display + if !PROGRESS_ACTIVE.load(Ordering::Relaxed) { + board.board_hal.general_fault(false).await; + + // Clear all LEDs + for i in 0..8 { + let _ = board.board_hal.fault(i, false).await; + } } } Timer::after_millis(delay).await; + hal::PlantHal::feed_watchdog(); + if wait_type == WaitType::MqttConfig && !MQTT_STAY_ALIVE.load(Ordering::Relaxed) { reboot_now.store(true, Ordering::Relaxed); } if reboot_now.load(Ordering::Relaxed) { + info!("Rebooting now"); //ensure clean http answer Timer::after_millis(500).await; BOARD_ACCESS @@ -1055,7 +1106,47 @@ async fn wait_infinity( } } -#[esp_hal_embassy::main] +async fn handle_serial_config( + board: &mut MutexGuard<'_, CriticalSectionRawMutex, HAL<'_>>, + serial_config_receive: &AtomicBool, + reboot_now: &AtomicBool, +) -> FatResult<()> { + match board.board_hal.get_esp().read_serial_line().await { + Ok(serial_line) => match serial_line { + None => Ok(()), + Some(line) => { + if serial_config_receive.load(Ordering::Relaxed) { + let ll = line.as_str(); + let config: PlantControllerConfig = serde_json::from_str(ll)?; + board + .board_hal + .get_esp() + .save_config(Vec::from(ll.as_bytes())) + .await?; + board.board_hal.set_config(config); + serial_config_receive.store(false, Ordering::Relaxed); + info!("Config received, rebooting"); + board.board_hal.get_esp().set_restart_to_conf(false); + reboot_now.store(true, Ordering::Relaxed); + Ok(()) + } else { + if line == "automation:streamconfig" { + serial_config_receive.store(true, Ordering::Relaxed); + info!("streamconfig:recieving"); + } + Ok(()) + } + } + }, + Err(_) => { + error!("Error reading serial line"); + Ok(()) + } + } +} + +use embassy_time::WithTimeout; +#[esp_rtos::main] async fn main(spawner: Spawner) -> ! { // intialize embassy logger::init_logger_from_env(); @@ -1099,11 +1190,17 @@ async fn get_version( let hash = &env!("VERGEN_GIT_SHA")[0..8]; let board = board.board_hal.get_esp(); - let ota_slot = board.get_ota_slot(); + let heap = esp_alloc::HEAP.stats(); VersionInfo { git_hash: branch + "@" + hash, build_time: env!("VERGEN_BUILD_TIMESTAMP").to_owned(), - partition: ota_slot, + current: format!("{:?}", board.current), + slot0_state: format!("{:?}", board.slot0_state), + slot1_state: format!("{:?}", board.slot1_state), + heap_total: heap.size, + heap_used: heap.current_usage, + heap_free: heap.size.saturating_sub(heap.current_usage), + heap_max_used: heap.max_usage, } } @@ -1111,5 +1208,11 @@ async fn get_version( struct VersionInfo { git_hash: String, build_time: String, - partition: String, + current: String, + slot0_state: String, + slot1_state: String, + heap_total: usize, + heap_used: usize, + heap_free: usize, + heap_max_used: usize, } diff --git a/rust/src/mcutie_3_0_0/Cargo.toml b/rust/src/mcutie_3_0_0/Cargo.toml index 9492629..eb48eb1 100644 --- a/rust/src/mcutie_3_0_0/Cargo.toml +++ b/rust/src/mcutie_3_0_0/Cargo.toml @@ -14,7 +14,7 @@ defmt = [] log = ["dep:log"] [dependencies] -embassy-net = { version = "0.7.1", default-features = false, features = ["tcp", "dns", "proto-ipv4", "proto-ipv6", "medium-ethernet"] } +embassy-net = { version = "0.8.0", default-features = false, features = ["tcp", "dns", "proto-ipv4", "proto-ipv6", "medium-ethernet"] } embassy-sync = { version = "0.8.0", default-features = false } embassy-time = { version = "0.5.1", default-features = false } embassy-futures = { version = "0.1.2", default-features = false } diff --git a/rust/src/webserver/backup_manager.rs b/rust/src/webserver/backup_manager.rs index ddbb5e4..304af06 100644 --- a/rust/src/webserver/backup_manager.rs +++ b/rust/src/webserver/backup_manager.rs @@ -6,7 +6,7 @@ use alloc::format; use alloc::string::{String, ToString}; use chrono::DateTime; use edge_http::io::server::Connection; -use embedded_io_async::{Read, Write}; +use edge_nal::io::{Read, Write}; use log::info; use serde::{Deserialize, Serialize}; diff --git a/rust/src/webserver/file_manager.rs b/rust/src/webserver/file_manager.rs index 6aa0cf8..3ac3252 100644 --- a/rust/src/webserver/file_manager.rs +++ b/rust/src/webserver/file_manager.rs @@ -5,7 +5,7 @@ use alloc::format; use alloc::string::String; use edge_http::io::server::Connection; use edge_http::Method; -use embedded_io_async::{Read, Write}; +use edge_nal::io::{Read, Write}; use log::info; pub(crate) async fn list_files( diff --git a/rust/src/webserver/get_json.rs b/rust/src/webserver/get_json.rs index cf8e4c7..c29de77 100644 --- a/rust/src/webserver/get_json.rs +++ b/rust/src/webserver/get_json.rs @@ -1,5 +1,5 @@ use crate::fat_error::{FatError, FatResult}; -use crate::hal::{esp_time, PLANT_COUNT}; +use crate::hal::PLANT_COUNT; use crate::log::LogMessage; use crate::plant_state::{MoistureSensorState, PlantState}; use crate::tank::determine_tank_state; @@ -10,7 +10,8 @@ use alloc::vec::Vec; use chrono_tz::Tz; use core::str::FromStr; use edge_http::io::server::Connection; -use embedded_io_async::{Read, Write}; +use edge_nal::io::{Read, Write}; +use log::info; use serde::Serialize; #[derive(Serialize, Debug)] @@ -139,13 +140,29 @@ pub(crate) async fn get_time( ) -> FatResult> { let mut board = BOARD_ACCESS.get().await.lock().await; let conf = board.board_hal.get_config(); - let tz = Tz::from_str(conf.timezone.as_ref().unwrap_or(&"Europe/Berlin".to_string()).as_str()).unwrap(); - let native = esp_time().await.with_timezone(&tz).to_rfc3339(); + + let tz: Tz = match conf.timezone.as_ref() { + None => Tz::UTC, + Some(tz_string) => match Tz::from_str(tz_string) { + Ok(tz) => tz, + Err(err) => { + info!("failed parsing timezone {err}"); + Tz::UTC + } + }, + }; + + let native = board + .board_hal + .get_time() + .await + .with_timezone(&tz) + .to_rfc3339(); let rtc = match board.board_hal.get_rtc_module().get_rtc_time().await { Ok(time) => time.with_timezone(&tz).to_rfc3339(), Err(err) => { - format!("Error getting time: {}", err) + format!("Error getting time: {err}") } }; @@ -162,6 +179,6 @@ pub(crate) async fn get_log_localization_config( _request: &mut Connection<'_, T, N>, ) -> FatResult> { Ok(Some(serde_json::to_string( - &LogMessage::to_log_localisation_config(), + &LogMessage::log_localisation_config(), )?)) } diff --git a/rust/src/webserver/get_log.rs b/rust/src/webserver/get_log.rs index 4cc5331..0f403a0 100644 --- a/rust/src/webserver/get_log.rs +++ b/rust/src/webserver/get_log.rs @@ -1,7 +1,7 @@ use crate::fat_error::FatResult; use crate::log::LOG_ACCESS; use edge_http::io::server::Connection; -use embedded_io_async::{Read, Write}; +use edge_nal::io::{Read, Write}; pub(crate) async fn get_log( conn: &mut Connection<'_, T, N>, diff --git a/rust/src/webserver/get_static.rs b/rust/src/webserver/get_static.rs index 26b1947..1db5be8 100644 --- a/rust/src/webserver/get_static.rs +++ b/rust/src/webserver/get_static.rs @@ -1,6 +1,6 @@ use crate::fat_error::FatError; use edge_http::io::server::Connection; -use embedded_io_async::{Read, Write}; +use edge_nal::io::{Read, Write}; pub(crate) async fn serve_favicon( conn: &mut Connection<'_, T, { N }>, diff --git a/rust/src/webserver/mod.rs b/rust/src/webserver/mod.rs index 1dee0a8..8133de3 100644 --- a/rust/src/webserver/mod.rs +++ b/rust/src/webserver/mod.rs @@ -31,10 +31,10 @@ use core::sync::atomic::{AtomicBool, Ordering}; use edge_http::io::server::{Connection, Handler, Server}; use edge_http::Method; use edge_nal::TcpBind; +use edge_nal::io::{Read, Write}; use edge_nal_embassy::{Tcp, TcpBuffers}; use embassy_net::Stack; use embassy_time::Instant; -use embedded_io_async::{Read, Write}; use log::info; // fn ota( @@ -228,34 +228,6 @@ pub async fn http_server(reboot_now: Arc, stack: Stack<'static>) { info!("Webserver started and waiting for connections"); //TODO https if mbed_esp lands - - // server - // .fn_handler("/ota", Method::Post, |request| { - // handle_error_to500(request, ota) - // }) - // .unwrap(); - // server - // .fn_handler("/ota", Method::Options, |request| { - // cors_response(request, 200, "") - // }) - // .unwrap(); - // let reboot_now_for_reboot = reboot_now.clone(); - // server - // .fn_handler("/reboot", Method::Post, move |_| { - // BOARD_ACCESS - // .lock() - // .unwrap() - // .board_hal - // .get_esp() - // .set_restart_to_conf(true); - // reboot_now_for_reboot.store(true, std::sync::atomic::Ordering::Relaxed); - // anyhow::Ok(()) - // }) - // .unwrap(); - // - // unsafe { vTaskDelay(1) }; - // - // server } async fn handle_json<'a, T, const N: usize>( @@ -264,7 +236,7 @@ async fn handle_json<'a, T, const N: usize>( ) -> FatResult where T: Read + Write, - ::Error: Debug, + ::Error: Debug, { match chain { Ok(answer) => match answer { diff --git a/rust/src/webserver/post_json.rs b/rust/src/webserver/post_json.rs index 7f041d5..0a9e75d 100644 --- a/rust/src/webserver/post_json.rs +++ b/rust/src/webserver/post_json.rs @@ -1,13 +1,14 @@ use crate::config::PlantControllerConfig; use crate::fat_error::FatResult; -use crate::hal::esp_set_time; use crate::webserver::read_up_to_bytes_from_request; use crate::{do_secure_pump, BOARD_ACCESS}; +use alloc::borrow::ToOwned; use alloc::string::{String, ToString}; use alloc::vec::Vec; use chrono::DateTime; use edge_http::io::server::Connection; -use embedded_io_async::{Read, Write}; +use edge_nal::io::{Read, Write}; +use esp_radio::wifi::ap::AccessPointInfo; use log::info; use serde::{Deserialize, Serialize}; @@ -35,10 +36,10 @@ pub(crate) async fn wifi_scan( let mut board = BOARD_ACCESS.get().await.lock().await; info!("start wifi scan"); let mut ssids: Vec = Vec::new(); - let scan_result = board.board_hal.get_esp().wifi_scan().await?; + let scan_result: Vec = board.board_hal.get_esp().wifi_scan().await?; scan_result .iter() - .for_each(|s| ssids.push(s.ssid.to_string())); + .for_each(|s| ssids.push(s.ssid.as_str().to_owned())); let ssid_json = serde_json::to_string(&SSIDList { ssids })?; info!("Sending ssid list {}", &ssid_json); Ok(Some(ssid_json)) @@ -89,8 +90,9 @@ where { let actual_data = read_up_to_bytes_from_request(request, None).await?; let time: SetTime = serde_json::from_slice(&actual_data)?; - let parsed = DateTime::parse_from_rfc3339(time.time).unwrap(); - esp_set_time(parsed).await?; + let parsed = DateTime::parse_from_rfc3339(time.time)?; + let mut board = BOARD_ACCESS.get().await.lock().await; + board.board_hal.set_time(&parsed).await?; Ok(None) }