fix(host/uart): deliver RX to host and survive sustained bidirectional load

Two regressions on UART boards (heltec_v3_uart, etc.):

1. RADIO_THROTTLED was never cleared on UART. The throttle gate at
   src/radio.rs:2017 (added in 276f941, "chart-first restructure ...
   soak hardening") silently drops async RX while the flag is true.
   USB host_task clears it on first successful write_packet
   (src/host/usb.rs:354/373), but the UART path was never updated --
   every RX event was dropped from boot. The OLED bar lit up
   regardless because record_packet announces PacketRx before the
   gate. Mirror the USB pattern: clear on successful tx.write_all,
   re-arm on failure.

2. Once RX flowed, sustained bidirectional spam tripped the §3.4
   inactivity watchdog spuriously. The select3-based loop serialised
   read and write; tx.write_all (~25 ms per max frame at 115_200
   baud) blocked the read poll long enough for last_frame_at to age
   past 1000 ms under a saturated outbound queue. The Reset cascaded
   to radio (Unconfigured) and display (splash), leaving subsequent
   host TX commands timing out waiting for TX_DONE. Restructure
   host_task into two sub-loops joined in the same task: read_loop
   owns rx + watchdog, write_loop owns tx + throttle. The read future
   re-polls on every executor wake regardless of write progress.
   Same executor, disjoint borrows on the split rx/tx halves, no
   Mutex needed.

Also reset the FrameDecoder when the watchdog fires so a host that
resumes mid-frame resyncs cleanly on the next 0x00 sentinel (USB
gets this for free via DTR-drop reset; UART has no DTR).

Build: bump esp-hal/esp-rtos/esp-backtrace/esp-println version
requirements (1.0->1.1, 0.2->0.3, 0.18->0.19, 0.16->0.17) to match
what the upstream esp-rs/esp-hal main branch now publishes. cargo
update had invalidated the [patch.crates-io] entries since the
patched versions were no longer semver-compatible with the old
top-level requirements, which silently dropped the patches and fell
back to crates.io releases that pin embassy-executor 0.9 (mismatched
with our 0.10).
This commit is contained in:
swaits committed 2026-04-29 22:26:16 -06:00
1 parent eb8202058f
commit f68e5d9be5
3 files changed
+212 -141

No files matched your search

Generated
+83 -83
View File
@@ -125,9 +125,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.11.0"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]]
name = "block-buffer"
@@ -167,9 +167,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.2.60"
version = "1.2.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -274,9 +274,9 @@ dependencies = [
[[package]]
name = "cpubits"
version = "0.1.0"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ef0c543070d296ea414df2dd7625d1b24866ce206709d8a4a424f28377f5861"
checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
[[package]]
name = "cpufeatures"
@@ -743,7 +743,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b715380574504335c40ec3016bc44598fac3c385f82959b26c41b25c30b7648d"
dependencies = [
"bitflags 2.11.0",
"bitflags 2.11.1",
"cfg-if",
"cortex-m",
"cortex-m-rt",
@@ -880,12 +880,12 @@ dependencies = [
[[package]]
name = "embassy-time-queue-utils"
version = "0.3.0"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454"
checksum = "168297bf80aaf114b3c9ad589bf38b01b3009b9af7f97cd18086c5bbf96f5693"
dependencies = [
"embassy-executor-timer-queue",
"heapless 0.8.0",
"heapless 0.9.2",
]
[[package]]
@@ -894,7 +894,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a25746d8b152b72fbf2a217f489a083dbbe243f281f09184a1f2cfbe9bbb245f"
dependencies = [
"bitflags 2.11.0",
"bitflags 2.11.1",
"defmt 1.0.1",
"embassy-futures",
"embassy-net-driver-channel",
@@ -1084,8 +1084,8 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "esp-backtrace"
version = "0.18.1"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#70930f20d9f681a30ff20aced39d0bd5363126d5"
version = "0.19.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#54a62c57f1f005020777a4e0843ce03c68312aba"
dependencies = [
"cfg-if",
"defmt 1.0.1",
@@ -1101,8 +1101,8 @@ dependencies = [
[[package]]
name = "esp-config"
version = "0.6.1"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#70930f20d9f681a30ff20aced39d0bd5363126d5"
version = "0.7.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#54a62c57f1f005020777a4e0843ce03c68312aba"
dependencies = [
"document-features",
"esp-metadata-generated",
@@ -1113,11 +1113,11 @@ dependencies = [
[[package]]
name = "esp-hal"
version = "1.0.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#70930f20d9f681a30ff20aced39d0bd5363126d5"
version = "1.1.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#54a62c57f1f005020777a4e0843ce03c68312aba"
dependencies = [
"bitfield 0.19.4",
"bitflags 2.11.0",
"bitflags 2.11.1",
"bytemuck",
"cfg-if",
"critical-section",
@@ -1156,7 +1156,7 @@ dependencies = [
"nb 1.1.0",
"paste",
"portable-atomic",
"rand_core 0.10.0",
"rand_core 0.10.1",
"rand_core 0.6.4",
"rand_core 0.9.5",
"riscv",
@@ -1170,8 +1170,8 @@ dependencies = [
[[package]]
name = "esp-hal-procmacros"
version = "0.21.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#70930f20d9f681a30ff20aced39d0bd5363126d5"
version = "0.22.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#54a62c57f1f005020777a4e0843ce03c68312aba"
dependencies = [
"document-features",
"object",
@@ -1184,13 +1184,13 @@ dependencies = [
[[package]]
name = "esp-metadata-generated"
version = "0.3.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#70930f20d9f681a30ff20aced39d0bd5363126d5"
version = "0.4.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#54a62c57f1f005020777a4e0843ce03c68312aba"
[[package]]
name = "esp-println"
version = "0.16.1"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#70930f20d9f681a30ff20aced39d0bd5363126d5"
version = "0.17.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#54a62c57f1f005020777a4e0843ce03c68312aba"
dependencies = [
"defmt 1.0.1",
"document-features",
@@ -1202,8 +1202,8 @@ dependencies = [
[[package]]
name = "esp-riscv-rt"
version = "0.13.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#70930f20d9f681a30ff20aced39d0bd5363126d5"
version = "0.14.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#54a62c57f1f005020777a4e0843ce03c68312aba"
dependencies = [
"document-features",
"riscv",
@@ -1212,8 +1212,8 @@ dependencies = [
[[package]]
name = "esp-rom-sys"
version = "0.1.3"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#70930f20d9f681a30ff20aced39d0bd5363126d5"
version = "0.1.4"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#54a62c57f1f005020777a4e0843ce03c68312aba"
dependencies = [
"cfg-if",
"document-features",
@@ -1224,8 +1224,8 @@ dependencies = [
[[package]]
name = "esp-rtos"
version = "0.2.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#70930f20d9f681a30ff20aced39d0bd5363126d5"
version = "0.3.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#54a62c57f1f005020777a4e0843ce03c68312aba"
dependencies = [
"cfg-if",
"document-features",
@@ -1246,8 +1246,8 @@ dependencies = [
[[package]]
name = "esp-sync"
version = "0.1.1"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#70930f20d9f681a30ff20aced39d0bd5363126d5"
version = "0.2.1"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#54a62c57f1f005020777a4e0843ce03c68312aba"
dependencies = [
"cfg-if",
"document-features",
@@ -1274,64 +1274,64 @@ dependencies = [
[[package]]
name = "esp32"
version = "0.39.0"
source = "git+https://github.com/esp-rs/esp-pacs?rev=fc3e6d4#fc3e6d4b34cfc885f750641d27e6fe3e63bad5d2"
version = "0.40.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5726e07689249d1a2cb7c492077bc424837fb68a64f7eb5d46569325352e9428"
dependencies = [
"critical-section",
"vcell",
]
[[package]]
name = "esp32c2"
version = "0.28.0"
source = "git+https://github.com/esp-rs/esp-pacs?rev=fc3e6d4#fc3e6d4b34cfc885f750641d27e6fe3e63bad5d2"
version = "0.29.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ef0b623533bbaa37e348c18b6b41cfd5b47c3cb64a4b9e44f0295941d62aa2e"
dependencies = [
"critical-section",
"vcell",
]
[[package]]
name = "esp32c3"
version = "0.31.0"
source = "git+https://github.com/esp-rs/esp-pacs?rev=fc3e6d4#fc3e6d4b34cfc885f750641d27e6fe3e63bad5d2"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21e89ed62cf6c043a6d29c520b02a13b359ec8a75d67b65d4330ed717d15fe97"
dependencies = [
"critical-section",
"vcell",
]
[[package]]
name = "esp32c6"
version = "0.22.0"
source = "git+https://github.com/esp-rs/esp-pacs?rev=fc3e6d4#fc3e6d4b34cfc885f750641d27e6fe3e63bad5d2"
version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c58f34ff2633968c12125efc7f4f8f101078d5d34c7cb60eab82268db20986f9"
dependencies = [
"critical-section",
"vcell",
]
[[package]]
name = "esp32h2"
version = "0.18.0"
source = "git+https://github.com/esp-rs/esp-pacs?rev=fc3e6d4#fc3e6d4b34cfc885f750641d27e6fe3e63bad5d2"
version = "0.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5bab026020ed4606ce113b6fde598dbc48f7eefcc46e9469ece77cc2b1aa4be"
dependencies = [
"critical-section",
"vcell",
]
[[package]]
name = "esp32s2"
version = "0.30.0"
source = "git+https://github.com/esp-rs/esp-pacs?rev=fc3e6d4#fc3e6d4b34cfc885f750641d27e6fe3e63bad5d2"
version = "0.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0ad6f21cdf6ec7b06b7f7e0fbe51f0d975fd6a5fa67c3f8a5a910d3981af531"
dependencies = [
"critical-section",
"vcell",
]
[[package]]
name = "esp32s3"
version = "0.34.0"
source = "git+https://github.com/esp-rs/esp-pacs?rev=fc3e6d4#fc3e6d4b34cfc885f750641d27e6fe3e63bad5d2"
version = "0.35.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b4b8c4e4d9f187553ecdb7173edec7b2deb2beea106eedefecdb1654b8ee25a"
dependencies = [
"critical-section",
"vcell",
]
@@ -1511,9 +1511,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.16.1"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]]
name = "heapless"
@@ -1550,9 +1550,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "hsmc"
version = "0.3.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebed0d04633829a1e8319efcb038684dff8c01d06e4e50a4669691fb7c706f9e"
checksum = "adbdeff427849daf4d03395027c8538dcdd02a0a26c6e59745f8647f5e657527"
dependencies = [
"defmt 0.3.100",
"embassy-futures",
@@ -1564,9 +1564,9 @@ dependencies = [
[[package]]
name = "hsmc-macros"
version = "0.3.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4de168c4459a45a99c71d51be8af79d94eb09ecd62d6f25baee6ec174297db2"
checksum = "66ae4d525e3047234d69597f616c89b9ad49ac6a851e4522322a9d74fc2cc1bf"
dependencies = [
"proc-macro2",
"quote",
@@ -1590,12 +1590,12 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "indexmap"
version = "2.13.0"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
"hashbrown 0.17.0",
]
[[package]]
@@ -1693,9 +1693,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.184"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "litrs"
@@ -1827,9 +1827,9 @@ checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
[[package]]
name = "minijinja"
version = "2.18.0"
version = "2.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "328251e58ad8e415be6198888fc207502727dc77945806421ab34f35bf012e7d"
checksum = "805bfd7352166bae857ee569628b52bcd85a1cecf7810861ebceb1686b72b75d"
dependencies = [
"memo-map",
"serde",
@@ -2129,7 +2129,7 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "679341d22c78c6c649893cbd6c3278dcbe9fc4faa62fea3a9296ae2b50c14625"
dependencies = [
"bitflags 2.11.0",
"bitflags 2.11.1",
"memchr",
"unicase",
]
@@ -2172,9 +2172,9 @@ checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
[[package]]
name = "rand_core"
version = "0.10.0"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba"
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
name = "redox_syscall"
@@ -2182,7 +2182,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags 2.11.0",
"bitflags 2.11.1",
]
[[package]]
@@ -2434,9 +2434,9 @@ checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9"
[[package]]
name = "sha3"
version = "0.10.8"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"
dependencies = [
"digest 0.10.7",
"keccak",
@@ -2661,9 +2661,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.25.9+spec-1.1.0"
version = "0.25.11+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da053d28fe57e2c9d21b48261e14e7b4c8b670b54d2c684847b91feaf4c7dac5"
checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b"
dependencies = [
"indexmap",
"toml_datetime",
@@ -2673,9 +2673,9 @@ dependencies = [
[[package]]
name = "toml_parser"
version = "1.1.1+spec-1.1.0"
version = "1.1.2+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ca317ebc49f06bd748bfba29533eac9485569dc9bf80b849024b025e814fb9"
checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
dependencies = [
"winnow",
]
@@ -2912,9 +2912,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5"
checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0"
dependencies = [
"memchr",
]
@@ -2922,15 +2922,15 @@ dependencies = [
[[package]]
name = "xtensa-lx"
version = "0.13.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#70930f20d9f681a30ff20aced39d0bd5363126d5"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#54a62c57f1f005020777a4e0843ce03c68312aba"
dependencies = [
"critical-section",
]
[[package]]
name = "xtensa-lx-rt"
version = "0.21.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#70930f20d9f681a30ff20aced39d0bd5363126d5"
version = "0.22.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#54a62c57f1f005020777a4e0843ce03c68312aba"
dependencies = [
"document-features",
"xtensa-lx",
@@ -2940,7 +2940,7 @@ dependencies = [
[[package]]
name = "xtensa-lx-rt-proc-macros"
version = "0.5.0"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#70930f20d9f681a30ff20aced39d0bd5363126d5"
source = "git+https://github.com/esp-rs/esp-hal.git?branch=main#54a62c57f1f005020777a4e0843ce03c68312aba"
dependencies = [
"proc-macro2",
"quote",
+46 -23
View File
@@ -14,8 +14,10 @@ homepage = "https://github.com/donglora/firmware"
keywords = ["lora", "radio", "embedded", "usb", "firmware"]
categories = ["embedded", "hardware-support", "no-std"]
# NOTE: esp-rtos patched from GitHub main (see [patch.crates-io] below).
# Remove patches once esp-rtos publishes with embassy-executor 0.10 support.
# NOTE: esp-{hal,rtos,backtrace,println} patched from GitHub main (see
# [patch.crates-io] below). The patch versions must stay semver-compatible
# with the version requirements above, or Cargo silently drops the patch
# and falls back to the (older) crates.io release.
[dependencies]
cfg-if = "1.0"
critical-section = "1.2"
@@ -31,7 +33,7 @@ embassy-usb-driver = "0.2"
ucobs = { version = "0.3", features = ["legacy-msrv"] }
heapless = "0.9"
donglora-protocol = "1"
hsmc = { version = "0.3", default-features = false, features = ["embassy", "trace-defmt"] }
hsmc = { version = "0.4", default-features = false, features = ["embassy", "trace-defmt"] }
# Local path while iterating on CAD IRQ-clear / standby-after-CAD fixes.
# Switch back to git fork (then crates.io) once changes are upstreamed.
lora-phy = { path = "../../lora-rs/lora-phy", features = ["defmt-03"] }
@@ -50,17 +52,17 @@ embassy-futures = "0.1"
# Xtensa ESP crates — requires esp toolchain (espup). The chip feature
# (esp32 / esp32s3) is selected per board via the board-level feature flag
# below, so the same dep lines support both classic ESP32 and ESP32-S3.
esp-backtrace = { version = "0.18", optional = true, features = [
esp-backtrace = { version = "0.19", optional = true, features = [
"panic-handler",
"defmt",
] }
esp-hal = { version = "1.0", optional = true, features = [
esp-hal = { version = "1.1", optional = true, features = [
"unstable",
] }
esp-println = { version = "0.16", optional = true, features = [
esp-println = { version = "0.17", optional = true, features = [
"defmt-espflash",
] }
esp-rtos = { version = "0.2", optional = true, features = [
esp-rtos = { version = "0.3", optional = true, features = [
"embassy",
] }
@@ -100,13 +102,22 @@ minijinja = "2.18"
owo-colors = "4.3"
[features]
# The `embassy` feature gates the async-actions codepath inside the `hsmc`
# statechart macro. Its `#[cfg(feature = "embassy")]` attributes are
# evaluated in *this* crate's context (a proc-macro cfg quirk), so every
# board feature must enable it. `tokio` is declared only to silence the
# macro's `#[cfg(feature = "tokio")]` checks.
# Bridge features for hsmc's macro-expanded `#[cfg(feature = "...")]`
# attributes. Macros expand into the *call-site* crate, so any cfg the
# macro emits is evaluated against THIS crate's feature set, not hsmc's.
# That means enabling `hsmc/embassy` or `hsmc/trace-defmt` via the dep
# only activates them inside hsmc's own compilation — for the cfg gates
# inside expanded macro bodies to fire here, we need same-named features
# in this Cargo.toml. `tokio` is declared just to silence the macro's
# `#[cfg(feature = "tokio")]` checks.
#
# `trace-defmt` must be in every board feature so chart trace lines
# (entries, exits, transitions, etc.) actually compile in. Without it,
# hsmc 0.4's `__chart_observe!` macro expands to nothing for the defmt
# arm and you get silent statecharts.
embassy = []
tokio = []
trace-defmt = []
# When enabled, replaces the display task with a LED-based diagnostic
# blinker driven by `crate::debug_blink::CHECKPOINT`. Used for diagnosing
# the firmware on UART-only boards where defmt output would corrupt the
@@ -114,13 +125,15 @@ tokio = []
# a human watches the pattern and reports N to determine where execution
# reached / stopped.
debug-checkpoint = []
# NOTE: chart auto-tracing is wired in unconditionally via the
# `trace;` keyword in each `statechart!` and the `hsmc/trace-defmt`
# feature on the dependency. Whether the trace lines actually emit at
# runtime is controlled by defmt's standard compile-time level filter
# (`DEFMT_LOG` env var). Release/shipping builds with `DEFMT_LOG`
# unset (or `off`) compile every chart trace call out to nothing —
# zero overhead, no separate cargo feature needed.
# NOTE: chart auto-tracing in hsmc 0.4 emits `__chart_observe!` calls
# whose defmt arm is gated on `#[cfg(feature = "trace-defmt")]`. That
# cfg is evaluated against THIS crate (proc-macro cfg quirk above), so
# every board feature includes `trace-defmt` to keep the arm live. The
# `trace;` keyword in `statechart!` blocks is deprecated in 0.4 and
# no longer gates emission. Whether trace lines actually emit at
# runtime is still controlled by defmt's standard compile-time level
# filter (`DEFMT_LOG` env var) -- release builds with `DEFMT_LOG`
# unset compile the calls out to nothing.
# When enabled, the SH1106 OLED driver emits a defmt::info! line for
# every I2C transaction it issues — bytes, lengths, addresses. Used to
# debug the embassy_nrf::twim assertion panic at twim.rs:541 (cannot
@@ -131,6 +144,7 @@ debug-checkpoint = []
debug-twim = []
heltec_v3 = [
"embassy",
"trace-defmt",
"dep:esp-backtrace",
"dep:esp-hal",
"dep:esp-println",
@@ -142,6 +156,7 @@ heltec_v3 = [
]
heltec_v3_uart = [
"embassy",
"trace-defmt",
"dep:esp-backtrace",
"dep:esp-hal",
"dep:esp-println",
@@ -153,6 +168,7 @@ heltec_v3_uart = [
]
heltec_v4 = [
"embassy",
"trace-defmt",
"dep:esp-backtrace",
"dep:esp-hal",
"dep:esp-println",
@@ -164,6 +180,7 @@ heltec_v4 = [
]
elecrow_thinknode_m2 = [
"embassy",
"trace-defmt",
"dep:esp-backtrace",
"dep:esp-hal",
"dep:esp-println",
@@ -175,6 +192,7 @@ elecrow_thinknode_m2 = [
]
lilygo_tbeam_supreme = [
"embassy",
"trace-defmt",
"dep:esp-backtrace",
"dep:esp-hal",
"dep:esp-println",
@@ -186,6 +204,7 @@ lilygo_tbeam_supreme = [
]
lilygo_tbeam = [
"embassy",
"trace-defmt",
"dep:esp-backtrace",
"dep:esp-hal",
"dep:esp-println",
@@ -197,6 +216,7 @@ lilygo_tbeam = [
]
rak_wisblock_4631 = [
"embassy",
"trace-defmt",
"dep:cortex-m",
"dep:cortex-m-rt",
"cortex-m-rt/set-vtor",
@@ -208,6 +228,7 @@ rak_wisblock_4631 = [
]
wio_tracker_l1 = [
"embassy",
"trace-defmt",
"dep:cortex-m",
"dep:cortex-m-rt",
"cortex-m-rt/set-vtor",
@@ -219,6 +240,7 @@ wio_tracker_l1 = [
]
waveshare_rp2040_lora = [
"embassy",
"trace-defmt",
"dep:cortex-m",
"dep:cortex-m-rt",
"dep:defmt-rtt",
@@ -228,10 +250,11 @@ waveshare_rp2040_lora = [
"embassy-executor/executor-thread",
]
# REVISIT: esp-rtos 0.2.0 on crates.io pins embassy-executor ^0.9. The
# esp-rs/esp-hal main branch already supports 0.10. Remove these patches once
# esp-rtos publishes a release with embassy-executor 0.10 support. Track:
# https://github.com/esp-rs/esp-hal/tree/main/esp-rtos
# REVISIT: pulling esp-{hal,rtos,backtrace,println} from esp-rs/esp-hal main
# to ride ahead of crates.io releases (notably for embassy-executor 0.10
# support in esp-rtos). When bumping the upstream rev, also bump the version
# requirements above to stay semver-compatible — Cargo silently ignores a
# patch whose version falls outside the requested range.
[patch.crates-io]
esp-hal = { git = "https://github.com/esp-rs/esp-hal.git", branch = "main" }
esp-rtos = { git = "https://github.com/esp-rs/esp-hal.git", branch = "main" }
+83 -35
View File
@@ -4,7 +4,7 @@
//! rely entirely on the spec §3.4 inactivity watchdog (1 s of silence
//! → Disconnected → drop radio to Unconfigured).
use defmt::warn;
use defmt::{info, warn};
use embassy_executor::task;
use embassy_time::{Duration, Instant};
use embedded_io_async::Write;
@@ -34,25 +34,32 @@ pub async fn host_task(
display_commands.send(DisplayCommand::On).await;
}
let mut read_buf = [0u8; READ_BUF];
let mut write_buf: [u8; MAX_WIRE_FRAME] = [0u8; MAX_WIRE_FRAME];
let mut decoder = FrameDecoder::new();
let mut last_frame_at = Instant::now();
let mut connected = true;
// Two sub-loops joined in the same task. Splitting read and write
// is what stops a long-running tx.write_all (~25 ms for a max-size
// frame at 115_200 baud, multiplied across a saturated outbound
// queue) from starving the inbound poll. While write_all sits in
// its FIFO-drain await, join re-polls the read future on the next
// wake, so last_frame_at keeps refreshing and the §3.4 watchdog
// does not spuriously fire under sustained bidirectional load.
// Same task = single executor + disjoint borrows on the split
// rx/tx halves, so no Mutex is needed.
let read_loop = async {
let mut read_buf = [0u8; READ_BUF];
let mut decoder = FrameDecoder::new();
let mut last_frame_at = Instant::now();
let mut connected = true;
loop {
use embassy_futures::select::{select3, Either3};
loop {
use embassy_futures::select::{select, Either};
match select3(
embedded_io_async::Read::read(&mut rx, &mut read_buf),
outbound.receive(),
embassy_time::Timer::after(Duration::from_millis(100)),
)
.await
{
Either3::First(result) => match result {
Ok(0) => continue,
Ok(n) => {
match select(
embedded_io_async::Read::read(&mut rx, &mut read_buf),
embassy_time::Timer::after(Duration::from_millis(100)),
)
.await
{
Either::First(Ok(0)) => {}
Either::First(Ok(n)) => {
last_frame_at = Instant::now();
if !connected {
if has_display {
@@ -62,31 +69,72 @@ pub async fn host_task(
}
dispatch_bytes(&mut decoder, &read_buf[..n], commands, outbound).await;
}
Err(_) => {
Either::First(Err(_)) => {
// Byte-level error (FIFO overrun, UART framing,
// parity). Per PROTOCOL.md §3.4: only "byte stream
// activity that results in a complete frame
// arriving" resets the inactivity timer, so we do
// NOT touch last_frame_at here. The 100 ms sleep
// is anti-spin: a stuck driver pumping continuous
// Errs would otherwise burn the executor.
decoder.reset();
embassy_time::Timer::after_millis(100).await;
}
},
Either3::Second(frame) => {
if let Some(n) = encode_outbound(&frame, &mut write_buf) {
if tx.write_all(&write_buf[..n]).await.is_err() {
warn!("UART write failed, frame dropped");
Either::Second(()) => {}
}
// Inactivity watchdog (PROTOCOL.md §3.4, 1000 ms).
if connected && last_frame_at.elapsed() >= Duration::from_millis(INACTIVITY_MS) {
warn!("UART host inactivity timeout fired");
let _ = commands.try_send(InboundEvent::Reset);
if has_display {
let _ = display_commands.try_send(DisplayCommand::Reset);
}
// Drop in-flight frame state. When the host resumes it
// may pick up mid-frame; without resetting the decoder
// we'd chew through garbage until the next 0x00
// sentinel. USB gets the equivalent for free via
// DTR-drop reset (host/usb.rs Disconnected entry); UART
// has no DTR.
decoder.reset();
connected = false;
}
}
};
let write_loop = async {
let mut write_buf: [u8; MAX_WIRE_FRAME] = [0u8; MAX_WIRE_FRAME];
loop {
let frame = outbound.receive().await;
let Some(n) = encode_outbound(&frame, &mut write_buf) else {
continue;
};
// Mirror src/host/usb.rs:349-374. The throttle gate at
// src/radio.rs:2017 silently drops async RX into the
// outbound queue while RADIO_THROTTLED is true; clear it
// on every successful write and re-arm it on failure,
// same contract as USB.
match tx.write_all(&write_buf[..n]).await {
Ok(()) => {
if crate::channel::RADIO_THROTTLED
.load(portable_atomic::Ordering::Acquire)
{
info!("UART write recovered; clearing radio throttle");
crate::channel::RADIO_THROTTLED
.store(false, portable_atomic::Ordering::Release);
}
}
Err(_) => {
warn!("UART write failed, frame dropped, throttling radio");
crate::channel::RADIO_THROTTLED
.store(true, portable_atomic::Ordering::Release);
}
}
Either3::Third(()) => {}
}
};
// Inactivity watchdog.
if connected && last_frame_at.elapsed() >= Duration::from_millis(INACTIVITY_MS) {
warn!("UART host inactivity timeout fired");
let _ = commands.try_send(InboundEvent::Reset);
if has_display {
let _ = display_commands.try_send(DisplayCommand::Reset);
}
connected = false;
}
}
embassy_futures::join::join(read_loop, write_loop).await;
}
async fn dispatch_bytes(