commit c30f480c349c48c1d0bfab5e7f75615ac68cd8d3 Author: Stephen Waits Date: Thu Mar 26 15:16:31 2026 -0600 feat: initial lora-dongle firmware scaffold Transparent LoRa-over-USB dongle firmware supporting Heltec V3 (ESP32-S3) and RAK 4631 (nRF52840) boards. Actor-model architecture with typed channels between USB, radio, and display tasks. - Protocol: Command/Response enums with COBS+postcard framing design - Board abstraction: auto-discovered via build.rs Jinja2 codegen - Radio task: SX1262 state machine (Idle → Receiving ↔ Transmitting) - Display task: optional SSD1306 dashboard via StatusWatch - RAK 4631: fully wired SPI/USB/I2C peripherals, clippy-clean - Heltec V3: structure in place, awaits espup Xtensa toolchain diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ff6449cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +src/board/mod.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..49c64828 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1841 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "az" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be5eb007b7cacc6c660343e96f650fedf4b5a77512399eb952ca6642cf8d13f7" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bare-metal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fe8f5a8a398345e52358e18ff07cc17a568fbca5c6f73873d3a62056309603" + +[[package]] +name = "basic-toml" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" +dependencies = [ + "serde", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "bitfield" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" + +[[package]] +name = "bitfield" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "num-traits", +] + +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror", +] + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal 0.2.5", + "bitfield 0.13.2", + "embedded-hal 0.2.7", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.117", +] + +[[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 2.0.117", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "delegate" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780eb241654bf097afb00fc5f054a09b687dad862e485fdcf8399bb056565370" +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", +] + +[[package]] +name = "embassy-embedded-hal" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5794414bc20e0d750f145bc0e82366b19dd078e9e075e8331fb8dd069a1cb6a2" +dependencies = [ + "defmt 0.3.100", + "embassy-futures", + "embassy-sync 0.6.2", + "embassy-time", + "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-executor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64f84599b0f4296b92a4b6ac2109bc02340094bda47b9766c5f9ec6a318ebf8" +dependencies = [ + "cortex-m", + "critical-section", + "document-features", + "embassy-executor-macros", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3577b1e9446f61381179a330fc5324b01d511624c55f25e3c66c9e3c626dbecf" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "embassy-futures" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" + +[[package]] +name = "embassy-hal-internal" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef3bac31ec146321248a169e9c7b5799f1e0b3829c7a9b324cb4600a7438f59" +dependencies = [ + "cortex-m", + "critical-section", + "defmt 0.3.100", + "num-traits", +] + +[[package]] +name = "embassy-net-driver" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524eb3c489760508f71360112bca70f6e53173e6fe48fc5f0efd0f5ab217751d" + +[[package]] +name = "embassy-net-driver-channel" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7b2739fbcf6cd206ae08779c7d709087b16577d255f2ea4a45bc4bbbf305b3f" +dependencies = [ + "embassy-futures", + "embassy-net-driver", + "embassy-sync 0.7.2", +] + +[[package]] +name = "embassy-nrf" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5f41f2ef4df68d3066c62667a0027b194cf6294e58afe8e6c36b8feede1e4ac" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "cortex-m", + "cortex-m-rt", + "critical-section", + "defmt 0.3.100", + "document-features", + "embassy-embedded-hal", + "embassy-hal-internal", + "embassy-sync 0.6.2", + "embassy-time-driver", + "embassy-usb-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-io", + "embedded-io-async", + "embedded-storage", + "embedded-storage-async", + "fixed", + "nrf51-pac", + "nrf52805-pac", + "nrf52810-pac", + "nrf52811-pac", + "nrf52820-pac", + "nrf52832-pac", + "nrf52833-pac", + "nrf52840-pac", + "nrf5340-app-pac", + "nrf5340-net-pac", + "nrf9120-pac", + "nrf9160-pac", + "rand_core", +] + +[[package]] +name = "embassy-sync" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd938f25c0798db4280fcd8026bf4c2f48789aebf8f77b6e5cf8a7693ba114ec" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-util", + "heapless", +] + +[[package]] +name = "embassy-sync" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d2c8cdff05a7a51ba0087489ea44b0b1d97a296ca6b1d6d1a33ea7423d34049" +dependencies = [ + "cfg-if", + "critical-section", + "defmt 0.3.100", + "embedded-io-async", + "futures-sink", + "futures-util", + "heapless", +] + +[[package]] +name = "embassy-sync" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73974a3edbd0bd286759b3d483540f0ebef705919a5f56f4fc7709066f71689b" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-core", + "futures-sink", + "heapless", +] + +[[package]] +name = "embassy-time" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158080d48f824fad101d7b2fae2d83ac39e3f7a6fa01811034f7ab8ffc6e7309" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embassy-time-queue-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-util", + "heapless", +] + +[[package]] +name = "embassy-time-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c214077aaa9206958b16411c157961fb7990d4ea628120a78d1a5a28aed24" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-driver" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1177859559ebf42cd24ae7ba8fe6ee707489b01d0bf471f8827b7b12dcb0bc0" + +[[package]] +name = "embassy-usb" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d0b882133fa684b9d4652351cd7aac5afe8a2c2bf4a7da59f442ff61087cda2" +dependencies = [ + "defmt 0.3.100", + "embassy-futures", + "embassy-net-driver-channel", + "embassy-sync 0.6.2", + "embassy-usb-driver", + "heapless", + "ssmarshal", + "usbd-hid", +] + +[[package]] +name = "embassy-usb-driver" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340c5ce591ef58c6449e43f51d2c53efe1bf0bb6a40cbf80afa0d259c7d52c76" +dependencies = [ + "defmt 1.0.1", + "embedded-io-async", +] + +[[package]] +name = "embassy-usb-synopsys-otg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d46be92e72bcf39e623ff74d739a8ab29b02f4909a9b05986ca81c2157ac254a" +dependencies = [ + "critical-section", + "embassy-sync 0.5.0", + "embassy-usb-driver", +] + +[[package]] +name = "embedded-can" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d2e857f87ac832df68fa498d18ddc679175cf3d2e4aa893988e5601baf9438" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io", +] + +[[package]] +name = "embedded-storage" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21dea9854beb860f3062d10228ce9b976da520a73474aed3171ec276bc0c032" + +[[package]] +name = "embedded-storage-async" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1763775e2323b7d5f0aa6090657f5e21cfa02ede71f5dc40eead06d64dcd15cc" +dependencies = [ + "embedded-storage", +] + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "enumset" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b07a8dfbbbfc0064c0a6bdf9edcf966de6b1c33ce344bdeca3b41615452634" +dependencies = [ + "enumset_derive", +] + +[[package]] +name = "enumset_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43e744e4ea338060faee68ed933e46e722fb7f3617e722a5772d7e856d8b3ce" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "esp-backtrace" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7660d85e3e7b0e113aaeeffb1a155e64a09a5035d4104031875acdba4cb68e" +dependencies = [ + "defmt 0.3.100", + "esp-build", + "esp-println", + "semihosting", +] + +[[package]] +name = "esp-build" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94a4b8d74e7cc7baabcca5b2277b41877e039ad9cd49959d48ef94dac7eab4b" +dependencies = [ + "quote", + "syn 2.0.117", + "termcolor", +] + +[[package]] +name = "esp-config" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7584e4cd1dac06201fd92fff1c84b396be5458ac4d93e9457e7a89b1b42c60e" +dependencies = [ + "document-features", +] + +[[package]] +name = "esp-hal" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5605e1518d63f7bf9fbd9885e61d2896060d2e4f28954736bdd74da911b676" +dependencies = [ + "basic-toml", + "bitfield 0.17.0", + "bitflags 2.11.0", + "bytemuck", + "cfg-if", + "chrono", + "critical-section", + "delegate", + "document-features", + "embassy-embedded-hal", + "embassy-futures", + "embassy-sync 0.6.2", + "embassy-usb-driver", + "embassy-usb-synopsys-otg", + "embedded-can", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "embedded-io-async", + "enumset", + "esp-build", + "esp-config", + "esp-hal-procmacros", + "esp-metadata", + "esp-riscv-rt", + "esp-synopsys-usb-otg", + "esp32s3", + "fugit", + "nb 1.1.0", + "paste", + "portable-atomic", + "rand_core", + "serde", + "strum", + "ufmt-write", + "usb-device", + "void", + "xtensa-lx", + "xtensa-lx-rt", +] + +[[package]] +name = "esp-hal-embassy" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d0f2537ea2ff9bea26a1c8bfe43ad580d4c89febf27189653a9cf95f1f7961" +dependencies = [ + "critical-section", + "document-features", + "embassy-executor", + "embassy-time-driver", + "esp-build", + "esp-config", + "esp-hal", + "esp-hal-procmacros", + "esp-metadata", + "portable-atomic", + "static_cell", +] + +[[package]] +name = "esp-hal-procmacros" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69a9a8706b7d1182b56335d196e70eeb04e2b70f4b8db96432898bd3c2bdb91e" +dependencies = [ + "darling 0.20.11", + "document-features", + "litrs 0.4.2", + "object", + "proc-macro-crate", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "esp-metadata" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9972bbb21dcafe430b87f92efc7a788978a2d17cf8f572d104beeb48602482a" +dependencies = [ + "anyhow", + "basic-toml", + "serde", + "strum", +] + +[[package]] +name = "esp-println" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee38e87bc7e303c299047c0e9bcd0f8ccca7c7e70d1fd78bbb565db14f33beb6" +dependencies = [ + "critical-section", + "defmt 0.3.100", + "esp-build", + "log", + "portable-atomic", +] + +[[package]] +name = "esp-riscv-rt" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94aca65db6157aa5f42d9df6595b21462f28207ca4230b799aa3620352ef6a72" +dependencies = [ + "document-features", + "riscv", + "riscv-rt-macros", +] + +[[package]] +name = "esp-synopsys-usb-otg" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8938451cb19032f13365328ea66ab38c8d16deecdf322067442297110eb74468" +dependencies = [ + "critical-section", + "embedded-hal 0.2.7", + "ral-registers", + "usb-device", + "vcell", +] + +[[package]] +name = "esp32s3" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe69dca8db0ed37d222555f12dfaf99232592e84f54e56dd81e450a4769718bf" +dependencies = [ + "critical-section", + "vcell", +] + +[[package]] +name = "fixed" +version = "1.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9af2cbf772fa6d1c11358f92ef554cb6b386201210bcf0e91fb7fba8a907fb40" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fugit" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e639847d312d9a82d2e75b0edcc1e934efcc64e6cb7aa94f0b1fbec0bc231d6" +dependencies = [ + "gcd", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", +] + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "serde", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lora-dongle" +version = "0.1.0" +dependencies = [ + "cfg-if", + "cortex-m-rt", + "critical-section", + "defmt 0.3.100", + "embassy-embedded-hal", + "embassy-executor", + "embassy-nrf", + "embassy-sync 0.6.2", + "embassy-time", + "embassy-usb", + "esp-backtrace", + "esp-hal", + "esp-hal-embassy", + "esp-println", + "heapless", + "lora-phy", + "minijinja", + "owo-colors", + "panic-probe", + "postcard", + "serde", + "static_cell", +] + +[[package]] +name = "lora-modulation" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c96c4a41a1f1ad04de765306e1829c7007a37898fe4033204fa82cf2e055624" + +[[package]] +name = "lora-phy" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61471c3b2909789e3332083577f6cf6c41a4fcf37674ef15156bcbb20504ac65" +dependencies = [ + "defmt 0.3.100", + "embedded-hal 1.0.0", + "embedded-hal-async", + "lora-modulation", + "num-traits", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memo-map" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b" + +[[package]] +name = "minijinja" +version = "2.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328251e58ad8e415be6198888fc207502727dc77945806421ab34f35bf012e7d" +dependencies = [ + "memo-map", + "serde", +] + +[[package]] +name = "mutex-trait" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4bb1638d419e12f8b1c43d9e639abd0d1424285bdea2f76aa231e233c63cd3a" + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "nrf51-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "137f187dc6ee482e27312086bd3c3a83e1c273512782cf131a61957f72fc4219" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "nrf52805-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2da657648039d59f4de6bc31b948dd3a5d03b32529a4d5d19d9e2dd9d4bfa6c" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "nrf52810-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c26b12d5af17a9f4bb9a06ca9a1f814bca3d67bc8715b23f8dc230b09a227666" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "nrf52811-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4179b2a7ed0b2fd5e109d0fab9b4fc55b3936b2a4916a9306d22e5bc8dc1fd8f" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "nrf52820-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4791cff995e6419a5ad1aebc3b3c9539d79125ca85eb5bfd2cff9b470b81071" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "nrf52832-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0242b685c9c15648fb803e155628f42ace457478b2cb930868f40cae2db925e0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "nrf52833-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10e1358255b360cdc816dd7b6ef81be8c8499c0998277e5249bed222bd0f5241" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "nrf52840-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30713f36f1be02e5bc9abefa30eae4a1f943d810f199d4923d3ad062d1be1b3d" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "nrf5340-app-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c88824573cd150fe9f27c1a48cea31a8cb24d3322df488875775143618c087a" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "nrf5340-net-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c03e44df22fe5888109fe42e523162c7059adf4d30860f4f73ecc8b1fc16fe" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "nrf9120-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c012f18dc278aa33741722d374bc84e3d2d7694e29745f0bb83e56b2d6faf9b" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "nrf9160-pac" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7344d74afb5684e00c48d175cad9619f36d629cfb0687d33b4d1bb86fba688f4" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "panic-probe" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4047d9235d1423d66cc97da7d07eddb54d4f154d6c13805c6d0793956f4f25b0" +dependencies = [ + "cortex-m", + "defmt 0.3.100", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "serde", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.8+spec-1.1.0", +] + +[[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 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r0" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7a31eed1591dcbc95d92ad7161908e72f4677f8fabf2a32ca49b4237cbf211" + +[[package]] +name = "ral-registers" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46b71a9d9206e8b46714c74255adcaea8b11e0350c1d8456165073c3f75fc81a" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "riscv" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea8ff73d3720bdd0a97925f0bf79ad2744b6da8ff36be3840c48ac81191d7a7" +dependencies = [ + "critical-section", + "embedded-hal 1.0.0", + "paste", + "riscv-macros", + "riscv-pac", +] + +[[package]] +name = "riscv-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f265be5d634272320a7de94cea15c22a3bfdd4eb42eb43edc528415f066a1f25" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "riscv-pac" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8188909339ccc0c68cfb5a04648313f09621e8b87dc03095454f1a11f6c5d436" + +[[package]] +name = "riscv-rt-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30f19a85fe107b65031e0ba8ec60c34c2494069fe910d6c297f5e7cb5a6f76d0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semihosting" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8e4abf97879f4e80db69a9fba7bd64998e9bdad25f58ef045a778e191172fd4" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "ssmarshal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e6ad23b128192ed337dfa4f1b8099ced0c2bf30d61e551b65fda5916dbb850" +dependencies = [ + "encode_unicode", + "serde", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_cell" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", +] + +[[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]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97251a7c317e03ad83774a8752a7e81fb6067740609f75ea2b585b569a59198f" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[package]] +name = "toml_edit" +version = "0.25.8+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16bff38f1d86c47f9ff0647e6838d7bb362522bdf44006c7068c2b1e606f1f3c" +dependencies = [ + "indexmap", + "toml_datetime 1.1.0+spec-1.1.0", + "toml_parser", + "winnow 1.0.0", +] + +[[package]] +name = "toml_parser" +version = "1.1.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2334f11ee363607eb04df9b8fc8a13ca1715a72ba8662a26ac285c98aabb4011" +dependencies = [ + "winnow 1.0.0", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ufmt-write" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87a2ed6b42ec5e28cc3b94c09982969e9227600b2e3dcbc1db927a84c06bd69" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "usbd-hid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f291ab53d428685cc780f08a2eb9d5d6ff58622db2b36e239a4f715f1e184c" +dependencies = [ + "serde", + "ssmarshal", + "usb-device", + "usbd-hid-macros", +] + +[[package]] +name = "usbd-hid-descriptors" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee54712c5d778d2fb2da43b1ce5a7b5060886ef7b09891baeb4bf36910a3ed" +dependencies = [ + "bitfield 0.14.0", +] + +[[package]] +name = "usbd-hid-macros" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb573c76e7884035ac5e1ab4a81234c187a82b6100140af0ab45757650ccda38" +dependencies = [ + "byteorder", + "hashbrown 0.13.2", + "log", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", + "usbd-hid-descriptors", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" +dependencies = [ + "memchr", +] + +[[package]] +name = "xtensa-lx" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e758f94e1a1f71758f94052a2766dcb12604998eb372b8b2e30576e3ab1ba1e6" +dependencies = [ + "bare-metal 1.0.0", + "mutex-trait", + "spin", +] + +[[package]] +name = "xtensa-lx-rt" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0307d03dadbf95633942e13901984f2059df4c963367348168cbd21c962669" +dependencies = [ + "anyhow", + "bare-metal 1.0.0", + "document-features", + "enum-as-inner", + "minijinja", + "r0", + "serde", + "strum", + "toml", + "xtensa-lx", + "xtensa-lx-rt-proc-macros", +] + +[[package]] +name = "xtensa-lx-rt-proc-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11277b1e4cbb7ffe44678c668518b249c843c81df249b8f096701757bc50d7ee" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerocopy" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbb2a062be311f2ba113ce66f697a4dc589f85e78a4aea276200804cea0ed87" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e8bc7269b54418e7aeeef514aa68f8690b8c0489a06b0136e5f57c4c5ccab89" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..092000c5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "lora-dongle" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +[dependencies] +cfg-if = "1.0" +critical-section = "1.2" +defmt = "0.3" +embassy-embedded-hal = "0.2" +embassy-executor = { version = "0.6", features = ["task-arena-size-12288", "executor-thread"] } +embassy-sync = "0.6" +embassy-time = "0.3" +embassy-usb = { version = "0.3", features = ["defmt"] } +heapless = { version = "0.8", features = ["serde"] } +lora-phy = "3.0" +postcard = { version = "1.0", default-features = false } +serde = { version = "1.0", default-features = false, features = ["derive"] } +static_cell = "2.1" + +# ESP32-S3 (Heltec V3) +esp-backtrace = { version = "0.14", optional = true, features = ["esp32s3", "panic-handler", "defmt"] } +esp-hal = { version = "0.22", optional = true, features = ["esp32s3"] } +esp-hal-embassy = { version = "0.5", optional = true, features = ["esp32s3"] } +esp-println = { version = "0.12", optional = true, features = ["esp32s3", "defmt-espflash"] } + +# nRF52840 (RAK 4631) +cortex-m-rt = { version = "0.7", optional = true } +embassy-nrf = { version = "0.2", optional = true, features = ["nrf52840", "gpiote", "time-driver-rtc1", "defmt"] } +panic-probe = { version = "0.3", optional = true, features = ["print-defmt"] } + +[build-dependencies] +minijinja = "2.0" +owo-colors = "4.0" + +[features] +heltec_v3 = [ + "dep:esp-backtrace", + "dep:esp-hal", + "dep:esp-hal-embassy", + "dep:esp-println", +] +rak_4631 = [ + "dep:cortex-m-rt", + "dep:embassy-nrf", + "dep:panic-probe", + "embassy-executor/arch-cortex-m", +] diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 00000000..0b46f712 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,76 @@ +# Design Principles + +These are the non-negotiable first principles of this project. Every design decision, +naming choice, module boundary, and abstraction is evaluated against them. When in +doubt, choose simplicity and clarity over cleverness. + +## What This Is + +A transparent LoRa radio over USB. Plug the dongle into your PC, talk to any LoRa +radio within range. That's it. + +## What This Is Not + +Not a mesh network node. Not a Meshtastic device. Not a protocol implementation. Not +an application framework. This firmware has no opinion about what you do with your +radio — it just gives you clean access to it. + +## Principles + +1. **Single purpose.** Transparent LoRa radio over USB. No mesh, no routing, no + application logic. Ever. + +2. **Protocol-agnostic.** The firmware is a dumb pipe. It exposes clean LoRa + parameters (frequency, bandwidth, spreading factor, coding rate, sync word, TX + power) and nothing else. No raw register access, no chip-specific leakage. + +3. **Host-driven.** The firmware makes zero autonomous decisions. The radio idles on + boot until the host commands it. No persistence across reboots. The host is always + in control. + +4. **Zero-config.** Boots, enumerates USB, waits for commands. The host plugs in and + goes. + +5. **Robustness over features.** Tasks never panic. Errors are reported to the host. + The radio self-recovers. Ship fewer features, but make them bulletproof. + +6. **Minimal footprint.** Avoid heavy dependencies. Keep the binary small. Every + dependency must justify its inclusion. + +7. **Auto-detect peripherals.** If the board has a display, use it. No user + configuration required. + +8. **World-class Rust.** Fully leverage the type system for safety. Strict `no_std`. + Always clippy-clean. Thoughtfully designed — everything has an obvious home. + Anyone who reads this code should immediately think *"this is nice."* + +9. **Easy board support.** Adding a new board means dropping a `.rs` file and adding + a Cargo feature. Nothing else changes. + +## Architecture + +``` +Host PC + │ USB CDC-ACM (COBS-framed postcard) + ▼ +usb_task ──Command──► radio_task ──► SX1262 + ◄──Response── │ + StatusWatch + ▼ + display_task (optional) ──► OLED/TFT +``` + +Each task exclusively owns its peripheral. No shared state except typed channels and +a watch signal. Pure actor model. + +## Technical Decisions + +| Decision | Choice | Why | +|---|---|---| +| USB framing | COBS + postcard | Compact, `no_std` native, battle-tested | +| Serialization | postcard + serde | Zero-alloc binary codec, perfect for embedded | +| Async runtime | Embassy | De facto standard for async embedded Rust | +| Display | Radio status dashboard | Freq, BW, SF, packet counts, RSSI/SNR | +| Boot behavior | Idle until host commands | Host-driven principle | +| Persistence | None | Always start fresh, true dumb pipe | +| Board codegen | Jinja2 via build.rs | Auto-discovers boards from filesystem | diff --git a/Justfile b/Justfile new file mode 100644 index 00000000..bba6c6e3 --- /dev/null +++ b/Justfile @@ -0,0 +1,35 @@ +set shell := ["bash", "-c"] +heltec_v3 := "heltec_v3 xtensa-esp32s3-none-elf esp32s3" +rak_4631 := "rak_4631 thumbv7em-none-eabihf nRF52840_xxAA" + +# Check all boards compile cleanly +check-all: + just check heltec_v3 + just check rak_4631 + +# Check a single board compiles +check board: + @read -r feat target chip <<< "$(just _info {{board}})"; \ + cargo check --target $target --features $feat + +# Run clippy on a single board +clippy board: + @read -r feat target chip <<< "$(just _info {{board}})"; \ + cargo clippy --target $target --features $feat -- -D warnings + +# Build and flash a board via probe-rs +flash board: + @read -r feat target chip <<< "$(just _info {{board}})"; \ + cargo build --release --target $target --features $feat; \ + probe-rs run --chip $chip --target $target --features $feat + +# Show binary size for a release build +size board: + @read -r feat target chip <<< "$(just _info {{board}})"; \ + cargo size --release --target $target --features $feat + +[private] +_info name: + @if [ "{{name}}" == "heltec_v3" ]; then echo "{{heltec_v3}}"; \ + elif [ "{{name}}" == "rak_4631" ]; then echo "{{rak_4631}}"; \ + else echo "Unknown board" >&2; exit 1; fi diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..1a807a2b --- /dev/null +++ b/build.rs @@ -0,0 +1,35 @@ +use minijinja::{context, Environment}; +use owo_colors::OwoColorize; +use std::fs; + +fn main() { + let board_dir = "src/board"; + let boards: Vec = fs::read_dir(board_dir).unwrap() + .filter_map(|e| { + let p = e.ok()?.path(); + if p.is_file() && p.extension()? == "rs" && p.file_stem()? != "mod" { + return Some(p.file_stem()?.to_str()?.to_string()); + } + None + }).collect(); + + let enabled: Vec<_> = std::env::vars() + .filter(|(k, _)| k.starts_with("CARGO_FEATURE_")) + .map(|(k, _)| k.replace("CARGO_FEATURE_", "").to_lowercase()) + .filter(|f| boards.contains(f)).collect(); + + match enabled.len() { + 0 => panic!("\n\n{}\n", format!("No board selected! Found: {:?}", boards).red().bold()), + 1 => println!("cargo:rustc-cfg=board_selected"), + _ => panic!("\n\n{}\n", format!("Conflict: Multiple boards enabled {:?}", enabled).yellow().bold()), + } + + let mut env = Environment::new(); + let template = fs::read_to_string("src/board/mod.rs.j2").expect("Missing template .j2"); + env.add_template("mod", &template).unwrap(); + let rendered = env.get_template("mod").unwrap().render(context!(boards => boards)).unwrap(); + fs::write("src/board/mod.rs", rendered).unwrap(); + + println!("cargo:rerun-if-changed=src/board/mod.rs.j2"); + println!("cargo:rerun-if-changed={}", board_dir); +} diff --git a/src/board/heltec_v3.rs b/src/board/heltec_v3.rs new file mode 100644 index 00000000..f1c4d088 --- /dev/null +++ b/src/board/heltec_v3.rs @@ -0,0 +1,70 @@ +use esp_hal::gpio::{GpioPin, Input, Output, PullDown, PushPull}; +use esp_hal::peripherals::SPI2; +use esp_hal::spi::master::Spi; +use esp_hal::spi::SpiMode; +use esp_hal::i2c::I2c; +use esp_hal::peripherals::I2C0; +use esp_hal::Delay; + +use lora_phy::iv::GenericSx126xInterfaceVariant; +use lora_phy::sx126x::{self, Sx1262, Sx126x}; +use lora_phy::LoRa; + +/// Board name shown on the display dashboard. +pub const BOARD_NAME: &str = "Heltec V3"; + +// ── Concrete peripheral types ──────────────────────────────────────── + +type Iv = GenericSx126xInterfaceVariant>, Input<'static, GpioPin<14>>>; +type RadioSpi = Spi<'static, SPI2>; +type RadioDriver = Sx126x; + +/// Fully-constructed LoRa radio driver. +pub type Radio = LoRa; + +/// USB driver for CDC-ACM. +// TODO: define concrete USB OTG type once esp-hal USB support is wired up +pub type UsbDriver = (); + +/// I2C bus for the built-in SSD1306 OLED. +pub type DisplayI2c = I2c<'static, I2C0>; + +// ── Peripheral bundles ─────────────────────────────────────────────── + +pub struct RadioParts { + pub radio: Radio, +} + +pub struct UsbParts { + pub driver: UsbDriver, +} + +pub struct DisplayParts { + pub i2c: DisplayI2c, +} + +// ── Board init ─────────────────────────────────────────────────────── + +pub struct Board { + p: esp_hal::peripherals::Peripherals, +} + +impl Board { + pub fn init() -> Self { + let p = esp_hal::init(esp_hal::Config::default()); + Self { p } + } + + pub fn into_parts(self) -> (RadioParts, UsbParts, Option) { + let _p = self.p; + + // TODO: implement Heltec V3 peripheral init + // - SPI2 for SX1262 (SCK=9, MOSI=10, MISO=11, NSS=8) + // - GPIO for radio control (RST=12, DIO1=14, BUSY=13) + // - Vext pin (GPIO21) LOW to power OLED + // - I2C0 for SSD1306 (SDA=17, SCL=18) + // - USB OTG for CDC-ACM + + todo!("Heltec V3 peripheral init — requires espup toolchain to develop") + } +} diff --git a/src/board/mod.rs.j2 b/src/board/mod.rs.j2 new file mode 100644 index 00000000..55035d1d --- /dev/null +++ b/src/board/mod.rs.j2 @@ -0,0 +1,9 @@ +// @generated by build.rs — DO NOT EDIT +cfg_if::cfg_if! { + {%- for board in boards %} + {% if loop.first %}if{% else %}else if{% endif %} #[cfg(feature = "{{ board }}")] { + mod {{ board }}; + pub use {{ board }}::*; + } + {%- endfor %} +} diff --git a/src/board/rak_4631.rs b/src/board/rak_4631.rs new file mode 100644 index 00000000..a56d2ecc --- /dev/null +++ b/src/board/rak_4631.rs @@ -0,0 +1,117 @@ +use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; +use embassy_nrf::gpio::{AnyPin, Input, Level, Output, OutputDrive, Pull}; +use embassy_nrf::peripherals::USBD; +use embassy_nrf::spim::{self, Spim}; +use embassy_nrf::twim::{self, Twim}; +use embassy_nrf::usb::Driver; +use embassy_nrf::{bind_interrupts, Peripherals}; +use embassy_sync::blocking_mutex::raw::NoopRawMutex; +use embassy_time::Delay; +use static_cell::StaticCell; + +use lora_phy::iv::GenericSx126xInterfaceVariant; +use lora_phy::sx126x::{self, Sx1262, Sx126x}; + +bind_interrupts!(struct Irqs { + USBD => embassy_nrf::usb::InterruptHandler; + SPIM3 => embassy_nrf::spim::InterruptHandler; + SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0 => embassy_nrf::twim::InterruptHandler; +}); + +/// Board name shown on the display dashboard. +#[allow(dead_code)] +pub const BOARD_NAME: &str = "RAK 4631"; + +// ── Concrete peripheral types ──────────────────────────────────────── + +type SpiBus = Spim<'static, embassy_nrf::peripherals::SPI3>; +type Nss = Output<'static>; +type Iv = GenericSx126xInterfaceVariant, Input<'static>>; +type RadioSpiDevice = SpiDevice<'static, NoopRawMutex, SpiBus, Nss>; +/// SX126x driver (pre-LoRa construction — LoRa::new is async). +pub type RadioDriver = Sx126x; + +/// USB driver for CDC-ACM. +pub type UsbDriver = Driver<'static, USBD, &'static embassy_nrf::usb::vbus_detect::SoftwareVbusDetect>; + +/// I2C bus for an optional SSD1306 OLED. +pub type DisplayI2c = Twim<'static, embassy_nrf::peripherals::TWISPI0>; + +// ── Peripheral bundles ─────────────────────────────────────────────── + +pub struct RadioParts { + pub driver: RadioDriver, + pub delay: Delay, +} + +pub struct UsbParts { + pub driver: UsbDriver, +} + +pub struct DisplayParts { + pub i2c: DisplayI2c, +} + +// ── Board init ─────────────────────────────────────────────────────── + +pub struct Board { + p: Peripherals, +} + +impl Board { + pub fn init() -> Self { + let p = embassy_nrf::init(Default::default()); + Self { p } + } + + pub fn into_parts(self) -> (RadioParts, UsbParts, Option) { + let p = self.p; + + // ── SPI bus for SX1262 ─────────────────────────────────── + let mut spi_cfg = spim::Config::default(); + spi_cfg.frequency = spim::Frequency::M1; + let spi = Spim::new(p.SPI3, Irqs, p.P1_11, p.P1_13, p.P1_12, spi_cfg); + + static SPI_BUS: StaticCell> = + StaticCell::new(); + let spi_bus = SPI_BUS.init(embassy_sync::mutex::Mutex::new(spi)); + + let nss = Output::new(AnyPin::from(p.P1_10), Level::High, OutputDrive::Standard); + let spi_device = SpiDevice::new(spi_bus, nss); + + // ── SX1262 control pins ────────────────────────────────── + let reset = Output::new(AnyPin::from(p.P1_06), Level::High, OutputDrive::Standard); + let dio1 = Input::new(AnyPin::from(p.P1_15), Pull::Down); + let busy = Input::new(AnyPin::from(p.P1_14), Pull::Down); + + let iv = GenericSx126xInterfaceVariant::new(reset, dio1, busy, None, None).unwrap(); + + let sx_config = sx126x::Config { + chip: Sx1262, + tcxo_ctrl: Some(sx126x::TcxoCtrlVoltage::Ctrl1V7), + use_dcdc: true, + rx_boost: false, + }; + + let radio = RadioParts { + driver: Sx126x::new(spi_device, iv, sx_config), + delay: Delay, + }; + + // ── USB ────────────────────────────────────────────────── + static VBUS: StaticCell = + StaticCell::new(); + let vbus = VBUS.init(embassy_nrf::usb::vbus_detect::SoftwareVbusDetect::new(true, false)); + let usb = UsbParts { + driver: Driver::new(p.USBD, Irqs, vbus), + }; + + // ── Display (optional RAK1921 SSD1306 OLED on I2C) ────── + // TODO: probe I2C address 0x3C to detect presence + let i2c_cfg = twim::Config::default(); + let i2c = Twim::new(p.TWISPI0, Irqs, p.P0_13, p.P0_14, i2c_cfg); + let display = Some(DisplayParts { i2c }); + + (radio, usb, display) + } +} diff --git a/src/channel.rs b/src/channel.rs new file mode 100644 index 00000000..1bc9a2b0 --- /dev/null +++ b/src/channel.rs @@ -0,0 +1,46 @@ +use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; +use embassy_sync::channel::Channel; +use embassy_sync::watch::Watch; + +use crate::protocol::{Command, RadioConfig, Response}; + +/// Host-to-radio command channel. +pub type CommandChannel = Channel; + +/// Radio-to-host response channel. +pub type ResponseChannel = Channel; + +/// Observable radio status for the display task. +pub type StatusWatch = Watch; + +/// Current radio state exposed to observers (e.g. display). +#[derive(Debug, Clone, defmt::Format)] +pub struct RadioStatus { + pub state: RadioState, + pub config: Option, + pub rx_count: u32, + pub tx_count: u32, + pub last_rssi: Option, + pub last_snr: Option, +} + +/// Radio state machine states. +#[derive(Debug, Clone, Copy, PartialEq, Eq, defmt::Format)] +pub enum RadioState { + Idle, + Receiving, + Transmitting, +} + +impl Default for RadioStatus { + fn default() -> Self { + Self { + state: RadioState::Idle, + config: None, + rx_count: 0, + tx_count: 0, + last_rssi: None, + last_snr: None, + } + } +} diff --git a/src/display/mod.rs b/src/display/mod.rs new file mode 100644 index 00000000..ff2dc929 --- /dev/null +++ b/src/display/mod.rs @@ -0,0 +1,3 @@ +mod render; +mod task; +pub use task::display_task; diff --git a/src/display/render.rs b/src/display/render.rs new file mode 100644 index 00000000..bb8082c5 --- /dev/null +++ b/src/display/render.rs @@ -0,0 +1,25 @@ +use crate::channel::{RadioState, RadioStatus}; + +/// Render the status dashboard to a framebuffer. +/// +/// Pure logic — no hardware dependency. Takes a status snapshot and produces +/// draw calls that the display task flushes to the screen. +pub fn dashboard(status: &RadioStatus) { + // TODO: use embedded-graphics to render: + // Line 1: board name + radio state + // Line 2: freq / BW / SF + // Line 3: TX power / coding rate + // Line 4: RX count / TX count + // Line 5: last RSSI / SNR + let _ = status; +} + +/// Format radio state as a short display string. +#[allow(dead_code)] +pub fn state_label(state: RadioState) -> &'static str { + match state { + RadioState::Idle => "IDLE", + RadioState::Receiving => "RX", + RadioState::Transmitting => "TX", + } +} diff --git a/src/display/task.rs b/src/display/task.rs new file mode 100644 index 00000000..3ead9b20 --- /dev/null +++ b/src/display/task.rs @@ -0,0 +1,22 @@ +use embassy_executor::task; + +use crate::board::DisplayParts; +use crate::channel::StatusWatch; + +use super::render; + +#[task] +pub async fn display_task(parts: DisplayParts, status: &'static StatusWatch) { + let _i2c = parts.i2c; + + // TODO: init SSD1306 driver from I2C handle + // TODO: initial render of empty dashboard + + let mut receiver = status.receiver().unwrap(); + + loop { + let state = receiver.changed().await; + render::dashboard(&state); + // TODO: flush framebuffer to display + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..6d19e74b --- /dev/null +++ b/src/error.rs @@ -0,0 +1,16 @@ +/// Radio hardware errors. +#[allow(dead_code)] +#[derive(Debug, defmt::Format)] +pub enum RadioError { + InitFailed, + TxTimeout, + InvalidConfig, +} + +/// USB protocol framing errors. +#[allow(dead_code)] +#[derive(Debug, defmt::Format)] +pub enum ProtocolError { + DeserializeFailed, + BufferOverflow, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..8feca9aa --- /dev/null +++ b/src/main.rs @@ -0,0 +1,39 @@ +#![no_std] +#![no_main] + +mod board; +mod channel; +mod display; +mod error; +mod protocol; +mod radio; +mod usb; + +use embassy_executor::Spawner; + +use crate::channel::{CommandChannel, ResponseChannel, StatusWatch}; + +cfg_if::cfg_if! { + if #[cfg(feature = "rak_4631")] { + use panic_probe as _; + } else if #[cfg(feature = "heltec_v3")] { + use esp_backtrace as _; + } +} + +static COMMANDS: CommandChannel = CommandChannel::new(); +static RESPONSES: ResponseChannel = ResponseChannel::new(); +static STATUS: StatusWatch = StatusWatch::new(); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let board = board::Board::init(); + let (radio, usb, display) = board.into_parts(); + + spawner.spawn(radio::radio_task(radio, &COMMANDS, &RESPONSES, &STATUS)).unwrap(); + spawner.spawn(usb::usb_task(usb, &COMMANDS, &RESPONSES)).unwrap(); + + if let Some(dp) = display { + spawner.spawn(display::display_task(dp, &STATUS)).unwrap(); + } +} diff --git a/src/protocol.rs b/src/protocol.rs new file mode 100644 index 00000000..e84fb1ca --- /dev/null +++ b/src/protocol.rs @@ -0,0 +1,89 @@ +use heapless::Vec; +use serde::{Deserialize, Serialize}; + +/// Maximum LoRa payload size in bytes. +pub const MAX_PAYLOAD: usize = 256; + +/// Protocol version for host compatibility checks. +#[allow(dead_code)] +pub const PROTOCOL_VERSION: u8 = 1; + +/// LoRa signal bandwidth. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, defmt::Format)] +#[repr(u8)] +pub enum Bandwidth { + Khz7 = 0, + Khz10 = 1, + Khz15 = 2, + Khz20 = 3, + Khz31 = 4, + Khz41 = 5, + Khz62 = 6, + Khz125 = 7, + Khz250 = 8, + Khz500 = 9, +} + +/// LoRa forward error correction coding rate. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, defmt::Format)] +#[repr(u8)] +pub enum CodingRate { + Cr4_5 = 1, + Cr4_6 = 2, + Cr4_7 = 3, + Cr4_8 = 4, +} + +/// Complete LoRa radio configuration. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, defmt::Format)] +pub struct RadioConfig { + pub freq_hz: u32, + pub bw: Bandwidth, + pub sf: u8, + pub cr: CodingRate, + pub sync_word: u16, + pub tx_power_dbm: i8, +} + +/// Host → firmware commands. +// Payload variant is intentionally large (inline heapless::Vec) — no allocator available. +#[allow(clippy::large_enum_variant)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub enum Command { + Ping, + GetConfig, + SetConfig(RadioConfig), + StartRx, + StopRx, + Transmit { + config: Option, + payload: Vec, + }, +} + +/// Firmware → host responses. +// Payload variant is intentionally large (inline heapless::Vec) — no allocator available. +#[allow(clippy::large_enum_variant)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] +pub enum Response { + Pong, + Config(RadioConfig), + RxPacket { + rssi: i16, + snr: i8, + payload: Vec, + }, + TxDone, + Error(ErrorCode), +} + +/// Error codes reported to the host. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, defmt::Format)] +#[repr(u8)] +pub enum ErrorCode { + InvalidConfig = 1, + RadioBusy = 2, + TxTimeout = 3, + CrcError = 4, + NotConfigured = 5, +} diff --git a/src/radio/mod.rs b/src/radio/mod.rs new file mode 100644 index 00000000..85bb11e3 --- /dev/null +++ b/src/radio/mod.rs @@ -0,0 +1,2 @@ +mod task; +pub use task::radio_task; diff --git a/src/radio/task.rs b/src/radio/task.rs new file mode 100644 index 00000000..4218f3db --- /dev/null +++ b/src/radio/task.rs @@ -0,0 +1,95 @@ +use defmt::error; +use embassy_executor::task; +use lora_phy::LoRa; + +use crate::board::RadioParts; +use crate::channel::{CommandChannel, RadioState, RadioStatus, ResponseChannel, StatusWatch}; +use crate::protocol::{Command, ErrorCode, Response}; + +#[task] +pub async fn radio_task( + parts: RadioParts, + commands: &'static CommandChannel, + responses: &'static ResponseChannel, + status: &'static StatusWatch, +) { + let mut state = RadioStatus::default(); + status.sender().send(state.clone()); + + let lora = LoRa::new(parts.driver, false, parts.delay).await; + let mut lora = match lora { + Ok(l) => l, + Err(e) => { + error!("radio init failed: {}", e); + responses.send(Response::Error(ErrorCode::InvalidConfig)).await; + // Cannot proceed without radio — idle forever. + loop { + let cmd = commands.receive().await; + if let Command::Ping = cmd { + responses.send(Response::Pong).await; + } else { + responses.send(Response::Error(ErrorCode::InvalidConfig)).await; + } + } + } + }; + + loop { + let cmd = commands.receive().await; + match cmd { + Command::Ping => { + responses.send(Response::Pong).await; + } + Command::GetConfig => { + if let Some(cfg) = state.config { + responses.send(Response::Config(cfg)).await; + } else { + responses.send(Response::Error(ErrorCode::NotConfigured)).await; + } + } + Command::SetConfig(cfg) => { + state.config = Some(cfg); + // TODO: apply config to lora hardware + status.sender().send(state.clone()); + } + Command::StartRx => { + if state.config.is_some() { + state.state = RadioState::Receiving; + // TODO: start continuous RX via lora + status.sender().send(state.clone()); + } else { + responses.send(Response::Error(ErrorCode::NotConfigured)).await; + } + } + Command::StopRx => { + state.state = RadioState::Idle; + // TODO: put lora radio into standby + status.sender().send(state.clone()); + } + Command::Transmit { config, payload } => { + let tx_config = config.or(state.config); + if let Some(_cfg) = tx_config { + let was_receiving = state.state == RadioState::Receiving; + state.state = RadioState::Transmitting; + status.sender().send(state.clone()); + + // TODO: transmit payload via lora + let _ = &mut lora; + let _ = payload; + + state.tx_count = state.tx_count.wrapping_add(1); + responses.send(Response::TxDone).await; + + state.state = if was_receiving { + RadioState::Receiving + } else { + RadioState::Idle + }; + status.sender().send(state.clone()); + } else { + responses.send(Response::Error(ErrorCode::NotConfigured)).await; + } + } + } + } +} diff --git a/src/usb/mod.rs b/src/usb/mod.rs new file mode 100644 index 00000000..22b2a9b2 --- /dev/null +++ b/src/usb/mod.rs @@ -0,0 +1,2 @@ +mod task; +pub use task::usb_task; diff --git a/src/usb/task.rs b/src/usb/task.rs new file mode 100644 index 00000000..88c3e159 --- /dev/null +++ b/src/usb/task.rs @@ -0,0 +1,26 @@ +use embassy_executor::task; + +use crate::board::UsbParts; +use crate::channel::{CommandChannel, ResponseChannel}; + +#[task] +pub async fn usb_task( + parts: UsbParts, + commands: &'static CommandChannel, + responses: &'static ResponseChannel, +) { + let _driver = parts.driver; + let _ = commands; + let _ = responses; + + // TODO: build embassy_usb CDC-ACM class from driver + // TODO: COBS-framed postcard ser/de loop: + // select! { + // bytes = cdc_read() => deserialize Command → commands.send() + // resp = responses.receive() => serialize Response → cdc_write() + // } + + loop { + embassy_time::Timer::after_secs(1).await; + } +}