diff --git a/.cargo/config.toml b/.cargo/config.toml
index d571e1f..39e0735 100644
--- a/.cargo/config.toml
+++ b/.cargo/config.toml
@@ -3,11 +3,14 @@ runner = "espflash flash --monitor -B 921600 --chip esp32s3 --log-format defmt"
linker = "/home/fromost/.rustup/toolchains/esp/xtensa-esp-elf/esp-15.2.0_20250920/xtensa-esp-elf/bin/xtensa-esp32s3-elf-gcc"
[env]
-DEFMT_LOG = "info,lora=debug,eeprom24x=debug"
+DEFMT_LOG = "info,lora=trace,eeprom24x=debug,pn532=trace"
+
DEV_EUI = "000000DD44F30FAE"
APP_KEY = "00000000000000000000000040000000"
APP_EUI = "0000000000000040"
+CARD_KEY = "00000000000000000000000000000000"
+
[build]
rustflags = [
"-C", "link-arg=-nostartfiles",
diff --git a/.idea/GitLink.xml b/.idea/GitLink.xml
new file mode 100644
index 0000000..5143819
--- /dev/null
+++ b/.idea/GitLink.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml
index b69120c..6baf2b5 100644
--- a/.idea/dictionaries/project.xml
+++ b/.idea/dictionaries/project.xml
@@ -2,7 +2,9 @@
Trng
+ felica
gpst
+ trng
\ No newline at end of file
diff --git a/.idea/lora.iml b/.idea/lora.iml
index cf84ae4..71d18d2 100644
--- a/.idea/lora.iml
+++ b/.idea/lora.iml
@@ -2,7 +2,11 @@
+
+
+
+
diff --git a/Cargo.toml b/Cargo.toml
index 78ba1b7..c53c2b7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,46 +15,57 @@ test = false
debug = []
[dependencies]
-esp-hal = { version = "~1.0", features = ["defmt", "esp32s3", "unstable"] }
-esp-rtos = { version = "0.2.0", features = [
+esp-hal = { version = "~1.1", features = ["defmt", "esp32s3", "unstable"] }
+esp-rtos = { version = "0.3.0", features = [
"defmt",
"embassy",
"esp-alloc",
"esp32s3",
] }
-esp-alloc = { version = "0.9.0", features = ["defmt"] }
-esp-backtrace = { version = "0.18.1", features = [
+esp-alloc = { version = "0.10.0", features = ["defmt"] }
+esp-backtrace = { version = "0.19.0", features = [
"defmt",
"esp32s3",
"panic-handler",
] }
-esp-println = { version = "0.16.1", features = ["defmt-espflash", "esp32s3"] }
-esp-bootloader-esp-idf = { version = "0.4.0", features = ["defmt", "esp32s3"] }
+esp-println = { version = "0.17.0", features = ["defmt-espflash", "esp32s3"] }
+esp-bootloader-esp-idf = { version = "0.5.0", features = ["defmt", "esp32s3"] }
-embassy-executor = { version = "0.9.1", features = ["defmt"] }
+embassy-executor = { version = "0.10.0", features = ["defmt"] }
embassy-time = { version = "0.5.0", features = ["defmt", "generic-queue-8", "defmt-timestamp-uptime"] }
-embassy-embedded-hal = { version = "0.5.0", features = ["defmt"] }
-embassy-sync = { version = "0.7.2", features = ["defmt"] }
+embassy-embedded-hal = { version = "0.6.0", features = ["defmt"] }
+embassy-sync = { version = "0.8.0", features = ["defmt"] }
static_cell = "2.1.1"
-defmt = "1.0.1"
-anyhow = { version = "1.0.102", default-features = false }
-thiserror = { version = "2.0.18", default-features = false }
+defmt = "1.1.1"
+anyhow = { version = "1.0.104", default-features = false }
+thiserror = { version = "2.0.20", default-features = false }
lora-phy = { version = "3.0.1", features = ["lorawan-radio"] }
lorawan-device = { version = "0.12.2", default-features = false, features = ["region-as923-1", "defmt", "default-crypto"] }
ds3231 = { version = "0.3.0", features = ["defmt"] }
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
-chrono = { version = "0.4.44", default-features = false, features = ["alloc", "defmt", "serde"] }
-hifitime = { version = "4.2.5", default-features = false, features = ["serde_derive"] }
+chrono = { version = "0.4.45", default-features = false, features = ["alloc", "defmt", "serde"] }
+hifitime = { version = "4.3.1", default-features = false, features = ["serde_derive"] }
eeprom24x = { version = "0.7.2", features = ["defmt-03"] }
postcard = { version = "1.1.3", default-features = false, features = ["defmt", "alloc", "embedded-io", "postcard-derive"] }
-serde = { version = "1.0.228", default-features = false, features = ["derive"] }
+serde = { version = "1.0.229", default-features = false, features = ["derive"] }
+atomic = "0.6.1"
+bytemuck = { version = "1.25.2", features = ["derive", "alloc_uninit", "extern_crate_alloc", "zeroable_atomics", "zeroable_maybe_uninit"] }
+pn532 = { path = "pn532", features = ["defmt", "alloc"] }
+once_cell = { version = "1.21.4", default-features = false, features = ["alloc"] }
+heapless = { version = "0.9.3", features = ["alloc", "defmt", "serde"] }
+crossbeam = { version = "0.8.4", default-features = false, features = ["alloc"] }
+[build-dependencies]
+patch-crate = "0.1.13"
+
+[package.metadata.patch]
+crates = ["lorawan-device"]
[patch.crates-io]
-lorawan-device = { path = "lorawan-device-patch" }
+lorawan-device = { path = "./target/patch/lorawan-device-0.12.2" }
[profile.dev]
# Rust debug is too slow.
@@ -69,3 +80,7 @@ incremental = false
lto = 'fat'
opt-level = 's'
overflow-checks = false
+
+[workspace]
+members = ["pn532"]
+resolver = "3"
diff --git a/build.rs b/build.rs
index 4ec39ea..c54d3c7 100644
--- a/build.rs
+++ b/build.rs
@@ -1,4 +1,7 @@
fn main() {
+ println!("cargo:rerun-if-changed=Cargo.toml");
+ patch_crate::run().expect("Failed while patching");
+
linker_be_nice();
println!("cargo:rustc-link-arg=-Tdefmt.x");
// make sure linkall.x is the last linker script (otherwise might cause problems with flip-link)
diff --git a/lorawan-device-patch/.cargo-ok b/lorawan-device-patch/.cargo-ok
deleted file mode 100644
index 5f8b795..0000000
--- a/lorawan-device-patch/.cargo-ok
+++ /dev/null
@@ -1 +0,0 @@
-{"v":1}
\ No newline at end of file
diff --git a/lorawan-device-patch/.cargo_vcs_info.json b/lorawan-device-patch/.cargo_vcs_info.json
deleted file mode 100644
index f5c7a1e..0000000
--- a/lorawan-device-patch/.cargo_vcs_info.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "git": {
- "sha1": "0efdb6b26407053c877cc6659a76c83b2dbdd952"
- },
- "path_in_vcs": "lorawan-device"
-}
\ No newline at end of file
diff --git a/lorawan-device-patch/CHANGELOG.md b/lorawan-device-patch/CHANGELOG.md
deleted file mode 100644
index 3bfc22c..0000000
--- a/lorawan-device-patch/CHANGELOG.md
+++ /dev/null
@@ -1,33 +0,0 @@
-# Changelog
-
-All notable changes to this project will be documented in this file.
-
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres
-to [Semantic Versioning](https://semver.org/).
-
-## [v0.12.1]
-
-- Allow multilple RXC frames during RXC window ([#217](https://github.com/lora-rs/lora-rs/pull/217))
-- Individually feature-gate all regions ([#216](https://github.com/lora-rs/lora-rs/pull/236))
-- Fix log macro for
- error ([commit](https://github.com/lora-rs/lora-rs/pull/256/commits/99cb10b77baf0f1c51ae97b1830a80b4873864e1))
-
-## [v0.12.0]
-
-- Fixes bug related to FCntUp and confirmed uplink ([#182](https://github.com/lora-rs/lora-rs/pull/182))
-- Extend PhyRxTx to support antenna gain and max power ([#159](https://github.com/lora-rs/lora-rs/pull/159))
-- Implement Class C functionality for async_device ([#158](https://github.com/lora-rs/lora-rs/pull/159))
-- Implement rapid subband acquisition, aka "Join Bias" for US915 & AU915
- ([#110](https://github.com/lora-rs/lora-rs/pull/110) / [#170](https://github.com/lora-rs/lora-rs/pull/170) )
-- Develops `async_device` API to provide `JoinResponse` and
- `SendResponse` (#[144](https://github.com/lora-rs/lora-rs/pull/144))
-- Develops `nb_device` API around sending a join to be consistent with
- `async_device` (#[144](https://github.com/lora-rs/lora-rs/pull/144))
-- Refactor `external-lora-phy` in `lorawan-device` as `lorawan-radio` in
- `lora-phy` ([#189](https://github.com/lora-rs/lora-rs/pull/189))
-- Add `Timer` implementation based on embassy-time ([#171](https://github.com/lora-rs/lora-rs/pull/171))
-- Use radio timeout for end of RX1 and RX2 windows; preamble detection cancels
- timeout ([#204](https://github.com/lora-rs/lora-rs/pull/204))
-- Remove `async` feature-flag as async fn in traits is stable
-
-Change tracking starting at version 0.11.0.
diff --git a/lorawan-device-patch/Cargo.toml b/lorawan-device-patch/Cargo.toml
deleted file mode 100644
index 9a8bb49..0000000
--- a/lorawan-device-patch/Cargo.toml
+++ /dev/null
@@ -1,141 +0,0 @@
-# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
-#
-# When uploading crates to the registry Cargo will automatically
-# "normalize" Cargo.toml files for maximal compatibility
-# with all versions of Cargo and also rewrite `path` dependencies
-# to registry (e.g., crates.io) dependencies.
-#
-# If you are reading this file be aware that the original Cargo.toml
-# will likely look very different (and much more reasonable).
-# See Cargo.toml.orig for the original contents.
-
-[package]
-edition = "2021"
-rust-version = "1.75"
-name = "lorawan-device"
-version = "0.12.2"
-authors = [
- "Louis Thiery ",
- "Ulf Lilleengen ",
-]
-build = false
-autobins = false
-autoexamples = false
-autotests = false
-autobenches = false
-description = "A Rust LoRaWAN device stack implementation"
-readme = "README.md"
-categories = [
- "embedded",
- "hardware-support",
- "no-std",
-]
-license = "MIT"
-repository = "https://github.com/lora-rs/lora-rs"
-
-[package.metadata.docs.rs]
-all-features = true
-rustdoc-args = [
- "--cfg",
- "docsrs",
-]
-
-[lib]
-name = "lorawan_device"
-path = "src/lib.rs"
-
-[dependencies.defmt]
-version = "0.3"
-optional = true
-
-[dependencies.document-features]
-version = "0.2.8"
-
-[dependencies.embassy-time]
-version = "0.3.0"
-optional = true
-
-[dependencies.fastrand]
-version = "2"
-default-features = false
-
-[dependencies.futures]
-version = "0.3"
-default-features = false
-
-[dependencies.generic-array]
-version = "0.14"
-
-[dependencies.heapless]
-version = "0.7"
-
-[dependencies.lora-modulation]
-version = ">=0.1.2"
-default-features = false
-
-[dependencies.lorawan]
-version = "0.9"
-default-features = false
-
-[dependencies.rand_core]
-version = "0.6"
-default-features = false
-
-[dependencies.seq-macro]
-version = "0.3.5"
-
-[dependencies.serde]
-version = "1"
-features = ["derive"]
-optional = true
-default-features = false
-
-[dev-dependencies.lazy_static]
-version = "1"
-
-[dev-dependencies.rand]
-version = "0"
-features = ["getrandom"]
-
-[dev-dependencies.tokio]
-version = "1"
-features = [
- "rt",
- "macros",
- "time",
- "sync",
-]
-
-[features]
-all-regions = [
- "region-as923-1",
- "region-as923-2",
- "region-as923-3",
- "region-as923-4",
- "region-au915",
- "region-eu433",
- "region-eu868",
- "region-in865",
- "region-us915",
-]
-default = ["all-regions"]
-default-crypto = ["lorawan/default-crypto"]
-defmt = [
- "dep:defmt",
- "lorawan/defmt",
- "lora-modulation/defmt",
-]
-embassy-time = ["dep:embassy-time"]
-region-as923-1 = []
-region-as923-2 = []
-region-as923-3 = []
-region-as923-4 = []
-region-au915 = []
-region-eu433 = []
-region-eu868 = []
-region-in865 = []
-region-us915 = []
-serde = [
- "dep:serde",
- "lorawan/serde",
-]
diff --git a/lorawan-device-patch/Cargo.toml.orig b/lorawan-device-patch/Cargo.toml.orig
deleted file mode 100644
index 014ba50..0000000
--- a/lorawan-device-patch/Cargo.toml.orig
+++ /dev/null
@@ -1,74 +0,0 @@
-[package]
-name = "lorawan-device"
-version = "0.12.2"
-authors = ["Louis Thiery ", "Ulf Lilleengen "]
-edition = "2021"
-rust-version = "1.75"
-categories = [
- "embedded",
- "hardware-support",
- "no-std",
-]
-license = "MIT"
-readme = "README.md"
-description = "A Rust LoRaWAN device stack implementation"
-repository = "https://github.com/lora-rs/lora-rs"
-
-[package.metadata.docs.rs]
-all-features = true
-rustdoc-args = ["--cfg", "docsrs"]
-
-[dependencies]
-lora-modulation = { path = "../lora-modulation", version = ">=0.1.2", default-features = false }
-lorawan = { path = "../lorawan-encoding", version = "0.9", default-features = false }
-heapless = "0.7"
-generic-array = "0.14"
-defmt = { version = "0.3", optional = true }
-fastrand = { version = "2", default-features = false }
-futures = { version = "0.3", default-features = false }
-rand_core = { version = "0.6", default-features = false }
-serde = { version = "1", default-features = false, features = ["derive"], optional = true }
-seq-macro = "0.3.5"
-document-features = "0.2.8"
-embassy-time = { version = "0.3.0", optional = true }
-
-[dev-dependencies]
-tokio = { version = "1", features = ["rt", "macros", "time", "sync"] }
-rand = { version = "0", features = ["getrandom"] }
-lazy_static = "1"
-
-[features]
-default = ["all-regions"]
-all-regions = ["region-as923-1", "region-as923-2", "region-as923-3", "region-as923-4", "region-au915", "region-eu433", "region-eu868", "region-in865", "region-us915"]
-
-## Use pure Rust implementations of [`AES`](https://docs.rs/aes/latest/aes/) and [`CMAC`](https://docs.rs/cmac/latest/cmac/) for the LoRaWAN crypto layer.
-default-crypto = ["lorawan/default-crypto"]
-
-## Use [`defmt`](https://docs.rs/defmt/latest/defmt/) for logging.
-defmt = ["dep:defmt", "lorawan/defmt", "lora-modulation/defmt"]
-
-## Provide an `async_device::Timer` impl based on `embassy-time`.
-embassy-time = ["dep:embassy-time"]
-
-## Enable [`serde`](https://docs.rs/serde/latest/serde/) serialization/deserialization for data structures.
-serde = ["dep:serde", "lorawan/serde"]
-
-## Enable support for AS923-1 region (by default all regions are enabled).
-region-as923-1 = []
-## Enable support for AS923-2 region (by default all regions are enabled).
-region-as923-2 = []
-## Enable support for AS923-3 region (by default all regions are enabled).
-region-as923-3 = []
-## Enable support for AS923-4 region (by default all regions are enabled).
-region-as923-4 = []
-## Enable support for AU915 region (by default all regions are enabled).
-region-au915 = []
-## Enable support for EU433 region (by default all regions are enabled).
-region-eu433 = []
-## Enable support for EU868 region (by default all regions are enabled).
-region-eu868 = []
-## Enable support for IN865 region (by default all regions are enabled).
-region-in865 = []
-## Enable support for US915 region (by default all regions are enabled).
-region-us915 = []
-
diff --git a/lorawan-device-patch/README.md b/lorawan-device-patch/README.md
deleted file mode 100644
index 5407048..0000000
--- a/lorawan-device-patch/README.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# lorawan-device
-
-[![Latest Version]][crates.io]
-[![Docs]][doc.rs]
-
-This is an experimental LoRaWAN device stack with both non-blocking (`nb_device`) and async (`async_device`)
-implementations. Both implementations have their respective `radio::PhyRxTx` traits that describe the radio interface
-required.
-
-Note: The `lorawan-radio` feature in the `lora-phy` crate provides `LorawanRadio` as an async implementation of
-`radio::PhyRxTx`.
-
-Both stacks share a dependency on the internal module, `mac` where LoRaWAN 1.0.x is approximately implemented:
-
-- Class A device behavior
-- Class C device behavior (async only)
-- Over-the-Air Activation (OTAA) and Activation by Personalization (ABP)
-- CFList is supported for fixed and dynamic channel plans
-- Regional support for AS923_1, AS923_2, AS923_3, AS923_4, AU915, EU868, EU433, IN865, US915 (note: regional power
- limits are not enforced ([#168](https://github.com/lora-rs/lora-rs/issues/168))
-
-**Currently, MAC commands are minimally mocked. For example, an ADRReq is responded with an ADRResp, but not much
-is actually done with the payload**.
-
-Furthermore, both async and non-blocking implementation do not implement any retries for failed joins or failed
-confirmed uplinks. It is up to the client to implement retry behavior; see the examples for more.
-
-Please see [examples](https://github.com/lora-rs/lora-rs/tree/main/examples) for usage.
-
-A public chat on LoRa/LoRaWAN topics using Rust is [here](https://matrix.to/#/#public-lora-wan-rs:matrix.org).
-
-[Latest Version]: https://img.shields.io/crates/v/lorawan-device.svg
-
-[crates.io]: https://crates.io/crates/lorawan-device
-
-[Docs]: https://docs.rs/lorawan-device/badge.svg
-
-[doc.rs]: https://docs.rs/lorawan-device
diff --git a/lorawan-device-patch/src/async_device/embassy_time.rs b/lorawan-device-patch/src/async_device/embassy_time.rs
deleted file mode 100644
index 09d3dd5..0000000
--- a/lorawan-device-patch/src/async_device/embassy_time.rs
+++ /dev/null
@@ -1,34 +0,0 @@
-use embassy_time::{Duration, Instant};
-
-use super::radio::Timer;
-
-/// A [`Timer`] implementation based on [`embassy-time`].
-pub struct EmbassyTimer {
- start: Instant,
-}
-
-impl EmbassyTimer {
- pub fn new() -> Self {
- Self { start: Instant::now() }
- }
-}
-
-impl Default for EmbassyTimer {
- fn default() -> Self {
- Self::new()
- }
-}
-
-impl Timer for EmbassyTimer {
- fn reset(&mut self) {
- self.start = Instant::now();
- }
-
- async fn at(&mut self, millis: u64) {
- embassy_time::Timer::at(self.start + Duration::from_millis(millis)).await
- }
-
- async fn delay_ms(&mut self, millis: u64) {
- embassy_time::Timer::after_millis(millis).await
- }
-}
diff --git a/lorawan-device-patch/src/async_device/mod.rs b/lorawan-device-patch/src/async_device/mod.rs
deleted file mode 100644
index aac0e75..0000000
--- a/lorawan-device-patch/src/async_device/mod.rs
+++ /dev/null
@@ -1,487 +0,0 @@
-//! LoRaWAN device which uses async-await for driving the protocol state against pin and timer events,
-//! allowing for asynchronous radio implementations. Requires the `async` feature.
-use super::mac::Mac;
-
-use super::mac::{self, Frame, Window};
-pub use super::{
- mac::{NetworkCredentials, SendData, Session},
- region::{self, Region},
- Downlink, JoinMode,
-};
-use crate::log;
-use core::marker::PhantomData;
-use futures::{future::select, future::Either, pin_mut};
-use heapless::Vec;
-use lorawan::{self, keys::CryptoFactory};
-use rand_core::RngCore;
-
-pub use crate::region::DR;
-use crate::{radio::RadioBuffer, rng};
-
-pub mod radio;
-
-#[cfg(feature = "embassy-time")]
-mod embassy_time;
-#[cfg(feature = "embassy-time")]
-pub use embassy_time::EmbassyTimer;
-
-#[cfg(test)]
-mod test;
-
-use self::radio::{RxQuality, RxStatus};
-
-/// Type representing a LoRaWAN capable device.
-///
-/// A device is bound to the following types:
-/// - R: An asynchronous radio implementation
-/// - T: An asynchronous timer implementation
-/// - C: A CryptoFactory implementation
-/// - RNG: A random number generator implementation. An external RNG may be provided, or you may use a builtin PRNG by
-/// providing a random seed
-/// - N: The size of the radio buffer. Generally, this should be set to 256 to support the largest possible LoRa frames.
-/// - D: The amount of downlinks that may be buffered. This is used to support Class C operation. See below for more.
-///
-/// Note that the const generics N and D are used to configure the size of the radio buffer and the number of downlinks
-/// that may be buffered. The defaults are 256 and 1 respectively which should be fine for Class A devices. **For Class
-/// C operation**, it is recommended to increase D to at least 2, if not 3. This is because during the RX1/RX2 windows
-/// after a Class A transmit, it is possible to receive Class C downlinks (in additional to any RX1/RX2 responses!).
-pub struct Device
-where
- R: radio::PhyRxTx + Timings,
- T: radio::Timer,
- C: CryptoFactory + Default,
- G: RngCore,
-{
- crypto: PhantomData,
- radio: R,
- rng: G,
- timer: T,
- mac: Mac,
- radio_buffer: RadioBuffer,
- downlink: Vec,
- class_c: bool,
-}
-
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[derive(Debug)]
-pub enum Error {
- Radio(R),
- Mac(mac::Error),
-}
-
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[derive(Debug)]
-pub enum SendResponse {
- DownlinkReceived(mac::FcntDown),
- SessionExpired,
- NoAck,
- RxComplete,
-}
-
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[derive(Debug)]
-pub enum JoinResponse {
- JoinSuccess,
- NoJoinAccept,
-}
-
-impl From for Error {
- fn from(e: mac::Error) -> Self {
- Error::Mac(e)
- }
-}
-
-impl Device
-where
- R: radio::PhyRxTx + Timings,
- C: CryptoFactory + Default,
- T: radio::Timer,
-{
- /// Create a new [`Device`] by providing your own random seed. Using this method, [`Device`] will internally
- /// use an algorithmic PRNG. Depending on your use case, this may or may not be faster than using your own
- /// hardware RNG.
- ///
- /// # ⚠️Warning⚠️
- ///
- /// This function must **always** be called with a new randomly generated seed! **Never** call this function more
- /// than once using the same seed. Generate the seed using a true random number generator. Using the same seed will
- /// leave you vulnerable to replay attacks.
- pub fn new_with_seed(region: region::Configuration, radio: R, timer: T, seed: u64) -> Self {
- Device::new_with_seed_and_session(region, radio, timer, seed, None)
- }
-
- /// Create a new [`Device`] by providing your own random seed. Also optionally provide your own [`Session`].
- /// Using this method, [`Device`] will internally use an algorithmic PRNG to generate random numbers. Depending on
- /// your use case, this may or may not be faster than using your own hardware RNG.
- ///
- /// # ⚠️Warning⚠️
- ///
- /// This function must **always** be called with a new randomly generated seed! **Never** call this function more
- /// than once using the same seed. Generate the seed using a true random number generator. Using the same seed will
- /// leave you vulnerable to replay attacks.
- pub fn new_with_seed_and_session(
- region: region::Configuration,
- radio: R,
- timer: T,
- seed: u64,
- session: Option,
- ) -> Self {
- let rng = rng::Prng::new(seed);
- Device::new_with_session(region, radio, timer, rng, session)
- }
-}
-
-impl Device
-where
- R: radio::PhyRxTx + Timings,
- C: CryptoFactory + Default,
- T: radio::Timer,
- G: RngCore,
-{
- /// Create a new instance of [`Device`] with a RNG external to the LoRa chip. You must provide your own RNG
- /// implementing [`RngCore`].
- ///
- /// See also [`new_with_seed`](Device::new_with_seed) to let [`Device`] use a builtin PRNG by providing a random
- /// seed.
- pub fn new(region: region::Configuration, radio: R, timer: T, rng: G) -> Self {
- Device::new_with_session(region, radio, timer, rng, None)
- }
-
- /// Create a new [`Device`] and provide an optional [`Session`].
- pub fn new_with_session(
- region: region::Configuration,
- radio: R,
- timer: T,
- rng: G,
- session: Option,
- ) -> Self {
- let mut mac = Mac::new(region, R::MAX_RADIO_POWER, R::ANTENNA_GAIN);
- if let Some(session) = session {
- mac.set_session(session);
- }
- Self {
- crypto: PhantomData,
- radio,
- rng,
- mac,
- radio_buffer: RadioBuffer::new(),
- timer,
- downlink: Vec::new(),
- class_c: false,
- }
- }
-
- /// Enables Class C behavior. Note that Class C downlinks are not possible until a confirmed
- /// uplink is sent to the LNS.
-
- pub fn enable_class_c(&mut self) {
- self.class_c = true;
- }
-
- /// Disables Class C behavior. Note that an uplink must be set for the radio to disable
- /// Class C listen.
- pub fn disable_class_c(&mut self) {
- self.class_c = false;
- }
-
- pub fn get_session(&mut self) -> Option<&Session> {
- self.mac.get_session()
- }
-
- pub fn get_region(&mut self) -> ®ion::Configuration {
- &self.mac.region
- }
-
- pub fn get_radio(&mut self) -> &R {
- &self.radio
- }
-
- pub fn get_mut_radio(&mut self) -> &mut R {
- &mut self.radio
- }
-
- /// Retrieve the current data rate being used by this device.
- pub fn get_datarate(&mut self) -> DR {
- self.mac.configuration.data_rate
- }
-
- /// Set the data rate being used by this device. This overrides the region default.
- pub fn set_datarate(&mut self, datarate: DR) {
- self.mac.configuration.data_rate = datarate;
- }
-
- /// Join the LoRaWAN network asynchronously. The returned future completes when
- /// the LoRaWAN network has been joined successfully, or an error has occurred.
- ///
- /// Repeatedly calling join using OTAA will result in a new LoRaWAN session to be created.
- ///
- /// Note that for a Class C enabled device, you must repeatedly send *confirmed* uplink until
- /// LoRaWAN Network Server (LNS) confirmation after joining.
- pub async fn join(&mut self, join_mode: &JoinMode) -> Result> {
- match join_mode {
- JoinMode::OTAA { deveui, appeui, appkey } => {
- let (tx_config, _) = self.mac.join_otaa::(
- &mut self.rng,
- NetworkCredentials::new(*appeui, *deveui, *appkey),
- &mut self.radio_buffer,
- );
-
- // Transmit the join payload
- let ms = self
- .radio
- .tx(tx_config, self.radio_buffer.as_ref_for_read())
- .await
- .map_err(Error::Radio)?;
-
- // Receive join response within RX window
- self.timer.reset();
- Ok(self.rx_downlink(&Frame::Join, ms).await?.try_into()?)
- }
- JoinMode::ABP { newskey, appskey, devaddr } => {
- self.mac.join_abp(*newskey, *appskey, *devaddr);
- Ok(JoinResponse::JoinSuccess)
- }
- }
- }
-
- /// Send data on a given port with the expected confirmation. If downlink data is provided, the
- /// data is copied into the provided byte slice.
- ///
- /// The returned future completes when the data have been sent successfully and downlink data,
- /// if any, is available by calling take_downlink. Response::DownlinkReceived indicates a
- /// downlink is available.
- ///
- /// In Class C mode, it is possible to get one or more downlinks and `Reponse::DownlinkReceived`
- /// maybe not even be indicated. It is recommended to call `take_downlink` after `send` until
- /// it returns `None`.
- pub async fn send(
- &mut self,
- data: &[u8],
- fport: u8,
- confirmed: bool,
- ) -> Result> {
- // Prepare transmission buffer
- let (tx_config, _fcnt_up) = self.mac.send::(
- &mut self.rng,
- &mut self.radio_buffer,
- &SendData { data, fport, confirmed },
- )?;
- // Transmit our data packet
- let ms = self
- .radio
- .tx(tx_config, self.radio_buffer.as_ref_for_read())
- .await
- .map_err(Error::Radio)?;
-
- // Wait for received data within window
- self.timer.reset();
- Ok(self.rx_downlink(&Frame::Data, ms).await?.try_into()?)
- }
-
- /// Take the downlink data from the device. This is typically called after a
- /// `Response::DownlinkReceived` is returned from `send`. This call consumes the downlink
- /// data. If no downlink data is available, `None` is returned.
- pub fn take_downlink(&mut self) -> Option {
- self.downlink.pop()
- }
-
- async fn window_complete(&mut self) -> Result<(), Error> {
- if self.class_c {
- let rf_config = self.mac.get_rxc_config();
- self.radio.setup_rx(rf_config).await.map_err(Error::Radio)
- } else {
- self.radio.low_power().await.map_err(Error::Radio)
- }
- }
-
- async fn between_windows(
- &mut self,
- duration: u32,
- ) -> Result, Error> {
- if !self.class_c {
- self.radio.low_power().await.map_err(Error::Radio)?;
- self.timer.at(duration.into()).await;
- return Ok(None);
- }
-
- #[allow(unused)]
- enum RxcWindowResponse + Sized + Unpin> {
- Rx(usize, RxQuality, F),
- Timeout(u32),
- }
-
- /// RXC window listen until timeout
- async fn rxc_listen_until_timeout(
- radio: &mut R,
- rx_buf: &mut RadioBuffer,
- window_duration: u32,
- timeout_fut: F,
- ) -> RxcWindowResponse
- where
- F: futures::Future + Sized + Unpin,
- R: radio::PhyRxTx + Timings,
- {
- let rx_fut = radio.rx_continuous(rx_buf.as_mut());
- pin_mut!(rx_fut);
- // Wait until either a RF frame is received or the timeout future fires
- match select(rx_fut, timeout_fut).await {
- Either::Left((r, timeout_fut)) => match r {
- Ok((sz, q)) => RxcWindowResponse::Rx(sz, q, timeout_fut),
- // Ignore errors or timeouts and wait until the RX2 window is ready.
- // Setting timeout to 0 ensures that `window_duration != rx2_start_delay`
- _ => {
- timeout_fut.await;
- RxcWindowResponse::Timeout(0)
- }
- },
- // Timeout! Prepare for the next window.
- Either::Right(_) => RxcWindowResponse::Timeout(window_duration),
- }
- }
-
- // Class C listen while waiting for the window
- let rx_config = self.mac.get_rxc_config();
- log::debug!("Configuring RXC window with config {}.", rx_config);
- self.radio.setup_rx(rx_config).await.map_err(Error::Radio)?;
- let mut response = None;
- let timeout_fut = self.timer.at(duration.into());
- pin_mut!(timeout_fut);
- let mut maybe_timeout_fut = Some(timeout_fut);
-
- // Keep processing RF frames until the timeout fires
- while let Some(timeout_fut) = maybe_timeout_fut.take() {
- match rxc_listen_until_timeout(
- &mut self.radio,
- &mut self.radio_buffer,
- duration,
- timeout_fut,
- )
- .await
- {
- RxcWindowResponse::Rx(sz, _, timeout_fut) => {
- log::debug!("RXC window received {} bytes.", sz);
- self.radio_buffer.set_pos(sz);
- match self
- .mac
- .handle_rxc::(&mut self.radio_buffer, &mut self.downlink)?
- {
- mac::Response::NoUpdate => {
- log::debug!("RXC frame was invalid.");
- self.radio_buffer.clear();
- // we preserve the timeout
- maybe_timeout_fut = Some(timeout_fut);
- }
- r => {
- log::debug!("Valid RXC frame received.");
- self.radio_buffer.clear();
- response = Some(r);
- // more than one downlink may be received so we preserve the timeout
- maybe_timeout_fut = Some(timeout_fut);
- }
- }
- }
- RxcWindowResponse::Timeout(_) => return Ok(response),
- };
- }
- Ok(response)
- }
-
- /// Attempt to receive data within RX1 and RX2 windows. This function will populate the
- /// provided buffer with data if received.
- async fn rx_downlink(
- &mut self,
- frame: &Frame,
- window_delay: u32,
- ) -> Result> {
- self.radio_buffer.clear();
-
- let rx1_start_delay = self.mac.get_rx_delay(frame, &Window::_1) + window_delay
- - self.radio.get_rx_window_lead_time_ms();
-
- log::debug!("Starting RX1 in {} ms.", rx1_start_delay);
- // sleep or RXC
- let _ = self.between_windows(rx1_start_delay).await?;
-
- // RX1
- let rx_config =
- self.mac.get_rx_config(self.radio.get_rx_window_buffer(), frame, &Window::_1);
- log::debug!("Configuring RX1 window with config {}.", rx_config);
- self.radio.setup_rx(rx_config).await.map_err(Error::Radio)?;
-
- if let Some(response) = self.rx_listen().await? {
- log::debug!("RX1 received {}", response);
- return Ok(response);
- }
-
- let rx2_start_delay = self.mac.get_rx_delay(frame, &Window::_2) + window_delay
- - self.radio.get_rx_window_lead_time_ms();
- log::debug!("RX1 did not receive anything. Awaiting RX2 for {} ms.", rx2_start_delay);
- // sleep or RXC
- let _ = self.between_windows(rx2_start_delay).await?;
-
- // RX2
- let rx_config =
- self.mac.get_rx_config(self.radio.get_rx_window_buffer(), frame, &Window::_2);
- log::debug!("Configuring RX2 window with config {}.", rx_config);
- self.radio.setup_rx(rx_config).await.map_err(Error::Radio)?;
-
- if let Some(response) = self.rx_listen().await? {
- log::debug!("RX2 received {}", response);
- return Ok(response);
- }
- log::debug!("RX2 did not receive anything.");
- Ok(self.mac.rx2_complete())
- }
-
- async fn rx_listen(&mut self) -> Result, Error> {
- let response =
- match self.radio.rx_single(self.radio_buffer.as_mut()).await.map_err(Error::Radio)? {
- RxStatus::Rx(s, _q) => {
- self.radio_buffer.set_pos(s);
- match self.mac.handle_rx::(&mut self.radio_buffer, &mut self.downlink)
- {
- mac::Response::NoUpdate => None,
- r => Some(r),
- }
- }
- RxStatus::RxTimeout => None,
- };
- self.radio_buffer.clear();
- self.window_complete().await?;
- Ok(response)
- }
-
- /// When not involved in sending and RX1/RX2 windows, a class C configured device will be
- /// listening to RXC frames. The caller is expected to be awaiting this message at all times.
- pub async fn rxc_listen(&mut self) -> Result> {
- loop {
- let (sz, _rx_quality) =
- self.radio.rx_continuous(self.radio_buffer.as_mut()).await.map_err(Error::Radio)?;
- self.radio_buffer.set_pos(sz);
- match self.mac.handle_rxc::(&mut self.radio_buffer, &mut self.downlink)? {
- mac::Response::NoUpdate => {
- self.radio_buffer.clear();
- }
- r => {
- self.radio_buffer.clear();
- return Ok(r);
- }
- }
- }
- }
-}
-
-/// Allows to fine-tune the beginning and end of the receive windows for a specific board and runtime.
-pub trait Timings {
- /// How many milliseconds before the RX window should the SPI transaction start?
- /// This value needs to account for the time it takes to wake up the radio and start the SPI transaction, as
- /// well as any non-deterministic delays in the system.
- fn get_rx_window_lead_time_ms(&self) -> u32;
-
- /// Explicitly set the amount of milliseconds to listen before the window starts. By default, the pessimistic assumption
- /// of `Self::get_rx_window_lead_time_ms` will be used. If you override, be sure that: `Self::get_rx_window_buffer
- /// < Self::get_rx_window_lead_time_ms`.
- fn get_rx_window_buffer(&self) -> u32 {
- self.get_rx_window_lead_time_ms()
- }
-}
diff --git a/lorawan-device-patch/src/async_device/radio.rs b/lorawan-device-patch/src/async_device/radio.rs
deleted file mode 100644
index 945c8a1..0000000
--- a/lorawan-device-patch/src/async_device/radio.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-pub use crate::radio::{RfConfig, RxConfig, RxMode, RxQuality, TxConfig};
-
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct Error(pub E);
-
-impl From> for super::Error {
- fn from(radio_error: Error) -> super::Error {
- super::Error::Radio(radio_error.0)
- }
-}
-
-pub enum RxStatus {
- Rx(usize, RxQuality),
- RxTimeout,
-}
-
-/// An asynchronous timer that allows the state machine to await
-/// between RX windows.
-#[allow(async_fn_in_trait)]
-pub trait Timer {
- fn reset(&mut self);
-
- /// Wait until millis milliseconds after reset has passed
- async fn at(&mut self, millis: u64);
-
- /// Delay for millis milliseconds
- async fn delay_ms(&mut self, millis: u64);
-}
-
-/// An asynchronous radio implementation that can transmit and receive data.
-#[allow(async_fn_in_trait)]
-pub trait PhyRxTx: Sized {
- #[cfg(feature = "defmt")]
- type PhyError: defmt::Format;
-
- #[cfg(not(feature = "defmt"))]
- type PhyError;
-
- /// Board-specific antenna gain and power loss in dBi.
- const ANTENNA_GAIN: i8 = 0;
-
- /// Maximum power (dBm) that the radio is able to output. When preparing instructions for radio,
- /// the value of maximum power will be used as an upper bound.
- const MAX_RADIO_POWER: u8;
-
- /// Transmit data buffer with the given transceiver configuration. The returned future
- /// should only complete once data have been transmitted.
- async fn tx(&mut self, config: TxConfig, buf: &[u8]) -> Result;
-
- /// Configures the radio to receive data. This future should not actually await the data itself.
- async fn setup_rx(&mut self, config: RxConfig) -> Result<(), Self::PhyError>;
-
- /// Receive data into the provided buffer with the given transceiver configuration. The returned
- /// future should only complete when RX data has been received. Furthermore, it should be
- /// possible to await the future again without settings up the receive config again.
- async fn rx_continuous(
- &mut self,
- rx_buf: &mut [u8],
- ) -> Result<(usize, RxQuality), Self::PhyError>;
-
- /// Receive data into the provided buffer with the given transceiver configuration. The returned
- /// future should complete when RX data has been received or when the timeout has expired.
- async fn rx_single(&mut self, buf: &mut [u8]) -> Result;
-
- /// Puts the radio into a low-power mode
- async fn low_power(&mut self) -> Result<(), Self::PhyError> {
- Ok(())
- }
-}
diff --git a/lorawan-device-patch/src/async_device/test/mod.rs b/lorawan-device-patch/src/async_device/test/mod.rs
deleted file mode 100644
index e90ba2d..0000000
--- a/lorawan-device-patch/src/async_device/test/mod.rs
+++ /dev/null
@@ -1,329 +0,0 @@
-use super::*;
-use crate::{
- radio::{RxQuality, TxConfig},
- region,
- test_util::*,
-};
-use lorawan::default_crypto::DefaultFactory;
-use std::sync::Arc;
-use tokio::sync::Mutex;
-
-mod timer;
-use timer::TestTimer;
-
-mod radio;
-use radio::TestRadio;
-
-mod util;
-use util::{setup, setup_with_session, setup_with_session_class_c};
-
-type Device =
-crate::async_device::Device;
-
-#[tokio::test]
-async fn test_join_rx1() {
- let (radio, timer, mut async_device) = setup();
- // Run the device
- let async_device =
- tokio::spawn(async move { async_device.join(&get_otaa_credentials()).await });
-
- // Trigger beginning of RX1
- timer.fire_most_recent().await;
- // Trigger handling of JoinAccept
- radio.handle_rxtx(handle_join_request::<3>).await;
-
- // Await the device to return and verify state
- if let Ok(JoinResponse::JoinSuccess) = async_device.await.unwrap() {
- assert_eq!(1, timer.get_armed_count().await);
- } else {
- panic!();
- }
-}
-
-#[tokio::test]
-async fn test_join_rx2() {
- let (radio, timer, mut async_device) = setup();
- // Run the device
- let async_device =
- tokio::spawn(async move { async_device.join(&get_otaa_credentials()).await });
-
- // Trigger beginning of RX1
- timer.fire_most_recent().await;
- // Trigger end of RX1
- radio.handle_timeout().await;
- // Trigger start of RX2
- timer.fire_most_recent().await;
- // Pass the join request handler
- radio.handle_rxtx(handle_join_request::<4>).await;
-
- // Await the device to return and verify state
- if async_device.await.unwrap().is_ok() {
- assert_eq!(2, timer.get_armed_count().await);
- } else {
- panic!();
- }
-}
-
-#[tokio::test]
-async fn test_no_join_accept() {
- let (radio, timer, mut async_device) = setup();
- // Run the device
- let async_device =
- tokio::spawn(async move { async_device.join(&get_otaa_credentials()).await });
-
- // Trigger beginning of RX1
- timer.fire_most_recent().await;
- // Trigger end of RX1
- radio.handle_timeout().await;
- // Trigger start of RX2
- timer.fire_most_recent().await;
- // Trigger end of RX2
- radio.handle_timeout().await;
-
- // Await the device to return and verify state
- let response = async_device.await.unwrap();
- if let Ok(JoinResponse::NoJoinAccept) = response {
- assert_eq!(2, timer.get_armed_count().await);
- } else {
- panic!("Unexpected response: {response:?}");
- }
-}
-
-#[tokio::test]
-async fn test_unconfirmed_uplink_no_downlink() {
- let (radio, timer, mut async_device) = setup_with_session();
- let send_await_complete = Arc::new(Mutex::new(false));
-
- // Run the device
- let complete = send_await_complete.clone();
- let async_device = tokio::spawn(async move {
- let response = async_device.send(&[1, 2, 3], 3, false).await;
-
- let mut complete = complete.lock().await;
- *complete = true;
- response
- });
- // Trigger beginning of RX1
- timer.fire_most_recent().await;
- assert!(!*send_await_complete.lock().await);
- // Trigger end of RX1
- radio.handle_timeout().await;
- // Trigger start of RX2
- timer.fire_most_recent().await;
- assert!(!*send_await_complete.lock().await);
- // Trigger end of RX2
- radio.handle_timeout().await;
-
- match async_device.await.unwrap() {
- Ok(SendResponse::RxComplete) => (),
- _ => panic!(),
- }
- assert!(*send_await_complete.lock().await);
-}
-
-#[tokio::test]
-async fn test_confirmed_uplink_no_ack() {
- let (radio, timer, mut async_device) = setup_with_session();
- let send_await_complete = Arc::new(Mutex::new(false));
-
- // Run the device
- let complete = send_await_complete.clone();
- let async_device = tokio::spawn(async move {
- let response = async_device.send(&[1, 2, 3], 3, true).await;
-
- let mut complete = complete.lock().await;
- *complete = true;
- response
- });
- // Trigger beginning of RX1
- timer.fire_most_recent().await;
- assert!(!*send_await_complete.lock().await);
- // Trigger end of RX1
- radio.handle_timeout().await;
- // Trigger start of RX2
- timer.fire_most_recent().await;
- assert!(!*send_await_complete.lock().await);
- // Trigger end of RX1
- radio.handle_timeout().await;
-
- match async_device.await.unwrap() {
- Ok(SendResponse::NoAck) => (),
- _ => panic!(),
- }
- assert!(*send_await_complete.lock().await);
-}
-
-#[tokio::test]
-async fn test_confirmed_uplink_with_ack_rx1() {
- let (radio, timer, mut async_device) = setup_with_session();
- let send_await_complete = Arc::new(Mutex::new(false));
-
- // Run the device
- let complete = send_await_complete.clone();
- let async_device = tokio::spawn(async move {
- let response = async_device.send(&[1, 2, 3], 3, true).await;
-
- let mut complete = complete.lock().await;
- *complete = true;
- response
- });
- // Trigger beginning of RX1
- timer.fire_most_recent().await;
- assert!(!*send_await_complete.lock().await);
-
- // Send a downlink with confirmation
- radio.handle_rxtx(handle_data_uplink_with_link_adr_req::<0, 0>).await;
- match async_device.await.unwrap() {
- Ok(SendResponse::DownlinkReceived(_)) => (),
- _ => {
- panic!()
- }
- }
-}
-
-#[tokio::test]
-async fn test_confirmed_uplink_with_ack_rx2() {
- let (radio, timer, mut async_device) = setup_with_session();
- let send_await_complete = Arc::new(Mutex::new(false));
-
- // Run the device
- let complete = send_await_complete.clone();
- let async_device = tokio::spawn(async move {
- let response = async_device.send(&[1, 2, 3], 3, true).await;
-
- let mut complete = complete.lock().await;
- *complete = true;
- response
- });
- // Trigger beginning of RX1
- timer.fire_most_recent().await;
- assert!(!*send_await_complete.lock().await);
- // Trigger end of RX1
- radio.handle_timeout().await;
- assert!(!*send_await_complete.lock().await);
- // Trigger start of RX2
- timer.fire_most_recent().await;
-
- // Send a downlink confirmation
- radio.handle_rxtx(handle_data_uplink_with_link_adr_req::<0, 0>).await;
-
- match async_device.await.unwrap() {
- Ok(SendResponse::DownlinkReceived(_)) => (),
- _ => {
- panic!()
- }
- }
-}
-
-#[tokio::test]
-async fn test_link_adr_ans() {
- let (radio, timer, mut async_device) = setup_with_session();
- let send_await_complete = Arc::new(Mutex::new(false));
-
- // Run the device
- let complete = send_await_complete.clone();
- let async_device = tokio::spawn(async move {
- async_device.send(&[1, 2, 3], 3, true).await.unwrap();
- {
- let mut complete = complete.lock().await;
- *complete = true;
- }
- async_device.send(&[1, 2, 3], 3, true).await
- });
- // Trigger beginning of RX1
- timer.fire_most_recent().await;
- // Send a downlink with confirmation
- radio.handle_rxtx(handle_data_uplink_with_link_adr_req::<0, 0>).await;
- tokio::time::sleep(tokio::time::Duration::from_millis(15)).await;
- assert!(*send_await_complete.lock().await);
- // at this point, the device thread should be sending the second frame
- // Trigger beginning of RX1
- timer.fire_most_recent().await;
- // Send a downlink with confirmation
- radio.handle_rxtx(handle_data_uplink_with_link_adr_ans).await;
- match async_device.await.unwrap() {
- Ok(SendResponse::DownlinkReceived(_)) => (),
- _ => {
- panic!()
- }
- }
-}
-
-#[tokio::test]
-async fn test_class_c_data_before_rx1() {
- let (radio, timer, mut async_device) = setup_with_session_class_c().await;
- // Run the device
- let task = tokio::spawn(async move {
- let response = async_device.send(&[1, 2, 3], 3, true).await;
- (async_device, response)
- });
-
- // send first downlink before RX1
- radio.handle_rxtx(class_c_downlink::<1>).await;
- // Trigger beginning of RX1
- timer.fire_most_recent().await;
- // We expect FCntUp 1 up since the test util for Class C setup sends first frame
- // We set FcntDown to 2, since ACK to setup (1) and Class C downlink above (2)
- radio.handle_rxtx(handle_data_uplink_with_link_adr_req::<1, 2>).await;
- let (mut device, response) = task.await.unwrap();
- match response {
- Ok(SendResponse::DownlinkReceived(_)) => (),
- _ => {
- panic!()
- }
- }
- let _ = device.take_downlink().unwrap();
- let _ = device.take_downlink().unwrap();
-}
-
-#[tokio::test]
-async fn test_class_c_data_before_rx2() {
- let (radio, timer, mut async_device) = setup_with_session_class_c().await;
- // Run the device
- let task = tokio::spawn(async move {
- let response = async_device.send(&[1, 2, 3], 3, true).await;
- (async_device, response)
- });
-
- // send first downlink before RX1
- // Trigger beginning of RX1
- timer.fire_most_recent().await;
- // Trigger end of RX1
- radio.handle_timeout().await;
-
- radio.handle_rxtx(class_c_downlink::<1>).await;
- // Trigger beginning of RX2
- timer.fire_most_recent().await;
- // We expect FCntUp 1 up since the test util for Class C setup sends first frame
- // We set FcntDown to 2, since ACK to setup (1) and Class C downlink above (2)
- radio.handle_rxtx(handle_data_uplink_with_link_adr_req::<1, 2>).await;
- let (mut device, response) = task.await.unwrap();
- match response {
- Ok(SendResponse::DownlinkReceived(_)) => (),
- _ => {
- panic!()
- }
- }
- let _ = device.take_downlink().unwrap();
- let _ = device.take_downlink().unwrap();
-}
-
-#[tokio::test]
-async fn test_class_c_async_down() {
- let (radio, _timer, mut async_device) = setup_with_session_class_c().await;
- // Run the device
- let task = tokio::spawn(async move {
- let response = async_device.rxc_listen().await;
- (async_device, response)
- });
-
- radio.handle_rxtx(class_c_downlink::<1>).await;
- let (mut device, response) = task.await.unwrap();
- match response {
- Ok(mac::Response::DownlinkReceived(_)) => (),
- _ => {
- panic!()
- }
- }
- let _ = device.take_downlink().unwrap();
-}
diff --git a/lorawan-device-patch/src/async_device/test/radio.rs b/lorawan-device-patch/src/async_device/test/radio.rs
deleted file mode 100644
index 827a408..0000000
--- a/lorawan-device-patch/src/async_device/test/radio.rs
+++ /dev/null
@@ -1,111 +0,0 @@
-use crate::async_device::radio::{PhyRxTx, RxConfig, RxStatus};
-use std::sync::Arc;
-use tokio::{
- sync::{mpsc, Mutex},
- time,
-};
-impl TestRadio {
- pub fn new() -> (RadioChannel, Self) {
- let (tx, rx) = mpsc::channel(2);
- let last_uplink = Arc::new(Mutex::new(None));
- (
- RadioChannel { tx, last_uplink: last_uplink.clone() },
- Self { rx, last_uplink, current_config: None },
- )
- }
-}
-
-#[derive(Debug)]
-enum Msg {
- RxTx(RxTxHandler),
- Timeout,
-}
-
-pub struct TestRadio {
- current_config: Option,
- last_uplink: Arc>>,
- rx: mpsc::Receiver,
-}
-
-impl PhyRxTx for TestRadio {
- type PhyError = &'static str;
-
- const MAX_RADIO_POWER: u8 = 26;
-
- const ANTENNA_GAIN: i8 = 0;
-
- async fn tx(&mut self, config: TxConfig, buffer: &[u8]) -> Result {
- let length = buffer.len();
- // stash the uplink, to be consumed by channel or by rx handler
- let mut last_uplink = self.last_uplink.lock().await;
- *last_uplink = Some(Uplink::new(buffer, config).map_err(|_| "Parse error")?);
- Ok(length as u32)
- }
-
- async fn setup_rx(&mut self, config: RxConfig) -> Result<(), Self::PhyError> {
- self.current_config = Some(config);
- Ok(())
- }
-
- async fn rx_continuous(
- &mut self,
- rx_buf: &mut [u8],
- ) -> Result<(usize, RxQuality), Self::PhyError> {
- let msg = self.rx.recv().await.unwrap();
- match msg {
- Msg::RxTx(handler) => {
- let last_uplink = self.last_uplink.lock().await;
- // a quick yield to let timer arm
- time::sleep(time::Duration::from_millis(5)).await;
- if let Some(config) = &self.current_config {
- let length = handler(last_uplink.clone(), config.rf, rx_buf);
- Ok((length, RxQuality::new(-80, 0)))
- } else {
- panic!("Trying to rx before settings config!")
- }
- }
- Msg::Timeout => Err("Unexpected Timeout"),
- }
- }
- async fn rx_single(&mut self, rx_buf: &mut [u8]) -> Result {
- let msg = self.rx.recv().await.unwrap();
- match msg {
- Msg::RxTx(handler) => {
- let last_uplink = self.last_uplink.lock().await;
- // a quick yield to let timer arm
- time::sleep(time::Duration::from_millis(5)).await;
- if let Some(config) = &self.current_config {
- let length = handler(last_uplink.clone(), config.rf, rx_buf);
- Ok(RxStatus::Rx(length, RxQuality::new(-80, 0)))
- } else {
- panic!("Trying to rx before settings config!")
- }
- }
- Msg::Timeout => Ok(RxStatus::RxTimeout),
- }
- }
-}
-
-impl Timings for TestRadio {
- fn get_rx_window_lead_time_ms(&self) -> u32 {
- 10
- }
-}
-
-/// A channel for the test fixture to trigger fires and to check calls.
-pub struct RadioChannel {
- #[allow(unused)]
- last_uplink: Arc>>,
- tx: mpsc::Sender,
-}
-
-impl RadioChannel {
- pub async fn handle_rxtx(&self, handler: RxTxHandler) {
- tokio::time::sleep(time::Duration::from_millis(5)).await;
- self.tx.send(Msg::RxTx(handler)).await.unwrap();
- }
- pub async fn handle_timeout(&self) {
- tokio::time::sleep(time::Duration::from_millis(5)).await;
- self.tx.send(Msg::Timeout).await.unwrap();
- }
-}
diff --git a/lorawan-device-patch/src/async_device/test/timer.rs b/lorawan-device-patch/src/async_device/test/timer.rs
deleted file mode 100644
index 1c450a2..0000000
--- a/lorawan-device-patch/src/async_device/test/timer.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-use crate::async_device::radio::Timer;
-use std::{collections::HashMap, sync::Arc};
-use tokio::sync::{mpsc, Mutex};
-
-impl TestTimer {
- pub fn new() -> (TimerChannel, Self) {
- let tx = Arc::new(Mutex::new(HashMap::new()));
- let armed_count = Arc::new(Mutex::new(0));
- (
- TimerChannel { tx: tx.clone(), armed_count: armed_count.clone() },
- Self { tx, armed_count },
- )
- }
-}
-
-pub struct TestTimer {
- armed_count: Arc>,
- tx: Arc>>>,
-}
-
-impl TestTimer {
- async fn create_channel_and_await(&mut self) {
- let (tx, mut rx) = mpsc::channel(1);
- {
- *self.armed_count.lock().await += 1;
- let mut tx_map = self.tx.lock().await;
- tx_map.insert(*self.armed_count.lock().await, tx);
- }
- rx.recv().await;
- }
-}
-
-impl Timer for TestTimer {
- fn reset(&mut self) {}
-
- async fn at(&mut self, _millis: u64) {
- self.create_channel_and_await().await;
- }
-
- async fn delay_ms(&mut self, _millis: u64) {
- self.create_channel_and_await().await;
- }
-}
-
-/// A channel for the test fixture to trigger fires and to check calls.
-pub struct TimerChannel {
- armed_count: Arc>,
- tx: Arc>>>,
-}
-
-impl TimerChannel {
- pub async fn fire_most_recent(&self) {
- tokio::time::sleep(tokio::time::Duration::from_millis(5)).await;
- let mut tx_map = self.tx.lock().await;
- let armed_count = *self.armed_count.lock().await;
- let tx = tx_map.remove(&armed_count).unwrap();
- tx.send(()).await.unwrap();
- }
-
- #[allow(unused)]
- pub async fn confirm_dropped_timer(&self, index: usize) {
- tokio::time::sleep(tokio::time::Duration::from_millis(5)).await;
- let mut tx_map = self.tx.lock().await;
- let tx = tx_map.remove(&index).unwrap();
- if tx.try_send(()).is_ok() {
- panic!("Timer was not dropped");
- }
- }
-
- pub async fn get_armed_count(&self) -> usize {
- *self.armed_count.lock().await
- }
-}
diff --git a/lorawan-device-patch/src/async_device/test/util.rs b/lorawan-device-patch/src/async_device/test/util.rs
deleted file mode 100644
index c758271..0000000
--- a/lorawan-device-patch/src/async_device/test/util.rs
+++ /dev/null
@@ -1,57 +0,0 @@
-use super::{get_dev_addr, get_key, region, Device, SendResponse};
-
-use crate::mac::Session;
-use crate::test_util::handle_class_c_uplink_after_join;
-use crate::{AppSKey, NewSKey};
-
-fn setup_internal(session_data: Option) -> (RadioChannel, TimerChannel, Device) {
- let (radio_channel, mock_radio) = TestRadio::new();
- let (timer_channel, mock_timer) = TestTimer::new();
- let region = region::US915::default();
- let async_device = Device::new_with_session(
- region.into(),
- mock_radio,
- mock_timer,
- rand::rngs::OsRng,
- session_data,
- );
- (radio_channel, timer_channel, async_device)
-}
-
-pub fn setup_with_session() -> (RadioChannel, TimerChannel, Device) {
- setup_internal(Some(Session {
- newskey: NewSKey::from(get_key()),
- appskey: AppSKey::from(get_key()),
- devaddr: get_dev_addr(),
- fcnt_up: 0,
- fcnt_down: 0,
- confirmed: false,
- uplink: Default::default(),
- }))
-}
-
-pub async fn setup_with_session_class_c() -> (RadioChannel, TimerChannel, Device) {
- let (radio, timer, mut async_device) = setup_with_session();
- async_device.enable_class_c();
- // Run the device
- let task = tokio::spawn(async move {
- let response = async_device.send(&[3, 2, 1], 3, false).await;
- (async_device, response)
- });
- // timeout the first sends RX windows which enables class C
- timer.fire_most_recent().await;
- radio.handle_rxtx(handle_class_c_uplink_after_join).await;
-
- let (device, response) = task.await.unwrap();
- match response {
- Ok(SendResponse::DownlinkReceived(0)) => (),
- _ => {
- panic!()
- }
- }
- (radio, timer, device)
-}
-
-pub fn setup() -> (RadioChannel, TimerChannel, Device) {
- setup_internal(None)
-}
diff --git a/lorawan-device-patch/src/lib.rs b/lorawan-device-patch/src/lib.rs
deleted file mode 100644
index 539a0ed..0000000
--- a/lorawan-device-patch/src/lib.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-#![cfg_attr(not(test), no_std)]
-#![cfg_attr(docsrs, feature(doc_cfg))]
-
-//! ## Feature flags
-#![doc = document_features::document_features!(feature_label = r#"{feature} "#)]
-#![doc = include_str!("../README.md")]
-
-use core::default::Default;
-use heapless::Vec;
-
-mod radio;
-
-pub mod mac;
-use mac::NetworkCredentials;
-
-pub mod region;
-pub use region::Region;
-
-#[cfg(test)]
-mod test_util;
-
-pub mod async_device;
-
-pub mod nb_device;
-use nb_device::state::State;
-
-use core::marker::PhantomData;
-#[cfg(feature = "default-crypto")]
-#[cfg_attr(docsrs, doc(cfg(feature = "default-crypto")))]
-pub use lorawan::default_crypto;
-pub use lorawan::{
- keys::{AppEui, AppKey, AppSKey, CryptoFactory, DevEui, NewSKey},
- parser::DevAddr,
-};
-
-pub use rand_core::RngCore;
-mod rng;
-pub use rng::Prng;
-
-mod log;
-
-/// Provides the application payload and FPort of a downlink message.
-pub struct Downlink {
- pub data: Vec,
- pub fport: u8,
-}
-
-#[cfg(feature = "defmt")]
-impl defmt::Format for Downlink {
- fn format(&self, f: defmt::Formatter) {
- defmt::write!(f, "Downlink {{ fport: {}, data: ", self.fport, );
-
- for byte in self.data.iter() {
- defmt::write!(f, "{:02x}", byte);
- }
- defmt::write!(f, " }}")
- }
-}
-
-/// Allows to fine-tune the beginning and end of the receive windows for a specific board.
-pub trait Timings {
- /// The offset in milliseconds from the beginning of the receive windows. For example, settings this to 100
- /// tell the LoRaWAN stack to begin configuring the receive window 100 ms before the window needs to start.
- fn get_rx_window_offset_ms(&self) -> i32;
-
- /// How long to leave the receive window open in milliseconds. For example, if offset was set to 100 and duration
- /// was set to 200, the window would be open 100 ms before and close 100 ms after the target time.
- fn get_rx_window_duration_ms(&self) -> u32;
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-/// Join the network using either OTAA or ABP.
-pub enum JoinMode {
- OTAA { deveui: DevEui, appeui: AppEui, appkey: AppKey },
- ABP { newskey: NewSKey, appskey: AppSKey, devaddr: DevAddr<[u8; 4]> },
-}
diff --git a/lorawan-device-patch/src/log.rs b/lorawan-device-patch/src/log.rs
deleted file mode 100644
index 3fafd3e..0000000
--- a/lorawan-device-patch/src/log.rs
+++ /dev/null
@@ -1,35 +0,0 @@
-#![allow(unused_macros)]
-#![allow(unused)]
-
-#[cfg(feature = "defmt")]
-macro_rules! llog {
- (trace, $($arg:expr),*) => { defmt::trace!($($arg),*) };
- (debug, $($arg:expr),*) => { defmt::debug!($($arg),*) };
- (info, $($arg:expr),*) => { defmt::info!($($arg),*) };
- (error, $($arg:expr),*) => { defmt::error!($($arg),*) };
-}
-
-#[cfg(not(feature = "defmt"))]
-macro_rules! llog {
- ($level:ident, $($arg:expr),*) => {{ $( let _ = $arg; )* }}
-}
-pub(crate) use llog;
-
-macro_rules! trace {
- ($($arg:expr),*) => (log::llog!(trace, $($arg),*));
-}
-pub(crate) use trace;
-
-macro_rules! debug {
- ($($arg:expr),*) => (log::llog!(debug, $($arg),*));
-}
-pub(crate) use debug;
-macro_rules! info {
- ($($arg:expr),*) => (log::llog!(info, $($arg),*));
-}
-pub(crate) use info;
-
-macro_rules! error {
- ($($arg:expr),*) => (log::llog!(error, $($arg),*));
-}
-pub(crate) use error;
diff --git a/lorawan-device-patch/src/mac/mod.rs b/lorawan-device-patch/src/mac/mod.rs
deleted file mode 100644
index 00da5b4..0000000
--- a/lorawan-device-patch/src/mac/mod.rs
+++ /dev/null
@@ -1,368 +0,0 @@
-//! LoRaWAN MAC layer implementation written as a non-async state machine (leveraged by `async_device` and `nb_device`).
-//! Manages state internally while providing client with transmit and receive frequencies, while writing to and
-//! decrypting from send and receive buffers.
-
-use crate::{
- radio::{self, RadioBuffer, RfConfig, RxConfig, RxMode},
- region, AppSKey, Downlink, NewSKey,
-};
-use heapless::Vec;
-use lorawan::{self, keys::CryptoFactory};
-use lorawan::{maccommands::DownlinkMacCommand, parser::DevAddr};
-
-pub type FcntDown = u32;
-pub type FcntUp = u32;
-
-mod session;
-use rand_core::RngCore;
-pub use session::{Session, SessionKeys};
-
-mod otaa;
-pub use otaa::NetworkCredentials;
-
-use crate::async_device;
-use crate::nb_device;
-
-pub(crate) mod uplink;
-
-#[derive(Copy, Clone, Debug)]
-pub(crate) enum Frame {
- Join,
- Data,
-}
-
-#[derive(Copy, Clone, Debug)]
-pub(crate) enum Window {
- _1,
- _2,
-}
-
-#[derive(Debug, PartialEq, Clone, Copy)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-/// LoRaWAN Session and Network Configurations
-pub struct Configuration {
- pub(crate) data_rate: region::DR,
- rx1_delay: u32,
- join_accept_delay1: u32,
- join_accept_delay2: u32,
-}
-
-impl Configuration {
- fn handle_downlink_macs(
- &mut self,
- region: &mut region::Configuration,
- uplink: &mut uplink::Uplink,
- cmds: lorawan::maccommands::MacCommandIterator,
- ) {
- use uplink::MacAnsTrait;
- for cmd in cmds {
- match cmd {
- DownlinkMacCommand::LinkADRReq(payload) => {
- // we ignore DR and TxPwr
- region.set_channel_mask(
- payload.redundancy().channel_mask_control(),
- payload.channel_mask(),
- );
- uplink.adr_ans.add();
- }
- DownlinkMacCommand::RXTimingSetupReq(payload) => {
- self.rx1_delay = del_to_delay_ms(payload.delay());
- uplink.ack_rx_delay();
- }
- _ => (),
- }
- }
- }
-}
-
-pub(crate) struct Mac {
- pub configuration: Configuration,
- pub region: region::Configuration,
- board_eirp: BoardEirp,
- state: State,
-}
-
-struct BoardEirp {
- max_power: u8,
- antenna_gain: i8,
-}
-
-#[allow(clippy::large_enum_variant)]
-enum State {
- Joined(Session),
- Otaa(otaa::Otaa),
- Unjoined,
-}
-
-#[derive(Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub enum Error {
- NotJoined,
- InvalidResponse(Response),
-}
-
-pub struct SendData<'a> {
- pub data: &'a [u8],
- pub fport: u8,
- pub confirmed: bool,
-}
-
-pub(crate) type Result = core::result::Result;
-
-impl Mac {
- pub(crate) fn new(region: region::Configuration, max_power: u8, antenna_gain: i8) -> Self {
- let data_rate = region.get_default_datarate();
- Self {
- board_eirp: BoardEirp { max_power, antenna_gain },
- region,
- state: State::Unjoined,
- configuration: Configuration {
- data_rate,
- rx1_delay: region::constants::RECEIVE_DELAY1,
- join_accept_delay1: region::constants::JOIN_ACCEPT_DELAY1,
- join_accept_delay2: region::constants::JOIN_ACCEPT_DELAY2,
- },
- }
- }
-
- /// Prepare the radio buffer with transmitting a join request frame and provides the radio
- /// configuration for the transmission.
- pub(crate) fn join_otaa(
- &mut self,
- rng: &mut RNG,
- credentials: NetworkCredentials,
- buf: &mut RadioBuffer,
- ) -> (radio::TxConfig, u16) {
- let mut otaa = otaa::Otaa::new(credentials);
- let dev_nonce = otaa.prepare_buffer::(rng, buf);
- self.state = State::Otaa(otaa);
- let mut tx_config =
- self.region.create_tx_config(rng, self.configuration.data_rate, &Frame::Join);
- tx_config.adjust_power(self.board_eirp.max_power, self.board_eirp.antenna_gain);
- (tx_config, dev_nonce)
- }
-
- /// Join via ABP. This does not transmit a join request frame, but instead sets the session.
- pub(crate) fn join_abp(
- &mut self,
- newskey: NewSKey,
- appskey: AppSKey,
- devaddr: DevAddr<[u8; 4]>,
- ) {
- self.state = State::Joined(Session::new(newskey, appskey, devaddr));
- }
-
- /// Join via ABP. This does not transmit a join request frame, but instead sets the session.
- pub(crate) fn set_session(&mut self, session: Session) {
- self.state = State::Joined(session);
- }
-
- /// Prepare the radio buffer for transmitting a data frame and provide the radio configuration
- /// for the transmission. Returns an error if the device is not joined.
- pub(crate) fn send(
- &mut self,
- rng: &mut RNG,
- buf: &mut RadioBuffer,
- send_data: &SendData,
- ) -> Result<(radio::TxConfig, FcntUp)> {
- let fcnt = match &mut self.state {
- State::Joined(ref mut session) => Ok(session.prepare_buffer::(send_data, buf)),
- State::Otaa(_) => Err(Error::NotJoined),
- State::Unjoined => Err(Error::NotJoined),
- }?;
- let mut tx_config =
- self.region.create_tx_config(rng, self.configuration.data_rate, &Frame::Data);
- tx_config.adjust_power(self.board_eirp.max_power, self.board_eirp.antenna_gain);
- Ok((tx_config, fcnt))
- }
-
- pub(crate) fn get_rx_delay(&self, frame: &Frame, window: &Window) -> u32 {
- match frame {
- Frame::Join => match window {
- Window::_1 => self.configuration.join_accept_delay1,
- Window::_2 => self.configuration.join_accept_delay2,
- },
- Frame::Data => match window {
- Window::_1 => self.configuration.rx1_delay,
- // RECEIVE_DELAY2 is not configurable. LoRaWAN 1.0.3 Section 5.7:
- // "The second reception slot opens one second after the first reception slot."
- Window::_2 => self.configuration.rx1_delay + 1000,
- },
- }
- }
-
- /// Gets the radio configuration and timing for a given frame type and window.
- pub(crate) fn get_rx_parameters_legacy(
- &mut self,
- frame: &Frame,
- window: &Window,
- ) -> (RfConfig, u32) {
- (
- self.region.get_rx_config(self.configuration.data_rate, frame, window),
- self.get_rx_delay(frame, window),
- )
- }
-
- /// Handles a received RF frame. Returns None is unparseable, fails decryption, or fails MIC
- /// verification. Upon successful join, provides Response::JoinSuccess. Upon successful data
- /// rx, provides Response::DownlinkReceived. User must take the downlink from vec for
- /// application data.
- pub(crate) fn handle_rx(
- &mut self,
- buf: &mut RadioBuffer,
- dl: &mut Vec,
- ) -> Response {
- match &mut self.state {
- State::Joined(ref mut session) => session.handle_rx::(
- &mut self.region,
- &mut self.configuration,
- buf,
- dl,
- false,
- ),
- State::Otaa(ref mut otaa) => {
- if let Some(session) =
- otaa.handle_rx::(&mut self.region, &mut self.configuration, buf)
- {
- self.state = State::Joined(session);
- Response::JoinSuccess
- } else {
- Response::NoUpdate
- }
- }
- State::Unjoined => Response::NoUpdate,
- }
- }
-
- /// Handles a received RF frame during RXC window. Returns None if unparseable, fails decryption,
- /// or fails MIC verification. Upon successful data rx, provides Response::DownlinkReceived.
- /// User must later call `take_downlink()` on the device to get the application data.
- pub(crate) fn handle_rxc(
- &mut self,
- buf: &mut RadioBuffer,
- dl: &mut Vec,
- ) -> Result {
- match &mut self.state {
- State::Joined(ref mut session) => Ok(session.handle_rx::(
- &mut self.region,
- &mut self.configuration,
- buf,
- dl,
- true,
- )),
- State::Otaa(_) => Err(Error::NotJoined),
- State::Unjoined => Err(Error::NotJoined),
- }
- }
-
- pub(crate) fn rx2_complete(&mut self) -> Response {
- match &mut self.state {
- State::Joined(session) => session.rx2_complete(),
- State::Otaa(otaa) => otaa.rx2_complete(),
- State::Unjoined => Response::NoUpdate,
- }
- }
-
- pub(crate) fn get_session_keys(&self) -> Option {
- match &self.state {
- State::Joined(session) => session.get_session_keys(),
- State::Otaa(_) => None,
- State::Unjoined => None,
- }
- }
-
- pub(crate) fn get_session(&self) -> Option<&Session> {
- match &self.state {
- State::Joined(session) => Some(session),
- State::Otaa(_) => None,
- State::Unjoined => None,
- }
- }
-
- pub(crate) fn is_joined(&self) -> bool {
- matches!(&self.state, State::Joined(_))
- }
-
- pub(crate) fn get_fcnt_up(&self) -> Option {
- match &self.state {
- State::Joined(session) => Some(session.fcnt_up),
- State::Otaa(_) => None,
- State::Unjoined => None,
- }
- }
-
- pub(crate) fn get_rx_config(&self, buffer_ms: u32, frame: &Frame, window: &Window) -> RxConfig {
- RxConfig {
- rf: self.region.get_rx_config(self.configuration.data_rate, frame, window),
- mode: RxMode::Single { ms: buffer_ms },
- }
- }
-
- pub(crate) fn get_rxc_config(&self) -> RxConfig {
- RxConfig {
- rf: self.region.get_rxc_config(self.configuration.data_rate),
- mode: RxMode::Continuous,
- }
- }
-}
-
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[derive(Debug)]
-pub enum Response {
- NoAck,
- SessionExpired,
- DownlinkReceived(FcntDown),
- NoJoinAccept,
- JoinSuccess,
- NoUpdate,
- RxComplete,
-}
-
-impl From for nb_device::Response {
- fn from(r: Response) -> Self {
- match r {
- Response::SessionExpired => nb_device::Response::SessionExpired,
- Response::DownlinkReceived(fcnt) => nb_device::Response::DownlinkReceived(fcnt),
- Response::NoAck => nb_device::Response::NoAck,
- Response::NoJoinAccept => nb_device::Response::NoJoinAccept,
- Response::JoinSuccess => nb_device::Response::JoinSuccess,
- Response::NoUpdate => nb_device::Response::NoUpdate,
- Response::RxComplete => nb_device::Response::RxComplete,
- }
- }
-}
-
-impl TryFrom for async_device::SendResponse {
- type Error = Error;
-
- fn try_from(r: Response) -> Result {
- match r {
- Response::SessionExpired => Ok(async_device::SendResponse::SessionExpired),
- Response::DownlinkReceived(fcnt) => {
- Ok(async_device::SendResponse::DownlinkReceived(fcnt))
- }
- Response::NoAck => Ok(async_device::SendResponse::NoAck),
- Response::RxComplete => Ok(async_device::SendResponse::RxComplete),
- r => Err(Error::InvalidResponse(r)),
- }
- }
-}
-
-impl TryFrom for async_device::JoinResponse {
- type Error = Error;
-
- fn try_from(r: Response) -> Result {
- match r {
- Response::NoJoinAccept => Ok(async_device::JoinResponse::NoJoinAccept),
- Response::JoinSuccess => Ok(async_device::JoinResponse::JoinSuccess),
- r => Err(Error::InvalidResponse(r)),
- }
- }
-}
-
-fn del_to_delay_ms(del: u8) -> u32 {
- match del {
- 2..=15 => del as u32 * 1000,
- _ => region::constants::RECEIVE_DELAY1,
- }
-}
diff --git a/lorawan-device-patch/src/mac/otaa.rs b/lorawan-device-patch/src/mac/otaa.rs
deleted file mode 100644
index 730fd18..0000000
--- a/lorawan-device-patch/src/mac/otaa.rs
+++ /dev/null
@@ -1,92 +0,0 @@
-use super::{del_to_delay_ms, session::Session, Response};
-use crate::radio::RadioBuffer;
-use crate::region::Configuration;
-use crate::{AppEui, AppKey, DevEui};
-use lorawan::keys::CryptoFactory;
-use lorawan::{
- creator::JoinRequestCreator,
- parser::{parse_with_factory as lorawan_parse, *},
-};
-use rand_core::RngCore;
-
-pub(crate) type DevNonce = lorawan::parser::DevNonce<[u8; 2]>;
-
-pub(crate) struct Otaa {
- dev_nonce: DevNonce,
- network_credentials: NetworkCredentials,
-}
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[derive(Debug, Clone)]
-pub struct NetworkCredentials {
- deveui: DevEui,
- appeui: AppEui,
- appkey: AppKey,
-}
-
-impl Otaa {
- pub fn new(network_credentials: NetworkCredentials) -> Self {
- Self { dev_nonce: DevNonce::from([0, 0]), network_credentials }
- }
-
- /// Prepare a join request to be sent. This populates the radio buffer with the request to be
- /// sent, and returns the radio config to use for transmitting.
- pub(crate) fn prepare_buffer(
- &mut self,
- rng: &mut G,
- buf: &mut RadioBuffer,
- ) -> u16 {
- self.dev_nonce = DevNonce::from(rng.next_u32() as u16);
- buf.clear();
- let mut phy: JoinRequestCreator<[u8; 23], C> = JoinRequestCreator::default();
- phy.set_app_eui(self.network_credentials.appeui)
- .set_dev_eui(self.network_credentials.deveui)
- .set_dev_nonce(self.dev_nonce);
- let vec = phy.build(&self.network_credentials.appkey);
- buf.extend_from_slice(vec).unwrap();
- u16::from(self.dev_nonce)
- }
-
- pub(crate) fn handle_rx(
- &mut self,
- region: &mut Configuration,
- configuration: &mut super::Configuration,
- rx: &mut RadioBuffer,
- ) -> Option {
- if let Ok(PhyPayload::JoinAccept(JoinAcceptPayload::Encrypted(encrypted))) =
- lorawan_parse(rx.as_mut_for_read(), C::default())
- {
- let decrypt = encrypted.decrypt(&self.network_credentials.appkey);
- region.process_join_accept(&decrypt);
- configuration.rx1_delay = del_to_delay_ms(decrypt.rx_delay());
- if decrypt.validate_mic(&self.network_credentials.appkey) {
- return Some(Session::derive_new(
- &decrypt,
- self.dev_nonce,
- &self.network_credentials,
- ));
- }
- }
- None
- }
-
- pub(crate) fn rx2_complete(&mut self) -> Response {
- Response::NoJoinAccept
- }
-}
-
-impl NetworkCredentials {
- pub fn new(appeui: AppEui, deveui: DevEui, appkey: AppKey) -> Self {
- Self { deveui, appeui, appkey }
- }
- pub fn appeui(&self) -> &AppEui {
- &self.appeui
- }
-
- pub fn deveui(&self) -> &DevEui {
- &self.deveui
- }
-
- pub fn appkey(&self) -> &AppKey {
- &self.appkey
- }
-}
diff --git a/lorawan-device-patch/src/mac/session.rs b/lorawan-device-patch/src/mac/session.rs
deleted file mode 100644
index d946a1f..0000000
--- a/lorawan-device-patch/src/mac/session.rs
+++ /dev/null
@@ -1,222 +0,0 @@
-use crate::{region, AppSKey, Downlink, NewSKey};
-use heapless::Vec;
-use lorawan::keys::CryptoFactory;
-use lorawan::maccommands::{DownlinkMacCommand, MacCommandIterator};
-use lorawan::{
- creator::DataPayloadCreator,
- maccommands::SerializableMacCommand,
- parser::{parse_with_factory as lorawan_parse, *},
- parser::{DecryptedJoinAcceptPayload, DevAddr},
-};
-
-use generic_array::{typenum::U256, GenericArray};
-
-use crate::radio::RadioBuffer;
-
-use super::{
- otaa::{DevNonce, NetworkCredentials},
- uplink, FcntUp, Response, SendData,
-};
-
-#[derive(Clone, Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub struct Session {
- pub uplink: uplink::Uplink,
- pub confirmed: bool,
- pub newskey: NewSKey,
- pub appskey: AppSKey,
- pub devaddr: DevAddr<[u8; 4]>,
- pub fcnt_up: u32,
- pub fcnt_down: u32,
-}
-
-#[derive(Clone, Debug)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-pub struct SessionKeys {
- pub newskey: NewSKey,
- pub appskey: AppSKey,
- pub devaddr: DevAddr<[u8; 4]>,
-}
-
-impl From for SessionKeys {
- fn from(session: Session) -> Self {
- Self { newskey: session.newskey, appskey: session.appskey, devaddr: session.devaddr }
- }
-}
-
-impl Session {
- pub fn derive_new, F: CryptoFactory>(
- decrypt: &DecryptedJoinAcceptPayload,
- devnonce: DevNonce,
- credentials: &NetworkCredentials,
- ) -> Self {
- Self::new(
- decrypt.derive_newskey(&devnonce, credentials.appkey()),
- decrypt.derive_appskey(&devnonce, credentials.appkey()),
- DevAddr::new([
- decrypt.dev_addr().as_ref()[0],
- decrypt.dev_addr().as_ref()[1],
- decrypt.dev_addr().as_ref()[2],
- decrypt.dev_addr().as_ref()[3],
- ])
- .unwrap(),
- )
- }
-
- pub fn new(newskey: NewSKey, appskey: AppSKey, devaddr: DevAddr<[u8; 4]>) -> Self {
- Self {
- newskey,
- appskey,
- devaddr,
- confirmed: false,
- fcnt_down: 0,
- fcnt_up: 0,
- uplink: uplink::Uplink::default(),
- }
- }
-
- pub fn devaddr(&self) -> &DevAddr<[u8; 4]> {
- &self.devaddr
- }
- pub fn appskey(&self) -> &AppSKey {
- &self.appskey
- }
- pub fn newskey(&self) -> &NewSKey {
- &self.newskey
- }
-
- pub fn get_session_keys(&self) -> Option {
- Some(SessionKeys { newskey: self.newskey, appskey: self.appskey, devaddr: self.devaddr })
- }
-}
-
-impl Session {
- pub(crate) fn handle_rx(
- &mut self,
- region: &mut region::Configuration,
- configuration: &mut super::Configuration,
- rx: &mut RadioBuffer,
- dl: &mut Vec,
- ignore_mac: bool,
- ) -> Response {
- if let Ok(PhyPayload::Data(DataPayload::Encrypted(encrypted_data))) =
- lorawan_parse(rx.as_mut_for_read(), C::default())
- {
- if self.devaddr() == &encrypted_data.fhdr().dev_addr() {
- let fcnt = encrypted_data.fhdr().fcnt() as u32;
- let confirmed = encrypted_data.is_confirmed();
- if encrypted_data.validate_mic(self.newskey().inner(), fcnt)
- && (fcnt > self.fcnt_down || fcnt == 0)
- {
- self.fcnt_down = fcnt;
- // We can safely unwrap here because we already validated the MIC
- let decrypted = encrypted_data
- .decrypt(
- Some(self.newskey().inner()),
- Some(self.appskey().inner()),
- self.fcnt_down,
- )
- .unwrap();
-
- if !ignore_mac {
- // MAC commands may be in the FHDR or the FRMPayload
- configuration.handle_downlink_macs(
- region,
- &mut self.uplink,
- MacCommandIterator::::new(decrypted.fhdr().data()),
- );
- if let FRMPayload::MACCommands(mac_cmds) = decrypted.frm_payload() {
- configuration.handle_downlink_macs(
- region,
- &mut self.uplink,
- MacCommandIterator::::new(mac_cmds.data()),
- );
- }
- }
-
- if confirmed {
- self.uplink.set_downlink_confirmation();
- }
-
- return if self.fcnt_up == 0xFFFF_FFFF {
- // if the FCnt is used up, the session has expired
- Response::SessionExpired
- } else {
- // we can always increment fcnt_up when we receive a downlink
- self.fcnt_up += 1;
- if let (Some(fport), FRMPayload::Data(data)) =
- (decrypted.f_port(), decrypted.frm_payload())
- {
- // heapless Vec from slice fails only if slice is too large.
- // A data FRM payload will never exceed 256 bytes.
- let data = Vec::from_slice(data).unwrap();
- // TODO: propagate error type when heapless vec is full?
- let _ = dl.push(Downlink { data, fport });
- }
- Response::DownlinkReceived(fcnt)
- };
- }
- }
- }
- Response::NoUpdate
- }
-
- pub(crate) fn rx2_complete(&mut self) -> Response {
- // Until we handle NbTrans, there is no case where we should not increment FCntUp.
- if self.fcnt_up == 0xFFFF_FFFF {
- // if the FCnt is used up, the session has expired
- return Response::SessionExpired;
- } else {
- self.fcnt_up += 1;
- }
- if self.confirmed {
- Response::NoAck
- } else {
- Response::RxComplete
- }
- }
-
- pub(crate) fn prepare_buffer(
- &mut self,
- data: &SendData,
- tx_buffer: &mut RadioBuffer,
- ) -> FcntUp {
- tx_buffer.clear();
- let fcnt = self.fcnt_up;
- let mut phy: DataPayloadCreator, C> = DataPayloadCreator::default();
-
- let mut fctrl = FCtrl(0x0, true);
- if self.uplink.confirms_downlink() {
- fctrl.set_ack();
- self.uplink.clear_downlink_confirmation();
- }
-
- self.confirmed = data.confirmed;
-
- phy.set_confirmed(data.confirmed)
- .set_fctrl(&fctrl)
- .set_f_port(data.fport)
- .set_dev_addr(self.devaddr)
- .set_fcnt(fcnt);
-
- let mut cmds = Vec::new();
- self.uplink.get_cmds(&mut cmds);
- let mut dyn_cmds: Vec<&dyn SerializableMacCommand, 8> = Vec::new();
-
- for cmd in &cmds {
- if let Err(_e) = dyn_cmds.push(cmd) {
- panic!("dyn_cmds too small compared to cmds")
- }
- }
-
- match phy.build(data.data, dyn_cmds.as_slice(), &self.newskey, &self.appskey) {
- Ok(packet) => {
- tx_buffer.clear();
- tx_buffer.extend_from_slice(packet).unwrap();
- }
- Err(e) => panic!("Error assembling packet! {:?} ", e),
- }
- fcnt
- }
-}
diff --git a/lorawan-device-patch/src/mac/uplink.rs b/lorawan-device-patch/src/mac/uplink.rs
deleted file mode 100644
index 0b4cb3c..0000000
--- a/lorawan-device-patch/src/mac/uplink.rs
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
-This a temporary design where flags will be left about desired MAC uplinks by the stack
-During Uplink assembly, this struct will be inquired to drive construction
- */
-use heapless::Vec;
-use lorawan::maccommands::{LinkADRAnsPayload, RXTimingSetupAnsPayload, UplinkMacCommand};
-
-#[derive(Default, Debug, Clone)]
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub struct Uplink {
- pub adr_ans: AdrAns,
- pub rx_delay_ans: RxDelayAns,
- confirmed: bool,
-}
-
-// multiple AdrAns may happen per downlink
-// so we aggregate how many AdrAns are required
-type AdrAns = u8;
-// only one RxDelayReq will happen
-// so we only need to implement this as a bool
-type RxDelayAns = bool;
-
-//work around for E0390
-pub(crate) trait MacAnsTrait {
- fn add(&mut self);
- fn clear(&mut self);
- // we use a uint instead of bool because some ADR responses
- // require a counter for state.
- // eg: ADR Req may be batched in a single downlink and require
- // multiple ADR Ans in the next uplink
- fn get(&self) -> u8;
-}
-
-impl MacAnsTrait for AdrAns {
- fn add(&mut self) {
- *self += 1;
- }
- fn clear(&mut self) {
- *self = 0;
- }
- fn get(&self) -> u8 {
- *self
- }
-}
-
-impl MacAnsTrait for RxDelayAns {
- fn add(&mut self) {
- *self = true;
- }
- fn clear(&mut self) {
- *self = false;
- }
- fn get(&self) -> u8 {
- u8::from(*self)
- }
-}
-
-impl Uplink {
- pub fn set_downlink_confirmation(&mut self) {
- self.confirmed = true;
- }
-
- pub fn clear_downlink_confirmation(&mut self) {
- self.confirmed = false;
- }
- pub fn confirms_downlink(&self) -> bool {
- self.confirmed
- }
-
- pub fn ack_rx_delay(&mut self) {
- self.rx_delay_ans.add();
- }
-
- pub fn get_cmds(&mut self, macs: &mut Vec) {
- for _ in 0..self.adr_ans.get() {
- macs.push(UplinkMacCommand::LinkADRAns(LinkADRAnsPayload::new(&[0x07]).unwrap()))
- .unwrap();
- }
- self.adr_ans.clear();
-
- if self.rx_delay_ans.get() != 0 {
- macs.push(UplinkMacCommand::RXTimingSetupAns(RXTimingSetupAnsPayload::new(&[])))
- .unwrap();
- }
- self.rx_delay_ans.clear();
- }
-}
diff --git a/lorawan-device-patch/src/nb_device/mod.rs b/lorawan-device-patch/src/nb_device/mod.rs
deleted file mode 100644
index 8e58574..0000000
--- a/lorawan-device-patch/src/nb_device/mod.rs
+++ /dev/null
@@ -1,173 +0,0 @@
-//! A non-blocking LoRaWAN device implementation which uses an explicitly defined state machine
-//! for driving the protocol state against pin and timer events. Depends on a non-async radio
-//! implementation.
-use super::radio::RadioBuffer;
-use super::*;
-use crate::nb_device::radio::PhyRxTx;
-use mac::{Mac, SendData};
-
-pub(crate) mod state;
-
-pub mod radio;
-#[cfg(test)]
-mod test;
-
-type TimestampMs = u32;
-
-pub struct Device
-where
- R: PhyRxTx + Timings,
- C: CryptoFactory + Default,
- RNG: RngCore,
-{
- state: State,
- shared: Shared,
- crypto: PhantomData,
-}
-
-impl Device
-where
- R: PhyRxTx + Timings,
- C: CryptoFactory + Default,
- RNG: RngCore,
-{
- pub fn new(region: region::Configuration, radio: R, rng: RNG) -> Device {
- Device {
- crypto: PhantomData,
- state: State::default(),
- shared: Shared {
- radio,
- rng,
- tx_buffer: RadioBuffer::new(),
- mac: Mac::new(region, R::MAX_RADIO_POWER, R::ANTENNA_GAIN),
- downlink: Vec::new(),
- },
- }
- }
-
- pub fn join(&mut self, join_mode: JoinMode) -> Result> {
- match join_mode {
- JoinMode::OTAA { deveui, appeui, appkey } => {
- self.handle_event(Event::Join(NetworkCredentials::new(appeui, deveui, appkey)))
- }
- JoinMode::ABP { devaddr, appskey, newskey } => {
- self.shared.mac.join_abp(newskey, appskey, devaddr);
- Ok(Response::JoinSuccess)
- }
- }
- }
-
- pub fn get_radio(&mut self) -> &mut R {
- &mut self.shared.radio
- }
-
- pub fn get_datarate(&mut self) -> region::DR {
- self.shared.mac.configuration.data_rate
- }
-
- pub fn set_datarate(&mut self, datarate: region::DR) {
- self.shared.mac.configuration.data_rate = datarate
- }
-
- pub fn ready_to_send_data(&self) -> bool {
- matches!(&self.state, State::Idle(_)) && self.shared.mac.is_joined()
- }
-
- pub fn send(&mut self, data: &[u8], fport: u8, confirmed: bool) -> Result> {
- self.handle_event(Event::SendDataRequest(SendData { data, fport, confirmed }))
- }
-
- pub fn get_fcnt_up(&self) -> Option {
- self.shared.mac.get_fcnt_up()
- }
-
- pub fn get_session(&self) -> Option<&mac::Session> {
- self.shared.mac.get_session()
- }
-
- pub fn set_session(&mut self, s: mac::Session) {
- self.shared.mac.set_session(s)
- }
-
- pub fn get_session_keys(&self) -> Option {
- self.shared.mac.get_session_keys()
- }
-
- pub fn take_downlink(&mut self) -> Option {
- self.shared.downlink.pop()
- }
-
- pub fn handle_event(&mut self, event: Event) -> Result> {
- let (new_state, result) = self.state.handle_event::(
- &mut self.shared.mac,
- &mut self.shared.radio,
- &mut self.shared.rng,
- &mut self.shared.tx_buffer,
- &mut self.shared.downlink,
- event,
- );
- self.state = new_state;
- result
- }
-}
-
-pub(crate) struct Shared {
- pub(crate) radio: R,
- pub(crate) rng: RNG,
- pub(crate) tx_buffer: RadioBuffer,
- pub(crate) mac: Mac,
- pub(crate) downlink: Vec,
-}
-
-#[derive(Debug)]
-pub enum Response {
- NoUpdate,
- TimeoutRequest(TimestampMs),
- JoinRequestSending,
- JoinSuccess,
- NoJoinAccept,
- UplinkSending(mac::FcntUp),
- DownlinkReceived(mac::FcntDown),
- NoAck,
- ReadyToSend,
- SessionExpired,
- RxComplete,
-}
-
-#[derive(Debug)]
-pub enum Error {
- Radio(R::PhyError),
- State(state::Error),
- Mac(mac::Error),
-}
-
-impl From for Error {
- fn from(mac_error: mac::Error) -> Error {
- Error::Mac(mac_error)
- }
-}
-
-pub enum Event<'a, R>
-where
- R: PhyRxTx,
-{
- Join(NetworkCredentials),
- SendDataRequest(SendData<'a>),
- RadioEvent(radio::Event<'a, R>),
- TimeoutFired,
-}
-
-impl<'a, R> core::fmt::Debug for Event<'a, R>
-where
- R: PhyRxTx,
-{
- fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
- let event = match self {
- Event::Join(_) => "Join",
- Event::SendDataRequest(_) => "SendDataRequest",
- Event::RadioEvent(_) => "RadioEvent",
- Event::TimeoutFired => "TimeoutFired",
- };
- write!(f, "lorawan_device::Event::{event}")
- }
-}
diff --git a/lorawan-device-patch/src/nb_device/radio.rs b/lorawan-device-patch/src/nb_device/radio.rs
deleted file mode 100644
index d7c9c8f..0000000
--- a/lorawan-device-patch/src/nb_device/radio.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-use super::TimestampMs;
-pub use crate::radio::*;
-pub use ::lora_modulation::{Bandwidth, CodingRate, SpreadingFactor};
-
-#[derive(Debug)]
-pub enum Event<'a, R>
-where
- R: PhyRxTx,
-{
- TxRequest(TxConfig, &'a [u8]),
- RxRequest(RfConfig),
- CancelRx,
- Phy(R::PhyEvent),
-}
-
-#[derive(Debug)]
-pub enum Response
-where
- R: PhyRxTx,
-{
- Idle,
- Txing,
- Rxing,
- TxDone(TimestampMs),
- RxDone(RxQuality),
- Phy(R::PhyResponse),
-}
-
-use core::fmt;
-
-pub trait PhyRxTx {
- type PhyEvent: fmt::Debug;
- type PhyError: fmt::Debug;
- type PhyResponse: fmt::Debug;
-
- /// Board-specific antenna gain and power loss in dBi.
- const ANTENNA_GAIN: i8 = 0;
-
- /// Maximum power (dBm) that the radio is able to output. When preparing instructions for radio,
- /// the value of maximum power will be used as an upper bound.
- const MAX_RADIO_POWER: u8;
-
- fn get_mut_radio(&mut self) -> &mut Self;
-
- // we require mutability so we may decrypt in place
- fn get_received_packet(&mut self) -> &mut [u8];
- fn handle_event(&mut self, event: Event) -> Result, Self::PhyError>
- where
- Self: Sized;
-}
diff --git a/lorawan-device-patch/src/nb_device/state.rs b/lorawan-device-patch/src/nb_device/state.rs
deleted file mode 100644
index 92ff26a..0000000
--- a/lorawan-device-patch/src/nb_device/state.rs
+++ /dev/null
@@ -1,411 +0,0 @@
-/*
-
-This state machine creates a non-blocking and no-async structure for coordinating radio events with
-the mac state.
-
-In this implementation, each state (eg: "Idle", "Txing") is a struct. When an event is handled
-(eg: "SendData", "TxComplete"), a transition may or may not occur. Regardless, a response is always
-given to the client, and those are indicated here in parenthesis (ie: "(Sending)"). If nothing is
-indicated in this diagram, the response is "NoUpdate".
-
-O
-│
-╔═══════════════════╗ ╔════════════════════╗
-║ Idle ║ ║ Txing ║
-║ SendData ║ if async (Sending) ║ ║
-║ ─────────╫───────────────┬───────────────>║ ║
-║ ║ │ ║ TxComplete ║
-╚═══════════════════╝ │ ║ ──────────╫───┐
- ^ │ ╚════════════════════╝ │
- │ │ │
- │ │ │
-┌─────┘ ╔═══════════════════╗ │ ╔════════════════════╗ │
-│ ║ WaitingForRx ║ │ ║ WaitingForRxWindow ║ │
-│ ║ ╔═════════════╗ ║ │else sync ║ ╔═════════════╗ ║ │
-│ ║ ║ RxWindow1 ║ ║ └──────────╫─>║ RxWindow1 ║<──╫─────────┘
-│(DataDown)║ ║ Rx ║ ║ (TimeoutReq)║ ║ ║ ║(TimeoutReq)
-├──────────╫─╫─────── ║ ║(TimeoutReq) ║ ║ Timeout ║ ║
-│ ║ ║ Timeout ║<──╫───────────────╫──╫──────────── ║ ║
-│ ║ ║ ─────────╫───╫──┐ ║ ╚═════════════╝ ║
-│ ║ ╚═════════════╝ ║ │ ║ ║
-│ ║ ╔═════════════╗ ║ │(TimeoutReq)║ ╔═════════════╗ ║
-│(DataDown)║ ║ RxWindow2 ║ ║ └────────────╫─> ║ RxWindow2 ║ ║
-├──────────╫─╫──┐ Rx ║ ║ ║ ║ ║ ║
-│ ║ ║ └─── ║ ║(TimeoutReq) ║ ║ Timeout ║ ║
-│ if conf ║ ║ Timeout ║<──╫───────────────╫───╫──────────── ║ ║
-│ (NoACK) ║ ║ ┌──────── ║ ║ ║ ╚═════════════╝ ║
-└──────────╫─╫───┘ ║ ║ ║ ║
-else(Ready)║ ╚═════════════╝ ║ ║ ║
- ╚═══════════════════╝ ╚════════════════════╝
- */
-use super::super::*;
-use super::{
- mac::{Frame, Mac, Window},
- radio, Event, RadioBuffer, Response, Timings,
-};
-
-#[derive(Copy, Clone)]
-pub enum State {
- Idle(Idle),
- SendingData(SendingData),
- WaitingForRxWindow(WaitingForRxWindow),
- WaitingForRx(WaitingForRx),
-}
-
-macro_rules! into_state {
- ($($from:tt),*) => {
- $(
- impl From<$from> for State
- {
- fn from(s: $from) -> State {
- State::$from(s)
- }
- }
- )*};
-}
-
-into_state!(Idle, SendingData, WaitingForRxWindow, WaitingForRx);
-
-impl Default for State {
- fn default() -> Self {
- State::Idle(Idle)
- }
-}
-
-impl From for Window {
- fn from(val: Rx) -> Window {
- match val {
- Rx::_1(_) => Window::_1,
- Rx::_2(_) => Window::_2,
- }
- }
-}
-
-#[derive(Debug)]
-pub enum Error {
- RadioEventWhileIdle,
- RadioEventWhileWaitingForRxWindow,
- NewSessionWhileWaitingForRxWindow,
- SendDataWhileWaitingForRxWindow,
- TxRequestDuringTx,
- NewSessionWhileWaitingForRx,
- SendDataWhileWaitingForRx,
- BufferTooSmall,
- UnexpectedRadioResponse,
-}
-
-impl From for super::Error {
- fn from(error: Error) -> super::Error {
- super::Error::State(error)
- }
-}
-
-impl State {
- pub(crate) fn handle_event<
- R: radio::PhyRxTx + Timings,
- C: CryptoFactory + Default,
- RNG: RngCore,
- const N: usize,
- const D: usize,
- >(
- self,
- mac: &mut Mac,
- radio: &mut R,
- rng: &mut RNG,
- buf: &mut RadioBuffer,
- dl: &mut Vec,
- event: Event,
- ) -> (Self, Result>) {
- match self {
- State::Idle(s) => s.handle_event::(mac, radio, rng, buf, event),
- State::SendingData(s) => s.handle_event::(mac, radio, event),
- State::WaitingForRxWindow(s) => s.handle_event::(mac, radio, event),
- State::WaitingForRx(s) => s.handle_event::(mac, radio, buf, event, dl),
- }
- }
-}
-
-#[derive(Copy, Clone)]
-pub struct Idle;
-
-impl Idle {
- pub(crate) fn handle_event<
- R: radio::PhyRxTx + Timings,
- C: CryptoFactory + Default,
- RNG: RngCore,
- const N: usize,
- >(
- self,
- mac: &mut Mac,
- radio: &mut R,
- rng: &mut RNG,
- buf: &mut RadioBuffer,
- event: Event,
- ) -> (State, Result>) {
- enum IntermediateResponse {
- RadioTx((Frame, radio::TxConfig, u32)),
- EarlyReturn(Result>),
- }
-
- let response = match event {
- // tolerate unexpected timeout
- Event::Join(creds) => {
- let (tx_config, dev_nonce) = mac.join_otaa::(rng, creds, buf);
- IntermediateResponse::RadioTx((Frame::Join, tx_config, dev_nonce as u32))
- }
- Event::TimeoutFired => IntermediateResponse::EarlyReturn(Ok(Response::NoUpdate)),
- Event::RadioEvent(_radio_event) => {
- IntermediateResponse::EarlyReturn(Err(Error::RadioEventWhileIdle.into()))
- }
- Event::SendDataRequest(send_data) => {
- let tx_config = mac.send::(rng, buf, &send_data);
- match tx_config {
- Err(e) => IntermediateResponse::EarlyReturn(Err(e.into())),
- Ok((tx_config, fcnt_up)) => {
- IntermediateResponse::RadioTx((Frame::Data, tx_config, fcnt_up))
- }
- }
- }
- };
- match response {
- IntermediateResponse::EarlyReturn(response) => (State::Idle(self), response),
- IntermediateResponse::RadioTx((frame, tx_config, fcnt_up)) => {
- let event: radio::Event =
- radio::Event::TxRequest(tx_config, buf.as_ref_for_read());
- match radio.handle_event(event) {
- Ok(response) => {
- match response {
- // intermediate state where we wait for Join to complete sending
- // allows for asynchronous sending
- radio::Response::Txing => (
- State::SendingData(SendingData { frame }),
- Ok(Response::UplinkSending(fcnt_up)),
- ),
- // directly jump to waiting for RxWindow
- // allows for synchronous sending
- radio::Response::TxDone(ms) => {
- data_rxwindow1_timeout::(frame, mac, radio, ms)
- }
- _ => (State::Idle(self), Err(Error::UnexpectedRadioResponse.into())),
- }
- }
- Err(e) => (State::Idle(self), Err(super::Error::Radio(e))),
- }
- }
- }
- }
-}
-
-#[derive(Copy, Clone)]
-pub struct SendingData {
- frame: Frame,
-}
-
-impl SendingData {
- pub(crate) fn handle_event(
- self,
- mac: &mut Mac,
- radio: &mut R,
- event: Event,
- ) -> (State, Result>) {
- match event {
- // we are waiting for the async tx to complete
- Event::RadioEvent(radio_event) => {
- // send the transmit request to the radio
- match radio.handle_event(radio_event) {
- Ok(response) => {
- match response {
- // expect a complete transmit
- radio::Response::TxDone(ms) => {
- data_rxwindow1_timeout::(self.frame, mac, radio, ms)
- }
- // anything other than TxComplete is unexpected
- _ => {
- panic!("SendingData: Unexpected radio response");
- }
- }
- }
- Err(e) => (State::SendingData(self), Err(super::Error::Radio(e))),
- }
- }
- // tolerate unexpected timeout
- Event::TimeoutFired => (State::SendingData(self), Ok(Response::NoUpdate)),
- // anything other than a RadioEvent is unexpected
- Event::Join(_) | Event::SendDataRequest(_) => {
- (self.into(), Err(Error::TxRequestDuringTx.into()))
- }
- }
- }
-}
-
-#[derive(Copy, Clone)]
-pub struct WaitingForRxWindow {
- frame: Frame,
- window: Rx,
-}
-
-impl WaitingForRxWindow {
- pub(crate) fn handle_event(
- self,
- mac: &mut Mac,
- radio: &mut R,
- event: Event,
- ) -> (State, Result>) {
- match event {
- // we are waiting for a Timeout
- Event::TimeoutFired => {
- let (rx_config, window_start) =
- mac.get_rx_parameters_legacy(&self.frame, &self.window.into());
- // configure the radio for the RX
- match radio.handle_event(radio::Event::RxRequest(rx_config)) {
- Ok(_) => {
- let window_close: u32 = match self.window {
- // RxWindow1 one must timeout before RxWindow2
- Rx::_1(time) => {
- let time_between_windows =
- mac.get_rx_delay(&self.frame, &Window::_2) - window_start;
- if time_between_windows > radio.get_rx_window_duration_ms() {
- time + radio.get_rx_window_duration_ms()
- } else {
- time + time_between_windows
- }
- }
- // RxWindow2 can last however long
- Rx::_2(time) => time + radio.get_rx_window_duration_ms(),
- };
- (
- State::WaitingForRx(self.into()),
- Ok(Response::TimeoutRequest(window_close)),
- )
- }
- Err(e) => (State::WaitingForRxWindow(self), Err(super::Error::Radio(e))),
- }
- }
- Event::RadioEvent(_) => (
- State::WaitingForRxWindow(self),
- Err(Error::RadioEventWhileWaitingForRxWindow.into()),
- ),
- Event::Join(_) => (
- State::WaitingForRxWindow(self),
- Err(Error::NewSessionWhileWaitingForRxWindow.into()),
- ),
- Event::SendDataRequest(_) => (
- State::WaitingForRxWindow(self),
- Err(Error::SendDataWhileWaitingForRxWindow.into()),
- ),
- }
- }
-}
-
-impl From for WaitingForRx {
- fn from(val: WaitingForRxWindow) -> WaitingForRx {
- WaitingForRx { frame: val.frame, window: val.window }
- }
-}
-
-#[derive(Copy, Clone)]
-pub struct WaitingForRx {
- frame: Frame,
- window: Rx,
-}
-
-impl WaitingForRx {
- pub(crate) fn handle_event<
- R: radio::PhyRxTx + Timings,
- C: CryptoFactory + Default,
- const N: usize,
- const D: usize,
- >(
- self,
- mac: &mut Mac,
- radio: &mut R,
- buf: &mut RadioBuffer,
- event: Event,
- dl: &mut Vec,
- ) -> (State, Result>) {
- match event {
- // we are waiting for the async tx to complete
- Event::RadioEvent(radio_event) => {
- // send the transmit request to the radio
- match radio.handle_event(radio_event) {
- Ok(response) => match response {
- radio::Response::RxDone(_quality) => {
- // copy from radio buffer to mac buffer
- buf.clear();
- if let Err(()) =
- buf.extend_from_slice(radio.get_received_packet().as_ref())
- {
- return (
- State::WaitingForRx(self),
- Err(Error::BufferTooSmall.into()),
- );
- }
- match mac.handle_rx::(buf, dl) {
- // NoUpdate can occur when a stray radio packet is received. Maintain state
- mac::Response::NoUpdate => {
- (State::WaitingForRx(self), Ok(Response::NoUpdate))
- }
- // Any other type of update indicates we are done receiving. Change to Idle
- r => (State::Idle(Idle), Ok(r.into())),
- }
- }
- _ => (State::WaitingForRx(self), Ok(Response::NoUpdate)),
- },
- Err(e) => (State::WaitingForRx(self), Err(super::Error::Radio(e))),
- }
- }
- Event::TimeoutFired => {
- if let Err(e) = radio.handle_event(radio::Event::CancelRx) {
- return (State::WaitingForRx(self), Err(super::Error::Radio(e)));
- }
-
- match self.window {
- Rx::_1(t1) => {
- let time_between_windows = mac.get_rx_delay(&self.frame, &Window::_2)
- - mac.get_rx_delay(&self.frame, &Window::_1);
- let t2 = t1 + time_between_windows;
- // TODO: jump to RxWindow2 if t2 == now
- (
- State::WaitingForRxWindow(WaitingForRxWindow {
- frame: self.frame,
- window: Rx::_2(t2),
- }),
- Ok(Response::TimeoutRequest(t2)),
- )
- }
- // Timeout during second RxWindow leads to giving up
- Rx::_2(_) => {
- let response = mac.rx2_complete();
- (State::Idle(Idle), Ok(response.into()))
- }
- }
- }
- Event::Join(_) => {
- (State::WaitingForRx(self), Err(Error::NewSessionWhileWaitingForRx.into()))
- }
- Event::SendDataRequest(_) => {
- (State::WaitingForRx(self), Err(Error::SendDataWhileWaitingForRx.into()))
- }
- }
- }
-}
-
-#[derive(Copy, Clone, Debug)]
-enum Rx {
- _1(u32),
- _2(u32),
-}
-
-fn data_rxwindow1_timeout(
- frame: Frame,
- mac: &mut Mac,
- radio: &mut R,
- timestamp_ms: u32,
-) -> (State, Result>) {
- let delay = mac.get_rx_delay(&frame, &Window::_1);
- let t1 = (delay as i32 + timestamp_ms as i32 + radio.get_rx_window_offset_ms()) as u32;
- (
- State::WaitingForRxWindow(WaitingForRxWindow { frame, window: Rx::_1(t1) }),
- Ok(Response::TimeoutRequest(t1)),
- )
-}
diff --git a/lorawan-device-patch/src/nb_device/test/mod.rs b/lorawan-device-patch/src/nb_device/test/mod.rs
deleted file mode 100644
index 0430947..0000000
--- a/lorawan-device-patch/src/nb_device/test/mod.rs
+++ /dev/null
@@ -1,128 +0,0 @@
-use super::*;
-mod util;
-
-use crate::nb_device::Event;
-#[test]
-fn test_join_rx1() {
- let mut device = test_device();
- let response = device.join(get_otaa_credentials()).unwrap();
- assert!(matches!(response, Response::TimeoutRequest(5000)));
- // send a timeout for beginning of window
- let response = device.handle_event(Event::TimeoutFired).unwrap();
- assert!(matches!(response, Response::TimeoutRequest(5100)));
- device.get_radio().set_rxtx_handler(handle_join_request::<1>);
- // send a radio event to let the radio device indicate a packet was received
- let response = device.handle_event(Event::RadioEvent(radio::Event::Phy(()))).unwrap();
- assert!(matches!(response, Response::JoinSuccess));
- assert!(device.get_session_keys().is_some());
-}
-
-#[test]
-fn test_join_rx2() {
- let mut device = test_device();
- device.get_radio().set_rxtx_handler(handle_join_request::<2>);
- let response = device.join(get_otaa_credentials()).unwrap();
- assert!(matches!(response, Response::TimeoutRequest(5000)));
- let response = device.handle_event(Event::TimeoutFired).unwrap();
- assert!(matches!(response, Response::TimeoutRequest(5100)));
- // send a timeout for end of rx2
- let response = device.handle_event(Event::TimeoutFired).unwrap();
- assert!(matches!(response, Response::TimeoutRequest(6000)));
- // send a timeout for beginning of rx2
- let response = device.handle_event(Event::TimeoutFired).unwrap();
- assert!(matches!(response, Response::TimeoutRequest(6100)));
- // send a radio event to let the radio device indicate a packet was received
- let response = device.handle_event(Event::RadioEvent(radio::Event::Phy(()))).unwrap();
- assert!(matches!(response, Response::JoinSuccess));
- assert!(device.get_session_keys().is_some());
-}
-
-#[test]
-fn test_unconfirmed_uplink_no_downlink() {
- let mut device = test_device();
- device.join(get_abp_credentials()).unwrap();
- let response = device.send(&[0; 1], 1, false).unwrap();
- assert!(matches!(response, Response::TimeoutRequest(1000)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // begin Rx1
- assert!(matches!(response, Response::TimeoutRequest(1100)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // end Rx1
- assert!(matches!(response, Response::TimeoutRequest(2000)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // being Rx2
- assert!(matches!(response, Response::TimeoutRequest(2100)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // end Rx2
- assert!(matches!(response, Response::RxComplete));
-}
-#[test]
-fn test_confirmed_uplink_no_ack() {
- let mut device = test_device();
- let response = device.join(get_abp_credentials());
- assert!(matches!(response, Ok(Response::JoinSuccess)));
- let response = device.send(&[0; 1], 1, true).unwrap();
- assert!(matches!(response, Response::TimeoutRequest(1000)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // begin Rx1
- assert!(matches!(response, Response::TimeoutRequest(1100)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // end Rx1
- assert!(matches!(response, Response::TimeoutRequest(2000)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // being Rx2
- assert!(matches!(response, Response::TimeoutRequest(2100)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // end Rx2
- assert!(matches!(response, Response::NoAck));
-}
-
-#[test]
-fn test_confirmed_uplink_with_ack_rx1() {
- let mut device = test_device();
- let response = device.join(get_abp_credentials());
- assert!(matches!(response, Ok(Response::JoinSuccess)));
- let response = device.send(&[0; 1], 1, true).unwrap();
- assert!(matches!(response, Response::TimeoutRequest(1000)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // begin Rx1
- assert!(matches!(response, Response::TimeoutRequest(1100)));
- device.get_radio().set_rxtx_handler(handle_data_uplink_with_link_adr_req::<0, 0>);
- // send a radio event to let the radio device indicate a packet was received
- let response = device.handle_event(Event::RadioEvent(radio::Event::Phy(()))).unwrap();
- assert!(matches!(response, Response::DownlinkReceived(0)));
-}
-
-#[test]
-fn test_confirmed_uplink_with_ack_rx2() {
- let mut device = test_device();
- let response = device.join(get_abp_credentials());
- assert!(matches!(response, Ok(Response::JoinSuccess)));
- let response = device.send(&[0; 1], 1, true).unwrap();
- assert!(matches!(response, Response::TimeoutRequest(1000)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // begin Rx1
- assert!(matches!(response, Response::TimeoutRequest(1100)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // end Rx1
- assert!(matches!(response, Response::TimeoutRequest(2000)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // being Rx2
- assert!(matches!(response, Response::TimeoutRequest(2100)));
- device.get_radio().set_rxtx_handler(handle_data_uplink_with_link_adr_req::<0, 0>);
- // send a radio event to let the radio device indicate a packet was received
- let response = device.handle_event(Event::RadioEvent(radio::Event::Phy(()))).unwrap();
- assert!(matches!(response, Response::DownlinkReceived(0)));
-}
-
-#[test]
-fn test_link_adr_ans() {
- let mut device = test_device();
- let response = device.join(get_abp_credentials());
- assert!(matches!(response, Ok(Response::JoinSuccess)));
- let response = device.send(&[0; 1], 1, true).unwrap();
- assert!(matches!(response, Response::TimeoutRequest(1000)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // begin Rx1
- assert!(matches!(response, Response::TimeoutRequest(1100)));
- device.get_radio().set_rxtx_handler(handle_data_uplink_with_link_adr_req::<0, 0>);
- // send a radio event to let the radio device indicate a packet was received
- let response = device.handle_event(Event::RadioEvent(radio::Event::Phy(()))).unwrap();
- assert!(matches!(response, Response::DownlinkReceived(0)));
- // send another uplink which should carry the LinkAdrAns
- let response = device.send(&[0; 1], 1, true).unwrap();
- assert!(matches!(response, Response::TimeoutRequest(1000)));
- let response = device.handle_event(Event::TimeoutFired).unwrap(); // begin Rx1
- assert!(matches!(response, Response::TimeoutRequest(1100)));
- device.get_radio().set_rxtx_handler(handle_data_uplink_with_link_adr_ans);
- // send a radio event to let the radio device indicate a packet was received
- let response = device.handle_event(Event::RadioEvent(radio::Event::Phy(()))).unwrap();
- assert!(matches!(response, Response::DownlinkReceived(1)));
-}
diff --git a/lorawan-device-patch/src/nb_device/test/util.rs b/lorawan-device-patch/src/nb_device/test/util.rs
deleted file mode 100644
index ab676be..0000000
--- a/lorawan-device-patch/src/nb_device/test/util.rs
+++ /dev/null
@@ -1,96 +0,0 @@
-use crate::radio::{RfConfig, RxQuality};
-
-use crate::nb_device::{
- radio::{Event, PhyRxTx, Response},
- Device, Timings,
-};
-use lorawan::default_crypto;
-use region::{Configuration, Region};
-
-pub fn test_device() -> Device {
- Device::new(Configuration::new(Region::US915), TestRadio::default(), rand::rngs::OsRng)
-}
-
-#[derive(Debug)]
-pub struct TestRadio {
- current_config: Option,
- last_uplink: Option,
- rxtx_handler: Option,
- buffer: [u8; 256],
- buffer_index: usize,
-}
-
-impl TestRadio {
- pub fn set_rxtx_handler(&mut self, handler: RxTxHandler) {
- self.rxtx_handler = Some(handler);
- }
-}
-
-impl Default for TestRadio {
- fn default() -> Self {
- Self {
- current_config: None,
- last_uplink: None,
- rxtx_handler: None,
- buffer: [0; 256],
- buffer_index: 0,
- }
- }
-}
-
-impl PhyRxTx for TestRadio {
- type PhyEvent = ();
- type PhyError = &'static str;
- type PhyResponse = ();
-
- const MAX_RADIO_POWER: u8 = 26;
-
- const ANTENNA_GAIN: i8 = 0;
-
- fn get_mut_radio(&mut self) -> &mut Self {
- self
- }
- fn get_received_packet(&mut self) -> &mut [u8] {
- &mut self.buffer[..self.buffer_index]
- }
-
- fn handle_event(&mut self, event: Event) -> Result, Self::PhyError>
- where
- Self: Sized,
- {
- match event {
- Event::TxRequest(config, buf) => {
- // ensure that we have always consumed the previous uplink
- if self.last_uplink.is_some() {
- panic!("last uplink not consumed")
- }
- self.last_uplink =
- Some(Uplink::new(buf, config).map_err(|_| "error creating uplink")?);
- return Ok(Response::TxDone(0));
- }
- Event::RxRequest(rf_config) => {
- self.current_config = Some(rf_config);
- }
- Event::CancelRx => (),
- Event::Phy(()) => {
- if let (Some(rf_config), Some(rxtx_handler)) =
- (self.current_config, self.rxtx_handler)
- {
- self.buffer_index =
- rxtx_handler(self.last_uplink.take(), rf_config, &mut self.buffer);
- return Ok(Response::RxDone(RxQuality::new(0, 0)));
- }
- }
- }
- Ok(Response::Idle)
- }
-}
-
-impl Timings for TestRadio {
- fn get_rx_window_offset_ms(&self) -> i32 {
- 0
- }
- fn get_rx_window_duration_ms(&self) -> u32 {
- 100
- }
-}
diff --git a/lorawan-device-patch/src/radio.rs b/lorawan-device-patch/src/radio.rs
deleted file mode 100644
index b3637d4..0000000
--- a/lorawan-device-patch/src/radio.rs
+++ /dev/null
@@ -1,112 +0,0 @@
-pub use lora_modulation::BaseBandModulationParams;
-
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct RfConfig {
- pub frequency: u32,
- pub bb: BaseBandModulationParams,
-}
-
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub enum RxMode {
- Continuous,
- /// Single shot receive. Argument `ms` indicates how many milliseconds of extra buffer time should
- /// be added to the preamble detection timeout.
- Single {
- ms: u32,
- },
-}
-
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct RxConfig {
- pub rf: RfConfig,
- pub mode: RxMode,
-}
-
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct TxConfig {
- pub pw: i8,
- pub rf: RfConfig,
-}
-
-impl TxConfig {
- pub fn adjust_power(&mut self, max_power: u8, antenna_gain: i8) {
- self.pw -= antenna_gain;
- self.pw = core::cmp::min(self.pw, max_power as i8);
- }
-}
-
-#[cfg_attr(feature = "defmt", derive(defmt::Format))]
-#[derive(Debug, Clone, Copy, PartialEq)]
-pub struct RxQuality {
- rssi: i16,
- snr: i8,
-}
-
-impl RxQuality {
- pub fn new(rssi: i16, snr: i8) -> RxQuality {
- RxQuality { rssi, snr }
- }
-
- pub fn rssi(self) -> i16 {
- self.rssi
- }
- pub fn snr(self) -> i8 {
- self.snr
- }
-}
-
-pub(crate) struct RadioBuffer {
- packet: [u8; N],
- pos: usize,
-}
-
-impl RadioBuffer {
- pub(crate) fn new() -> Self {
- Self { packet: [0; N], pos: 0 }
- }
-
- pub(crate) fn clear(&mut self) {
- self.pos = 0;
- }
-
- pub(crate) fn set_pos(&mut self, pos: usize) {
- self.pos = pos;
- }
-
- pub(crate) fn extend_from_slice(&mut self, buf: &[u8]) -> Result<(), ()> {
- if self.pos + buf.len() < self.packet.len() {
- self.packet[self.pos..self.pos + buf.len()].copy_from_slice(buf);
- self.pos += buf.len();
- Ok(())
- } else {
- Err(())
- }
- }
-
- /// Provides a mutable slice of the buffer up to the current position.
- pub(crate) fn as_mut_for_read(&mut self) -> &mut [u8] {
- &mut self.packet[..self.pos]
- }
-
- /// Provides a reference of the buffer up to the current position.
-
- pub(crate) fn as_ref_for_read(&self) -> &[u8] {
- &self.packet[..self.pos]
- }
-}
-
-impl AsMut<[u8]> for RadioBuffer {
- fn as_mut(&mut self) -> &mut [u8] {
- &mut self.packet
- }
-}
-
-impl AsRef<[u8]> for RadioBuffer {
- fn as_ref(&self) -> &[u8] {
- &self.packet
- }
-}
diff --git a/lorawan-device-patch/src/region/constants.rs b/lorawan-device-patch/src/region/constants.rs
deleted file mode 100644
index 4560b11..0000000
--- a/lorawan-device-patch/src/region/constants.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-#![allow(dead_code)]
-use lora_modulation::{Bandwidth, CodingRate, SpreadingFactor};
-
-pub(crate) const RECEIVE_DELAY1: u32 = 1000;
-pub(crate) const RECEIVE_DELAY2: u32 = RECEIVE_DELAY1 + 1000; // must be RECEIVE_DELAY + 1 s
-pub(crate) const JOIN_ACCEPT_DELAY1: u32 = 5000;
-pub(crate) const JOIN_ACCEPT_DELAY2: u32 = 6000;
-pub(crate) const MAX_FCNT_GAP: usize = 16384;
-pub(crate) const ADR_ACK_LIMIT: usize = 64;
-pub(crate) const ADR_ACK_DELAY: usize = 32;
-pub(crate) const ACK_TIMEOUT: usize = 2; // random delay between 1 and 3 seconds
-
-pub(crate) const DEFAULT_BANDWIDTH: Bandwidth = Bandwidth::_125KHz;
-pub(crate) const DEFAULT_SPREADING_FACTOR: SpreadingFactor = SpreadingFactor::_7;
-pub(crate) const DEFAULT_CODING_RATE: CodingRate = CodingRate::_4_5;
-pub(crate) const DEFAULT_DBM: i8 = 14;
diff --git a/lorawan-device-patch/src/region/dynamic_channel_plans/as923.rs b/lorawan-device-patch/src/region/dynamic_channel_plans/as923.rs
deleted file mode 100644
index 9fb571d..0000000
--- a/lorawan-device-patch/src/region/dynamic_channel_plans/as923.rs
+++ /dev/null
@@ -1,80 +0,0 @@
-use super::*;
-
-const JOIN_CHANNELS: [u32; 2] = [923200000, 923200000];
-
-pub(crate) type AS923_1 = DynamicChannelPlan<2, 7, AS923Region<923_200_000, 0>>;
-pub(crate) type AS923_2 = DynamicChannelPlan<2, 7, AS923Region<921_400_000, 1800000>>;
-pub(crate) type AS923_3 = DynamicChannelPlan<2, 7, AS923Region<916_600_000, 6600000>>;
-pub(crate) type AS923_4 = DynamicChannelPlan<2, 7, AS923Region<917_300_000, 5900000>>;
-
-#[derive(Default, Clone)]
-#[allow(clippy::upper_case_acronyms)]
-pub struct AS923Region;
-
-impl ChannelRegion<7>
-for AS923Region
-{
- fn datarates() -> &'static [Option; 7] {
- &DATARATES
- }
-}
-
-impl DynamicChannelRegion<2, 7>
-for AS923Region
-{
- fn join_channels() -> [u32; 2] {
- [JOIN_CHANNELS[0] + OFFSET, JOIN_CHANNELS[1] + OFFSET]
- }
-
- fn get_default_rx2() -> u32 {
- DEFAULT_RX2
- }
-}
-
-use super::{Bandwidth, Datarate, SpreadingFactor};
-
-pub(crate) const DATARATES: [Option; 7] = [
- Some(Datarate {
- spreading_factor: SpreadingFactor::_12,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 59,
- max_mac_payload_size_with_dwell_time: 0,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_11,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 59,
- max_mac_payload_size_with_dwell_time: 0,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_10,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 123,
- max_mac_payload_size_with_dwell_time: 19,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_9,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 123,
- max_mac_payload_size_with_dwell_time: 61,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_8,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 133,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_7,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_7,
- bandwidth: Bandwidth::_250KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- // TODO: ignore FSK data rate for now
-];
diff --git a/lorawan-device-patch/src/region/dynamic_channel_plans/eu433.rs b/lorawan-device-patch/src/region/dynamic_channel_plans/eu433.rs
deleted file mode 100644
index 413aa4d..0000000
--- a/lorawan-device-patch/src/region/dynamic_channel_plans/eu433.rs
+++ /dev/null
@@ -1,74 +0,0 @@
-#![allow(dead_code)]
-use super::*;
-
-const JOIN_CHANNELS: [u32; 3] = [433_175_000, 433_375_000, 433_575_000];
-
-pub(crate) type EU433 = DynamicChannelPlan<3, 7, EU433Region>;
-
-#[derive(Default, Clone)]
-#[allow(clippy::upper_case_acronyms)]
-pub struct EU433Region;
-
-impl ChannelRegion<7> for EU433Region {
- fn datarates() -> &'static [Option; 7] {
- &DATARATES
- }
-}
-
-impl DynamicChannelRegion<3, 7> for EU433Region {
- fn join_channels() -> [u32; 3] {
- JOIN_CHANNELS
- }
-
- fn get_default_rx2() -> u32 {
- 434_665_000
- }
-}
-
-use super::{Bandwidth, Datarate, SpreadingFactor};
-
-pub(crate) const DATARATES: [Option; 7] = [
- Some(Datarate {
- spreading_factor: SpreadingFactor::_12,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 59,
- max_mac_payload_size_with_dwell_time: 0,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_11,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 59,
- max_mac_payload_size_with_dwell_time: 0,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_10,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 123,
- max_mac_payload_size_with_dwell_time: 19,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_9,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 123,
- max_mac_payload_size_with_dwell_time: 61,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_8,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 133,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_7,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_7,
- bandwidth: Bandwidth::_250KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- // TODO 7 is defined in rp002-1-0-4
-];
diff --git a/lorawan-device-patch/src/region/dynamic_channel_plans/eu868.rs b/lorawan-device-patch/src/region/dynamic_channel_plans/eu868.rs
deleted file mode 100644
index bc7a5ee..0000000
--- a/lorawan-device-patch/src/region/dynamic_channel_plans/eu868.rs
+++ /dev/null
@@ -1,74 +0,0 @@
-#![allow(dead_code)]
-use super::*;
-
-const JOIN_CHANNELS: [u32; 3] = [868_100_000, 868_300_000, 868_500_000];
-
-pub(crate) type EU868 = DynamicChannelPlan<3, 7, EU868Region>;
-
-#[derive(Default, Clone)]
-#[allow(clippy::upper_case_acronyms)]
-pub struct EU868Region;
-
-impl ChannelRegion<7> for EU868Region {
- fn datarates() -> &'static [Option; 7] {
- &DATARATES
- }
-}
-
-impl DynamicChannelRegion<3, 7> for EU868Region {
- fn join_channels() -> [u32; 3] {
- JOIN_CHANNELS
- }
-
- fn get_default_rx2() -> u32 {
- 869_525_000
- }
-}
-
-use super::{Bandwidth, Datarate, SpreadingFactor};
-
-pub(crate) const DATARATES: [Option; 7] = [
- Some(Datarate {
- spreading_factor: SpreadingFactor::_12,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 59,
- max_mac_payload_size_with_dwell_time: 59,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_11,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 59,
- max_mac_payload_size_with_dwell_time: 59,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_10,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 59,
- max_mac_payload_size_with_dwell_time: 59,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_9,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 123,
- max_mac_payload_size_with_dwell_time: 123,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_8,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_7,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_7,
- bandwidth: Bandwidth::_250KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- // TODO: ignore FSK data rate for now
-];
diff --git a/lorawan-device-patch/src/region/dynamic_channel_plans/in865.rs b/lorawan-device-patch/src/region/dynamic_channel_plans/in865.rs
deleted file mode 100644
index 73e26d4..0000000
--- a/lorawan-device-patch/src/region/dynamic_channel_plans/in865.rs
+++ /dev/null
@@ -1,68 +0,0 @@
-#![allow(dead_code)]
-use super::*;
-
-const JOIN_CHANNELS: [u32; 3] = [865_062_500, 865_402_500, 865_985_000];
-
-pub(crate) type IN865 = DynamicChannelPlan<3, 6, IN865Region>;
-
-#[derive(Default, Clone)]
-#[allow(clippy::upper_case_acronyms)]
-pub struct IN865Region;
-
-impl ChannelRegion<6> for IN865Region {
- fn datarates() -> &'static [Option; 6] {
- &DATARATES
- }
-}
-
-impl DynamicChannelRegion<3, 6> for IN865Region {
- fn join_channels() -> [u32; 3] {
- JOIN_CHANNELS
- }
-
- fn get_default_rx2() -> u32 {
- 866_550_000
- }
-}
-
-use super::{Bandwidth, Datarate, SpreadingFactor};
-
-pub(crate) const DATARATES: [Option; 6] = [
- Some(Datarate {
- spreading_factor: SpreadingFactor::_12,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 59,
- max_mac_payload_size_with_dwell_time: 59,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_11,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 59,
- max_mac_payload_size_with_dwell_time: 59,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_10,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 59,
- max_mac_payload_size_with_dwell_time: 59,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_9,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 123,
- max_mac_payload_size_with_dwell_time: 123,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_8,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_7,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- // TODO: ignore FSK data rate for now
-];
diff --git a/lorawan-device-patch/src/region/dynamic_channel_plans/mod.rs b/lorawan-device-patch/src/region/dynamic_channel_plans/mod.rs
deleted file mode 100644
index 3552dce..0000000
--- a/lorawan-device-patch/src/region/dynamic_channel_plans/mod.rs
+++ /dev/null
@@ -1,220 +0,0 @@
-use super::*;
-use core::marker::PhantomData;
-
-#[cfg(any(
- feature = "region-as923-1",
- feature = "region-as923-2",
- feature = "region-as923-3",
- feature = "region-as923-4"
-))]
-mod as923;
-#[cfg(feature = "region-eu433")]
-mod eu433;
-#[cfg(feature = "region-eu868")]
-mod eu868;
-#[cfg(feature = "region-in865")]
-mod in865;
-
-#[cfg(feature = "region-as923-1")]
-pub(crate) use as923::AS923_1;
-#[cfg(feature = "region-as923-2")]
-pub(crate) use as923::AS923_2;
-#[cfg(feature = "region-as923-3")]
-pub(crate) use as923::AS923_3;
-#[cfg(feature = "region-as923-4")]
-pub(crate) use as923::AS923_4;
-#[cfg(feature = "region-eu433")]
-pub(crate) use eu433::EU433;
-#[cfg(feature = "region-eu868")]
-pub(crate) use eu868::EU868;
-#[cfg(feature = "region-in865")]
-pub(crate) use in865::IN865;
-
-#[derive(Default, Clone)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub(crate) struct DynamicChannelPlan<
- const NUM_JOIN_CHANNELS: usize,
- const NUM_DATARATES: usize,
- R: DynamicChannelRegion,
-> {
- additional_channels: [Option; 5],
- channel_mask: ChannelMask<9>,
- last_tx_channel: u8,
- _fixed_channel_region: PhantomData,
- rx1_offset: usize,
- rx2_dr: usize,
-}
-
-impl<
- const NUM_JOIN_CHANNELS: usize,
- const NUM_DATARATES: usize,
- R: DynamicChannelRegion,
-> DynamicChannelPlan
-{
- fn get_channel(&self, channel: usize) -> Option {
- if channel < NUM_JOIN_CHANNELS {
- Some(R::join_channels()[channel])
- } else {
- let index = channel - NUM_JOIN_CHANNELS;
- if index < self.additional_channels.len() {
- self.additional_channels[index]
- } else {
- None
- }
- }
- }
-
- fn highest_additional_channel_index_plus_one(&self) -> usize {
- let mut index_plus_one = 0;
- for (i, channel) in self.additional_channels.iter().enumerate() {
- if channel.is_some() {
- index_plus_one = i + 1;
- }
- }
- index_plus_one
- }
-
- fn get_random_in_range(&self, rng: &mut RNG) -> usize {
- let range = self.highest_additional_channel_index_plus_one() + NUM_JOIN_CHANNELS;
- let cm = if range > 16 {
- 0b11111
- } else if range > 8 {
- 0b1111
- } else {
- 0b111
- };
- (rng.next_u32() as usize) & cm
- }
-
- pub fn get_max_payload_length(datarate: DR, repeater_compatible: bool, dwell_time: bool) -> u8 {
- R::get_max_payload_length(datarate, repeater_compatible, dwell_time)
- }
-}
-
-pub(crate) trait DynamicChannelRegion:
-ChannelRegion
-{
- fn join_channels() -> [u32; NUM_JOIN_CHANNELS];
- fn get_default_rx2() -> u32;
-}
-
-impl<
- const NUM_JOIN_CHANNELS: usize,
- const NUM_DATARATES: usize,
- R: DynamicChannelRegion,
-> RegionHandler for DynamicChannelPlan
-{
- fn process_join_accept, C>(
- &mut self,
- join_accept: &DecryptedJoinAcceptPayload,
- ) {
- match join_accept.c_f_list() {
- Some(CfList::DynamicChannel(cf_list)) => {
- // If CfList of Type 0 is present, it may contain up to 5 frequencies
- // which define channels J to (J+4)
- for (index, freq) in cf_list.iter().enumerate() {
- let value = freq.value();
- // unused channels are set to 0
- if value != 0 {
- self.additional_channels[index] = Some(value);
- } else {
- self.additional_channels[index] = None;
- }
- }
- }
- Some(CfList::FixedChannel(_cf_list)) => {
- //TODO: dynamic channel plans have corresponding fixed channel lists,
- // however, this feature is entirely optional
- }
- None => {}
- }
- }
-
- fn handle_link_adr_channel_mask(
- &mut self,
- channel_mask_control: u8,
- channel_mask: ChannelMask<2>,
- ) {
- match channel_mask_control {
- 0..=4 => {
- let base_index = channel_mask_control as usize * 2;
- self.channel_mask.set_bank(base_index, channel_mask.get_index(0));
- self.channel_mask.set_bank(base_index + 1, channel_mask.get_index(1));
- }
- 5 => {
- let channel_mask: u16 =
- channel_mask.get_index(0) as u16 | ((channel_mask.get_index(1) as u16) << 8);
- self.channel_mask.set_bank(0, ((channel_mask & 0b1) * 0xFF) as u8);
- self.channel_mask.set_bank(1, ((channel_mask & 0b10) * 0xFF) as u8);
- self.channel_mask.set_bank(2, ((channel_mask & 0b100) * 0xFF) as u8);
- self.channel_mask.set_bank(3, ((channel_mask & 0b1000) * 0xFF) as u8);
- self.channel_mask.set_bank(4, ((channel_mask & 0b10000) * 0xFF) as u8);
- self.channel_mask.set_bank(5, ((channel_mask & 0b100000) * 0xFF) as u8);
- self.channel_mask.set_bank(6, ((channel_mask & 0b1000000) * 0xFF) as u8);
- self.channel_mask.set_bank(7, ((channel_mask & 0b10000000) * 0xFF) as u8);
- self.channel_mask.set_bank(8, ((channel_mask & 0b100000000) * 0xFF) as u8);
- }
- 6 => {
- // all channels on
- for i in 0..8 {
- self.channel_mask.set_bank(i, 0xFF);
- }
- }
- _ => {
- //RFU
- }
- }
- }
-
- fn get_tx_dr_and_frequency(
- &mut self,
- rng: &mut RNG,
- datarate: DR,
- frame: &Frame,
- ) -> (Datarate, u32) {
- match frame {
- Frame::Join => {
- // there are at most 8 join channels
- let mut channel = (rng.next_u32() & 0b111) as u8;
- // keep sampling until we select a join channel depending
- // on the frequency plan
- while channel as usize >= NUM_JOIN_CHANNELS {
- channel = (rng.next_u32() & 0b111) as u8;
- }
- self.last_tx_channel = channel;
- (
- R::datarates()[datarate as usize].clone().unwrap(),
- R::join_channels()[channel as usize],
- )
- }
- Frame::Data => {
- let mut channel = self.get_random_in_range(rng);
- loop {
- if self.channel_mask.is_enabled(channel).unwrap() {
- if let Some(freq) = self.get_channel(channel) {
- self.last_tx_channel = channel as u8;
- return (R::datarates()[datarate as usize].clone().unwrap(), freq);
- }
- }
- channel = self.get_random_in_range(rng)
- }
- }
- }
- }
-
- fn get_rx_frequency(&self, _frame: &Frame, window: &Window) -> u32 {
- match window {
- // TODO: implement RxOffset but first need to implement RxOffset MacCommand
- Window::_1 => self.get_channel(self.last_tx_channel as usize).unwrap(),
- Window::_2 => R::get_default_rx2(),
- }
- }
-
- fn get_rx_datarate(&self, tx_datarate: DR, _frame: &Frame, window: &Window) -> Datarate {
- let datarate = match window {
- Window::_1 => tx_datarate as usize + self.rx1_offset,
- Window::_2 => self.rx2_dr,
- };
- R::datarates()[datarate].clone().unwrap()
- }
-}
diff --git a/lorawan-device-patch/src/region/fixed_channel_plans/au915/datarates.rs b/lorawan-device-patch/src/region/fixed_channel_plans/au915/datarates.rs
deleted file mode 100644
index fd58b1b..0000000
--- a/lorawan-device-patch/src/region/fixed_channel_plans/au915/datarates.rs
+++ /dev/null
@@ -1,85 +0,0 @@
-use super::{Bandwidth, Datarate, SpreadingFactor};
-
-pub(crate) const DATARATES: [Option; 16] = [
- Some(Datarate {
- spreading_factor: SpreadingFactor::_12,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 59,
- max_mac_payload_size_with_dwell_time: 0,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_11,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 59,
- max_mac_payload_size_with_dwell_time: 0,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_10,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 59,
- max_mac_payload_size_with_dwell_time: 19,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_9,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 123,
- max_mac_payload_size_with_dwell_time: 61,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_8,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 133,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_7,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_8,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- None, // LR-FHSS -- not currently supported, TODO: defined in rp002-1-0-4
- Some(Datarate {
- spreading_factor: SpreadingFactor::_12,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 61,
- max_mac_payload_size_with_dwell_time: 61,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_11,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 137,
- max_mac_payload_size_with_dwell_time: 137,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_10,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_9,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_8,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_7,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- None, // RFU, TODO: defined in rp002-1-0-4
- None, // TODO: defined in rp002-1-0-4
-];
diff --git a/lorawan-device-patch/src/region/fixed_channel_plans/au915/frequencies.rs b/lorawan-device-patch/src/region/fixed_channel_plans/au915/frequencies.rs
deleted file mode 100644
index 7a5a020..0000000
--- a/lorawan-device-patch/src/region/fixed_channel_plans/au915/frequencies.rs
+++ /dev/null
@@ -1,94 +0,0 @@
-pub(crate) const UPLINK_CHANNEL_MAP: [u32; 72] = [
- // channels 0-7 (125 kHz)
- 915_200_000,
- 915_400_000,
- 915_600_000,
- 915_800_000,
- 916_000_000,
- 916_200_000,
- 916_400_000,
- 916_600_000,
- // channels 8-15 (125 kHz)
- 916_800_000,
- 917_000_000,
- 917_200_000,
- 917_400_000,
- 917_600_000,
- 917_800_000,
- 918_000_000,
- 918_200_000,
- // channels 16-23 (125 kHz)
- 918_400_000,
- 918_600_000,
- 918_800_000,
- 919_000_000,
- 919_200_000,
- 919_400_000,
- 919_600_000,
- 919_800_000,
- // channels 24-31 (125 kHz)
- 920_000_000,
- 920_200_000,
- 920_400_000,
- 920_600_000,
- 920_800_000,
- 921_000_000,
- 921_200_000,
- 921_400_000,
- // channels 32-39 (125 kHz)
- 921_600_000,
- 921_800_000,
- 922_000_000,
- 922_200_000,
- 922_400_000,
- 922_600_000,
- 922_800_000,
- 923_000_000,
- // channels 39-47 (125 kHz)
- 923_200_000,
- 923_400_000,
- 923_600_000,
- 923_800_000,
- 924_000_000,
- 924_200_000,
- 924_400_000,
- 924_600_000,
- // channels 47-55 (125 kHz)
- 924_800_000,
- 925_000_000,
- 925_200_000,
- 925_400_000,
- 925_600_000,
- 925_800_000,
- 926_000_000,
- 926_200_000,
- // channels 55-63 (125 kHz)
- 926_400_000,
- 926_600_000,
- 926_800_000,
- 927_000_000,
- 927_200_000,
- 927_400_000,
- 927_600_000,
- 927_800_000,
- // channels 63-71 (500 kHz)
- 915_900_000,
- 917_500_000,
- 919_100_000,
- 920_700_000,
- 922_300_000,
- 923_900_000,
- 925_500_000,
- 927_100_000,
-];
-
-pub(crate) const DOWNLINK_CHANNEL_MAP: [u32; 8] = [
- 922_300_000,
- 923_900_000,
- 924_500_000,
- 925_100_000,
- 925_700_000,
- 926_300_000,
- 926_900_000,
- 927_500_000,
-];
diff --git a/lorawan-device-patch/src/region/fixed_channel_plans/au915/mod.rs b/lorawan-device-patch/src/region/fixed_channel_plans/au915/mod.rs
deleted file mode 100644
index 16cbe8a..0000000
--- a/lorawan-device-patch/src/region/fixed_channel_plans/au915/mod.rs
+++ /dev/null
@@ -1,84 +0,0 @@
-use super::*;
-
-mod frequencies;
-use frequencies::*;
-
-mod datarates;
-use datarates::*;
-
-const AU_DBM: i8 = 21;
-const DEFAULT_RX2: u32 = 923_300_000;
-
-/// State struct for the `AU915` region. This struct may be created directly if you wish to fine-tune some parameters.
-/// At this time specifying a bias for the subband used during the join process is supported using
-/// [`set_join_bias`](Self::set_join_bias) and [`set_join_bias_and_noncompliant_retries`](Self::set_join_bias_and_noncompliant_retries)
-/// is suppored. This struct can then be turned into a [`Configuration`] as it implements [`Into`].
-///
-/// # Note:
-///
-/// Only [`US915`] and [`AU915`] can be created using this method, because they are the only ones which have
-/// parameters that may be fine-tuned at the region level. To create a [`Configuration`] for other regions, use
-/// [`Configuration::new`] and specify the region using the [`Region`] enum.
-///
-/// # Example: Setting up join bias
-///
-/// ```
-/// use lorawan_device::region::{Configuration, AU915, Subband};
-///
-/// let mut au915 = AU915::new();
-/// // Subband 2 is commonly used for The Things Network.
-/// au915.set_join_bias(Subband::_2);
-/// let configuration: Configuration = au915.into();
-/// ```
-#[derive(Default, Clone)]
-pub struct AU915(pub(crate) FixedChannelPlan<16, AU915Region>);
-
-impl AU915 {
- pub fn get_max_payload_length(datarate: DR, repeater_compatible: bool, dwell_time: bool) -> u8 {
- AU915Region::get_max_payload_length(datarate, repeater_compatible, dwell_time)
- }
-}
-
-#[derive(Default, Clone)]
-pub(crate) struct AU915Region;
-
-impl ChannelRegion<16> for AU915Region {
- fn datarates() -> &'static [Option; 16] {
- &DATARATES
- }
-}
-
-impl FixedChannelRegion<16> for AU915Region {
- fn uplink_channels() -> &'static [u32; 72] {
- &UPLINK_CHANNEL_MAP
- }
- fn downlink_channels() -> &'static [u32; 8] {
- &DOWNLINK_CHANNEL_MAP
- }
- fn get_default_rx2() -> u32 {
- DEFAULT_RX2
- }
- fn get_rx_datarate(tx_datarate: DR, _frame: &Frame, window: &Window) -> Datarate {
- let datarate = match window {
- Window::_1 => {
- // no support for RX1 DR Offset
- match tx_datarate {
- DR::_0 => DR::_8,
- DR::_1 => DR::_9,
- DR::_2 => DR::_10,
- DR::_3 => DR::_11,
- DR::_4 => DR::_12,
- DR::_5 => DR::_13,
- DR::_6 => DR::_13,
- DR::_7 => DR::_9,
- _ => panic!("Invalid TX datarate"),
- }
- }
- Window::_2 => DR::_8,
- };
- DATARATES[datarate as usize].clone().unwrap()
- }
- fn get_dbm() -> i8 {
- AU_DBM
- }
-}
diff --git a/lorawan-device-patch/src/region/fixed_channel_plans/join_channels.rs b/lorawan-device-patch/src/region/fixed_channel_plans/join_channels.rs
deleted file mode 100644
index d0ee5e1..0000000
--- a/lorawan-device-patch/src/region/fixed_channel_plans/join_channels.rs
+++ /dev/null
@@ -1,418 +0,0 @@
-use super::*;
-use core::cmp::Ordering;
-
-#[derive(Clone, Default)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub(crate) struct JoinChannels {
- /// The maximum amount of times we attempt to join on the preferred subband.
- max_retries: usize,
- /// The amount of times we've currently attempted to join on the preferred subband.
- pub num_retries: usize,
- /// Preferred subband
- preferred_subband: Option,
- /// Channels that have been attempted.
- pub(crate) available_channels: AvailableChannels,
- /// The channel used for the previous join request.
- pub(crate) previous_channel: u8,
-}
-
-impl JoinChannels {
- pub(crate) fn has_bias_and_not_exhausted(&self) -> bool {
- // there are remaining retries AND we have not yet been reset
- self.preferred_subband.is_some()
- && self.num_retries < self.max_retries
- && self.num_retries != 0
- }
-
- /// The first data channel will always be some random channel (possibly the same as previous)
- /// of the preferred subband. Returns None if there is no preferred subband.
- pub(crate) fn first_data_channel(&mut self, rng: &mut impl RngCore) -> Option {
- if self.preferred_subband.is_some() && self.num_retries != 0 {
- self.clear_join_bias();
- // determine which subband the successful join was sent on
- let sb = if self.previous_channel < 64 {
- self.previous_channel / 8
- } else {
- self.previous_channel % 8
- };
- // pick another channel on that subband
- Some((rng.next_u32() & 0b111) as u8 + (sb * 8))
- } else {
- None
- }
- }
-
- pub(crate) fn set_join_bias(&mut self, subband: Subband, max_retries: usize) {
- self.preferred_subband = Some(subband);
- self.max_retries = max_retries;
- }
-
- pub(crate) fn clear_join_bias(&mut self) {
- self.preferred_subband = None;
- self.max_retries = 0;
- }
-
- /// To be called after a join accept is received. Resets state for the next join attempt.
- pub(crate) fn reset(&mut self) {
- self.num_retries = 0;
- self.available_channels = AvailableChannels::default();
- }
-
- pub(crate) fn get_next_channel(&mut self, rng: &mut impl RngCore) -> u8 {
- match (self.preferred_subband, self.num_retries.cmp(&self.max_retries)) {
- (Some(sb), Ordering::Less) => {
- self.num_retries += 1;
- // pick a random number 0-7 on the preferred subband
- // NB: we don't use 500 kHz channels
- let channel = (rng.next_u32() % 8) as u8 + ((sb as usize - 1) as u8 * 8);
- if self.num_retries == self.max_retries {
- // this is our last try with our favorite subband, so will initialize the
- // standard join logic with the channel we just tried. This will ensure
- // standard and compliant behavior when num_retries is set to 1.
- self.available_channels.previous = Some(channel);
- self.available_channels.data.set_channel(channel.into(), false);
- }
- self.previous_channel = channel;
- channel
- }
- _ => {
- self.num_retries += 1;
- self.available_channels.get_next(rng)
- }
- }
- }
-}
-
-#[derive(Clone, Default)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub(crate) struct AvailableChannels {
- data: ChannelMask<9>,
- previous: Option,
-}
-
-impl AvailableChannels {
- fn is_exhausted(&self) -> bool {
- // check if every underlying byte is entirely cleared to 0
- for byte in self.data.as_ref() {
- if *byte != 0 {
- return false;
- }
- }
- true
- }
-
- fn get_next(&mut self, rng: &mut impl RngCore) -> u8 {
- // this guarantees that there will be _some_ open channel available
- if self.is_exhausted() {
- self.reset();
- }
-
- let channel = self.get_next_channel_inner(rng);
- // mark the channel invalid for future selection
- self.data.set_channel(channel.into(), false);
- self.previous = Some(channel);
- channel
- }
-
- fn get_next_channel_inner(&mut self, rng: &mut impl RngCore) -> u8 {
- if let Some(previous) = self.previous {
- // choose the next one by possibly wrapping around
- let next = (previous + 8) % 72;
- // if the channel is valid, great!
- if self.data.is_enabled(next.into()).unwrap() {
- next
- } else {
- // We've wrapped around to our original random bank.
- // Randomly select a new channel on the original bank.
- // NB: there shall always be something because this will be the first
- // bank to get exhausted and the caller of this function will reset
- // when the last one is exhausted.
- let bank = next / 8;
- let mut entropy = rng.next_u32();
- let mut channel = (entropy & 0b111) as u8 + bank * 8;
- let mut entropy_used = 1;
- loop {
- if self.data.is_enabled(channel.into()).unwrap() {
- return channel;
- } else {
- // we've used 30 of the 32 bits of entropy. reset the byte
- if entropy_used == 10 {
- entropy = rng.next_u32();
- entropy_used = 0;
- }
- entropy >>= 3;
- entropy_used += 1;
- channel = (entropy & 0b111) as u8 + bank * 8;
- }
- }
- }
- } else {
- // pick a completely random channel on the bottom 64
- // NB: all channels are currently valid
- (rng.next_u32() as u8) & 0b111111
- }
- }
-
- fn reset(&mut self) {
- self.data = ChannelMask::default();
- self.previous = None;
- }
-}
-
-/// This macro implements public functions relating to a fixed plan region. This is preferred to a
-/// trait implementation because the user does not have to worry about importing the trait to make
-/// use of these functions.
-macro_rules! impl_join_bias {
- ($region:ident) => {
- impl $region {
- /// Create this struct directly if you want to specify a subband on which to bias the join process.
- pub fn new() -> Self {
- Self::default()
- }
-
- /// Specify a preferred subband when joining the network. Only the first join attempt
- /// will occur on this subband. After that, each bank will be attempted sequentially
- /// as described in the US915/AU915 regional specifications.
- pub fn set_join_bias(&mut self, subband: Subband) {
- self.0.join_channels.set_join_bias(subband, 1)
- }
-
- /// # ⚠️Warning⚠️
- ///
- /// This method is explicitly not compliant with the LoRaWAN spec when more than one
- /// try is attempted.
- ///
- /// This method is similar to `set_join_bias`, but allows you to specify a potentially
- /// non-compliant amount of times your preferred join subband should be attempted.
- ///
- /// It is recommended to set a low number (ie, < 10) of join retries using the
- /// preferred subband. The reason for this is if you *only* try to join
- /// with a channel bias, and the network is configured to use a
- /// strictly different set of channels than the ones you provide, the
- /// network will NEVER be joined.
- pub fn set_join_bias_and_noncompliant_retries(
- &mut self,
- subband: Subband,
- max_retries: usize,
- ) {
- self.0.join_channels.set_join_bias(subband, max_retries)
- }
-
- pub fn clear_join_bias(&mut self) {
- self.0.join_channels.clear_join_bias()
- }
- }
- };
-}
-
-#[cfg(feature = "region-au915")]
-impl_join_bias!(AU915);
-#[cfg(feature = "region-us915")]
-impl_join_bias!(US915);
-
-#[cfg(test)]
-mod test {
- use super::*;
- use crate::mac::Response;
- use crate::{
- mac::{Mac, SendData},
- test_util::{get_key, handle_join_request, Uplink},
- AppEui, AppKey, DevEui, NetworkCredentials,
- };
- use heapless::Vec;
- use lorawan::default_crypto::DefaultFactory;
-
- #[test]
- fn test_join_channels_standard() {
- let mut rng = rand_core::OsRng;
- // run the test a bunch of times due to the rng
- for _ in 0..100 {
- let mut join_channels = JoinChannels::default();
- let first_channel = join_channels.get_next_channel(&mut rng);
- // the first channel is always in the bottom 64
- assert!(first_channel < 64);
- let next_channel = join_channels.get_next_channel(&mut rng);
- // the next channel is always incremented by 8, since we always have
- // the fat bank (channels 64-71)
- assert_eq!(next_channel, first_channel + 8);
- // we generate 6 more channels
- for _ in 0..7 {
- let c = join_channels.get_next_channel(&mut rng);
- assert!(c < 72);
- }
- // after 8 tries, we should be back at the original bank but on a different channel
- let ninth_channel = join_channels.get_next_channel(&mut rng);
- assert_eq!(ninth_channel / 8, first_channel / 8);
- assert_ne!(ninth_channel, first_channel);
- }
- }
-
- #[test]
- fn test_join_channels_standard_exhausted() {
- let mut rng = rand_core::OsRng;
-
- let mut join_channels = JoinChannels::default();
- let first_channel = join_channels.get_next_channel(&mut rng);
- // the first channel is always in the bottom 64
- assert!(first_channel < 64);
- let next_channel = join_channels.get_next_channel(&mut rng);
- // the next channel is always incremented by 8, since we always have
- // the fat bank (channels 64-71)
- assert_eq!(next_channel, first_channel + 8);
- // we generate 6000
- for _ in 0..6000 {
- let c = join_channels.get_next_channel(&mut rng);
- assert!(c < 72);
- }
- }
-
- #[test]
- fn test_join_channels_biased() {
- let mut rng = rand_core::OsRng;
- // run the test a bunch of times due to the rng
- for _ in 0..100 {
- let mut join_channels = JoinChannels::default();
- join_channels.set_join_bias(Subband::_2, 1);
- let first_channel = join_channels.get_next_channel(&mut rng);
- // the first is on subband 2
- assert!(first_channel > 7);
- assert!(first_channel < 16);
- let next_channel = join_channels.get_next_channel(&mut rng);
- // the next channel is always incremented by 8, since we always have
- // the fat bank (channels 64-71)
- assert_eq!(next_channel, first_channel + 8);
- // we generate 6 more channels
- for _ in 0..7 {
- let c = join_channels.get_next_channel(&mut rng);
- assert!(c < 72);
- }
- // after 8 tries, we should be back at the biased bank but on a different channel
- let ninth_channel = join_channels.get_next_channel(&mut rng);
- assert_eq!(ninth_channel / 8, first_channel / 8);
- assert_ne!(ninth_channel, first_channel);
- }
- }
-
- #[test]
- fn test_full_mac_compliant_bias() {
- let mut us915 = US915::new();
- us915.set_join_bias(Subband::_2);
- let mut mac = Mac::new(us915.into(), 21, 2);
-
- let mut buf: RadioBuffer<255> = RadioBuffer::new();
- let (tx_config, _len) = mac.join_otaa::(
- &mut rand::rngs::OsRng,
- NetworkCredentials::new(
- AppEui::from([0x0; 8]),
- DevEui::from([0x0; 8]),
- AppKey::from(get_key()),
- ),
- &mut buf,
- );
- // Confirm that the join request occurs on our subband
- assert!(
- tx_config.rf.frequency >= 903_900_000,
- "Unexpected frequency: {} is below 903.9 MHz!",
- tx_config.rf.frequency
- );
- assert!(
- tx_config.rf.frequency <= 905_300_000,
- "Unexpected frequency: {} is above 905.3 MHz!",
- tx_config.rf.frequency
- );
- let mut downlinks: Vec<_, 3> = Vec::new();
- let mut data = std::vec::Vec::new();
- data.extend_from_slice(buf.as_ref_for_read());
- let uplink = Uplink::new(buf.as_ref_for_read(), tx_config).unwrap();
-
- let mut rx_buf = [0; 255];
- let len = handle_join_request::<0>(Some(uplink), tx_config.rf, &mut rx_buf);
- buf.clear();
- buf.extend_from_slice(&rx_buf[..len]).unwrap();
- let response = mac.handle_rx::(&mut buf, &mut downlinks);
- if let Response::JoinSuccess = response {} else {
- panic!("Did not receive join success");
- }
- let (tx_config, _len) = mac
- .send::(
- &mut rand::rngs::OsRng,
- &mut buf,
- &SendData { fport: 1, data: &[0x0; 1], confirmed: false },
- )
- .unwrap();
- // Confirm that the first data frame occurs on our subband
- assert!(
- tx_config.rf.frequency >= 903_900_000,
- "Unexpected frequency: {} is below 903.9 MHz!",
- tx_config.rf.frequency
- );
- assert!(
- tx_config.rf.frequency <= 905_300_000,
- "Unexpected frequency: {} is above 905.3 MHz!",
- tx_config.rf.frequency
- );
- }
-
- #[test]
- fn test_full_mac_non_compliant_bias() {
- let mut us915 = US915::new();
- us915.set_join_bias_and_noncompliant_retries(Subband::_2, 8);
- let mut mac = Mac::new(us915.into(), 21, 2);
-
- let mut buf: RadioBuffer<255> = RadioBuffer::new();
- let (tx_config, _len) = mac.join_otaa::(
- &mut rand::rngs::OsRng,
- NetworkCredentials::new(
- AppEui::from([0x0; 8]),
- DevEui::from([0x0; 8]),
- AppKey::from(get_key()),
- ),
- &mut buf,
- );
- // Confirm that the join request occurs on our subband
- assert!(
- tx_config.rf.frequency >= 903_900_000,
- "Unexpected frequency: {} is below 903.9 MHz!",
- tx_config.rf.frequency
- );
- assert!(
- tx_config.rf.frequency <= 905_300_000,
- "Unexpected frequency: {} is above 905.3 MHz!",
- tx_config.rf.frequency
- );
- let mut downlinks: Vec<_, 3> = Vec::new();
- let mut data = std::vec::Vec::new();
- data.extend_from_slice(buf.as_ref_for_read());
- let uplink = Uplink::new(buf.as_ref_for_read(), tx_config).unwrap();
-
- let mut rx_buf = [0; 255];
- let len = handle_join_request::<0>(Some(uplink), tx_config.rf, &mut rx_buf);
- buf.clear();
- buf.extend_from_slice(&rx_buf[..len]).unwrap();
- let response = mac.handle_rx::(&mut buf, &mut downlinks);
- if let Response::JoinSuccess = response {} else {
- panic!("Did not receive JoinSuccess")
- }
- for _ in 0..8 {
- let (tx_config, _len) = mac
- .send::(
- &mut rand::rngs::OsRng,
- &mut buf,
- &SendData { fport: 1, data: &[0x0; 1], confirmed: false },
- )
- .unwrap();
- // Confirm that the first data frame occurs on our subband
- assert!(
- tx_config.rf.frequency >= 903_900_000,
- "Unexpected frequency: {} is below 903.9 MHz!",
- tx_config.rf.frequency
- );
- assert!(
- tx_config.rf.frequency <= 905_300_000,
- "Unexpected frequency: {} is above 905.3 MHz!",
- tx_config.rf.frequency
- );
- mac.rx2_complete();
- }
- }
-}
diff --git a/lorawan-device-patch/src/region/fixed_channel_plans/mod.rs b/lorawan-device-patch/src/region/fixed_channel_plans/mod.rs
deleted file mode 100644
index 7fb0f1a..0000000
--- a/lorawan-device-patch/src/region/fixed_channel_plans/mod.rs
+++ /dev/null
@@ -1,205 +0,0 @@
-use super::*;
-use core::marker::PhantomData;
-use lorawan::maccommands::ChannelMask;
-
-mod join_channels;
-use join_channels::JoinChannels;
-
-#[cfg(feature = "region-au915")]
-mod au915;
-#[cfg(feature = "region-us915")]
-mod us915;
-
-#[cfg(feature = "region-au915")]
-pub use au915::AU915;
-#[cfg(feature = "region-us915")]
-pub use us915::US915;
-
-seq_macro::seq!(
- N in 1..=8 {
- /// Subband definitions used to bias the join process of a fixed channel plan (ie: US915, AU915).
- /// Each Subband holds 8 channels. eg: subband 1 contains: channels 0-7, subband 2: channels 8-15, etc.
- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
- #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
- #[repr(usize)]
- pub enum Subband {
- #(
- _~N = N,
- )*
- }
- }
-);
-
-impl From for usize {
- fn from(value: Subband) -> Self {
- value as usize
- }
-}
-
-#[derive(Default, Clone)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-pub(crate) struct FixedChannelPlan> {
- last_tx_channel: u8,
- channel_mask: ChannelMask<9>,
- _fixed_channel_region: PhantomData,
- join_channels: JoinChannels,
-}
-
-impl> FixedChannelPlan {
- pub fn set_125k_channels(&mut self, enabled: bool) {
- let mask = if enabled {
- 0xFF
- } else {
- 0x00
- };
- self.channel_mask.set_bank(0, mask);
- self.channel_mask.set_bank(1, mask);
- self.channel_mask.set_bank(2, mask);
- self.channel_mask.set_bank(3, mask);
- self.channel_mask.set_bank(4, mask);
- self.channel_mask.set_bank(5, mask);
- self.channel_mask.set_bank(6, mask);
- self.channel_mask.set_bank(7, mask);
- }
-
- #[allow(unused)]
- pub fn get_max_payload_length(datarate: DR, repeater_compatible: bool, dwell_time: bool) -> u8 {
- F::get_max_payload_length(datarate, repeater_compatible, dwell_time)
- }
-}
-
-pub(crate) trait FixedChannelRegion: ChannelRegion {
- fn uplink_channels() -> &'static [u32; 72];
- fn downlink_channels() -> &'static [u32; 8];
- fn get_default_rx2() -> u32;
- fn get_rx_datarate(tx_datarate: DR, frame: &Frame, window: &Window) -> Datarate;
- fn get_dbm() -> i8;
-}
-
-impl> RegionHandler for FixedChannelPlan {
- fn process_join_accept, C>(
- &mut self,
- join_accept: &DecryptedJoinAcceptPayload,
- ) {
- if let Some(CfList::FixedChannel(channel_mask)) = join_accept.c_f_list() {
- // Reset the join channels state
- self.join_channels.reset();
- self.channel_mask = channel_mask;
- }
- }
-
- fn handle_link_adr_channel_mask(
- &mut self,
- channel_mask_control: u8,
- channel_mask: ChannelMask<2>,
- ) {
- self.join_channels.reset();
- match channel_mask_control {
- 0..=4 => {
- let base_index = channel_mask_control as usize * 2;
- self.channel_mask.set_bank(base_index, channel_mask.get_index(0));
- self.channel_mask.set_bank(base_index + 1, channel_mask.get_index(1));
- }
- 5 => {
- let channel_mask: u16 =
- channel_mask.get_index(0) as u16 | ((channel_mask.get_index(1) as u16) << 8);
- self.channel_mask.set_bank(0, ((channel_mask & 0b1) * 0xFF) as u8);
- self.channel_mask.set_bank(1, ((channel_mask & 0b10) * 0xFF) as u8);
- self.channel_mask.set_bank(2, ((channel_mask & 0b100) * 0xFF) as u8);
- self.channel_mask.set_bank(3, ((channel_mask & 0b1000) * 0xFF) as u8);
- self.channel_mask.set_bank(4, ((channel_mask & 0b10000) * 0xFF) as u8);
- self.channel_mask.set_bank(5, ((channel_mask & 0b100000) * 0xFF) as u8);
- self.channel_mask.set_bank(6, ((channel_mask & 0b1000000) * 0xFF) as u8);
- self.channel_mask.set_bank(7, ((channel_mask & 0b10000000) * 0xFF) as u8);
- self.channel_mask.set_bank(8, ((channel_mask & 0b100000000) * 0xFF) as u8);
- }
- 6 => {
- self.set_125k_channels(true);
- }
- 7 => {
- self.set_125k_channels(false);
- }
- _ => {
- //RFU
- }
- }
- }
-
- fn get_tx_dr_and_frequency(
- &mut self,
- rng: &mut RNG,
- datarate: DR,
- frame: &Frame,
- ) -> (Datarate, u32) {
- match frame {
- Frame::Join => {
- let channel = self.join_channels.get_next_channel(rng);
- let dr = if channel < 64 {
- DR::_0
- } else {
- DR::_4
- };
- self.last_tx_channel = channel;
- let data_rate = F::datarates()[dr as usize].clone().unwrap();
- (data_rate, F::uplink_channels()[channel as usize])
- }
- Frame::Data => {
- // The join bias gets reset after receiving CFList in Join Frame
- // or ChannelMask in the LinkADRReq in Data Frame.
- // If it has not been reset yet, we continue to use the bias for the data frames.
- // We hope to acquire ChannelMask via LinkADRReq.
- let (data_rate, channel) = if self.join_channels.has_bias_and_not_exhausted() {
- let channel = self.join_channels.get_next_channel(rng);
- let dr = if channel < 64 {
- DR::_0
- } else {
- DR::_4
- };
- (F::datarates()[dr as usize].clone().unwrap(), channel)
- // Alternatively, we will ask JoinChannel logic to determine a channel from the
- // subband that the join succeeded on.
- } else if let Some(channel) = self.join_channels.first_data_channel(rng) {
- (F::datarates()[datarate as usize].clone().unwrap(), channel)
- } else {
- // For the data frame, the datarate impacts which channel sets we can choose
- // from. If the datarate bandwidth is 500 kHz, we must use
- // channels 64-71. Else, we must use 0-63
- let datarate = F::datarates()[datarate as usize].clone().unwrap();
- if datarate.bandwidth == Bandwidth::_500KHz {
- let mut channel = (rng.next_u32() & 0b111) as u8;
- // keep selecting a random channel until we find one that is enabled
- while !self.channel_mask.is_enabled(channel.into()).unwrap() {
- channel = (rng.next_u32() & 0b111) as u8;
- }
- (datarate, 64 + channel)
- } else {
- let mut channel = (rng.next_u32() & 0b111111) as u8;
- // keep selecting a random channel until we find one that is enabled
- while !self.channel_mask.is_enabled(channel.into()).unwrap() {
- channel = (rng.next_u32() & 0b111111) as u8;
- }
- (datarate, channel)
- }
- };
- self.last_tx_channel = channel;
- (data_rate, F::uplink_channels()[channel as usize])
- }
- }
- }
-
- fn get_rx_frequency(&self, _frame: &Frame, window: &Window) -> u32 {
- let channel = self.last_tx_channel % 8;
- match window {
- Window::_1 => F::downlink_channels()[channel as usize],
- Window::_2 => F::get_default_rx2(),
- }
- }
-
- fn get_dbm(&self) -> i8 {
- F::get_dbm()
- }
-
- fn get_rx_datarate(&self, tx_datarate: DR, frame: &Frame, window: &Window) -> Datarate {
- F::get_rx_datarate(tx_datarate, frame, window)
- }
-}
diff --git a/lorawan-device-patch/src/region/fixed_channel_plans/us915/datarates.rs b/lorawan-device-patch/src/region/fixed_channel_plans/us915/datarates.rs
deleted file mode 100644
index 1a0453c..0000000
--- a/lorawan-device-patch/src/region/fixed_channel_plans/us915/datarates.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-use super::{Bandwidth, Datarate, SpreadingFactor};
-
-pub(crate) const DATARATES: [Option; 14] = [
- Some(Datarate {
- spreading_factor: SpreadingFactor::_10,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 19,
- max_mac_payload_size_with_dwell_time: 19,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_9,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 61,
- max_mac_payload_size_with_dwell_time: 61,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_8,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 133,
- max_mac_payload_size_with_dwell_time: 133,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_7,
- bandwidth: Bandwidth::_125KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_8,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- None, // TODO: defined in rp002-1-0-4
- None, // TODO: defined in rp002-1-0-4
- None,
- Some(Datarate {
- spreading_factor: SpreadingFactor::_12,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 61,
- max_mac_payload_size_with_dwell_time: 61,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_11,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 137,
- max_mac_payload_size_with_dwell_time: 137,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_10,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_9,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_8,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
- Some(Datarate {
- spreading_factor: SpreadingFactor::_7,
- bandwidth: Bandwidth::_500KHz,
- max_mac_payload_size: 250,
- max_mac_payload_size_with_dwell_time: 250,
- }),
-];
diff --git a/lorawan-device-patch/src/region/fixed_channel_plans/us915/frequencies.rs b/lorawan-device-patch/src/region/fixed_channel_plans/us915/frequencies.rs
deleted file mode 100644
index 6b1a681..0000000
--- a/lorawan-device-patch/src/region/fixed_channel_plans/us915/frequencies.rs
+++ /dev/null
@@ -1,94 +0,0 @@
-pub(crate) const UPLINK_CHANNEL_MAP: [u32; 72] = [
- // channels 0-7 (125 kHz)
- 902_300_000,
- 902_500_000,
- 902_700_000,
- 902_900_000,
- 903_100_000,
- 903_300_000,
- 903_500_000,
- 903_700_000,
- // channels 8-15 (125 kHz)
- 903_900_000,
- 904_100_000,
- 904_300_000,
- 904_500_000,
- 904_700_000,
- 904_900_000,
- 905_100_000,
- 905_300_000,
- // channels 16-23 (125 kHz)
- 905_500_000,
- 905_700_000,
- 905_900_000,
- 906_100_000,
- 906_300_000,
- 906_500_000,
- 906_700_000,
- 906_900_000,
- // channels 24-31 (125 kHz)
- 907_100_000,
- 907_300_000,
- 907_500_000,
- 907_700_000,
- 907_900_000,
- 908_100_000,
- 908_300_000,
- 908_500_000,
- // channels 32-39 (125 kHz)
- 908_700_000,
- 908_900_000,
- 909_100_000,
- 909_300_000,
- 909_500_000,
- 909_700_000,
- 909_900_000,
- 910_100_000,
- // channels 39-47 (125 kHz)
- 910_300_000,
- 910_500_000,
- 910_700_000,
- 910_900_000,
- 911_100_000,
- 911_300_000,
- 911_500_000,
- 911_700_000,
- // channels 47-55 (125 kHz)
- 911_900_000,
- 912_100_000,
- 912_300_000,
- 912_500_000,
- 912_700_000,
- 912_900_000,
- 913_100_000,
- 913_300_000,
- // channels 55-63
- 913_500_000,
- 913_700_000,
- 913_900_000,
- 914_100_000,
- 914_300_000,
- 914_500_000,
- 914_700_000,
- 914_900_000,
- // channels 63-71 (500 kHz)
- 903_000_000,
- 904_600_000,
- 906_200_000,
- 907_800_000,
- 909_400_000,
- 911_000_000,
- 912_600_000,
- 914_200_000,
-];
-
-pub(crate) const DOWNLINK_CHANNEL_MAP: [u32; 8] = [
- 923_300_000,
- 923_900_000,
- 924_500_000,
- 925_100_000,
- 925_700_000,
- 926_300_000,
- 926_900_000,
- 927_500_000,
-];
diff --git a/lorawan-device-patch/src/region/fixed_channel_plans/us915/mod.rs b/lorawan-device-patch/src/region/fixed_channel_plans/us915/mod.rs
deleted file mode 100644
index 17e4701..0000000
--- a/lorawan-device-patch/src/region/fixed_channel_plans/us915/mod.rs
+++ /dev/null
@@ -1,81 +0,0 @@
-use super::*;
-
-mod frequencies;
-use frequencies::*;
-
-mod datarates;
-use datarates::*;
-
-const US_DBM: i8 = 21;
-const DEFAULT_RX2: u32 = 923_300_000;
-
-/// State struct for the `US915` region. This struct may be created directly if you wish to fine-tune some parameters.
-/// At this time specifying a bias for the subband used during the join process is supported using
-/// [`set_join_bias`](Self::set_join_bias) and [`set_join_bias_and_noncompliant_retries`](Self::set_join_bias_and_noncompliant_retries)
-/// is suppored. This struct can then be turned into a [`Configuration`] as it implements [`Into`].
-///
-/// # Note:
-///
-/// Only [`US915`] and [`AU915`] can be created using this method, because they are the only ones which have
-/// parameters that may be fine-tuned at the region level. To create a [`Configuration`] for other regions, use
-/// [`Configuration::new`] and specify the region using the [`Region`] enum.
-///
-/// # Example: Setting up join bias
-///
-/// ```
-/// use lorawan_device::region::{Configuration, US915, Subband};
-///
-/// let mut us915 = US915::new();
-/// // Subband 2 is commonly used for The Things Network.
-/// us915.set_join_bias(Subband::_2);
-/// let configuration: Configuration = us915.into();
-/// ```
-#[derive(Default, Clone)]
-pub struct US915(pub(crate) FixedChannelPlan<14, US915Region>);
-
-impl US915 {
- pub fn get_max_payload_length(datarate: DR, repeater_compatible: bool, dwell_time: bool) -> u8 {
- US915Region::get_max_payload_length(datarate, repeater_compatible, dwell_time)
- }
-}
-
-#[derive(Default, Clone)]
-pub(crate) struct US915Region;
-
-impl ChannelRegion<14> for US915Region {
- fn datarates() -> &'static [Option; 14] {
- &DATARATES
- }
-}
-
-impl FixedChannelRegion<14> for US915Region {
- fn uplink_channels() -> &'static [u32; 72] {
- &UPLINK_CHANNEL_MAP
- }
- fn downlink_channels() -> &'static [u32; 8] {
- &DOWNLINK_CHANNEL_MAP
- }
- fn get_default_rx2() -> u32 {
- DEFAULT_RX2
- }
- fn get_rx_datarate(tx_datarate: DR, _frame: &Frame, window: &Window) -> Datarate {
- let datarate = match window {
- Window::_1 => {
- // no support for RX1 DR Offset
- match tx_datarate {
- DR::_0 => DR::_10,
- DR::_1 => DR::_11,
- DR::_2 => DR::_12,
- DR::_3 => DR::_13,
- DR::_4 => DR::_13,
- _ => panic!("Invalid TX datarate"),
- }
- }
- Window::_2 => DR::_8,
- };
- DATARATES[datarate as usize].clone().unwrap()
- }
- fn get_dbm() -> i8 {
- US_DBM
- }
-}
diff --git a/lorawan-device-patch/src/region/mod.rs b/lorawan-device-patch/src/region/mod.rs
deleted file mode 100644
index a924caa..0000000
--- a/lorawan-device-patch/src/region/mod.rs
+++ /dev/null
@@ -1,528 +0,0 @@
-//! LoRaWAN device region definitions (eg: EU868, US915, etc).
-use lora_modulation::{Bandwidth, BaseBandModulationParams, CodingRate, SpreadingFactor};
-use lorawan::{maccommands::ChannelMask, parser::CfList};
-use rand_core::RngCore;
-
-use crate::mac::{Frame, Window};
-pub(crate) mod constants;
-pub(crate) use crate::radio::*;
-use constants::*;
-
-#[cfg(not(any(
- feature = "region-as923-1",
- feature = "region-as923-2",
- feature = "region-as923-3",
- feature = "region-as923-4",
- feature = "region-eu433",
- feature = "region-eu868",
- feature = "region-in865",
- feature = "region-au915",
- feature = "region-us915"
-)))]
-compile_error!("You must enable at least one region! eg: `region-eu868`, `region-us915`...");
-
-#[cfg(any(
- feature = "region-as923-1",
- feature = "region-as923-2",
- feature = "region-as923-3",
- feature = "region-as923-4",
- feature = "region-eu433",
- feature = "region-eu868",
- feature = "region-in865"
-))]
-mod dynamic_channel_plans;
-#[cfg(feature = "region-as923-1")]
-pub(crate) use dynamic_channel_plans::AS923_1;
-#[cfg(feature = "region-as923-2")]
-pub(crate) use dynamic_channel_plans::AS923_2;
-#[cfg(feature = "region-as923-3")]
-pub(crate) use dynamic_channel_plans::AS923_3;
-#[cfg(feature = "region-as923-4")]
-pub(crate) use dynamic_channel_plans::AS923_4;
-#[cfg(feature = "region-eu433")]
-pub(crate) use dynamic_channel_plans::EU433;
-#[cfg(feature = "region-eu868")]
-pub(crate) use dynamic_channel_plans::EU868;
-#[cfg(feature = "region-in865")]
-pub(crate) use dynamic_channel_plans::IN865;
-
-#[cfg(any(feature = "region-us915", feature = "region-au915"))]
-mod fixed_channel_plans;
-#[cfg(any(feature = "region-us915", feature = "region-au915"))]
-pub use fixed_channel_plans::Subband;
-#[cfg(feature = "region-au915")]
-pub use fixed_channel_plans::AU915;
-#[cfg(feature = "region-us915")]
-pub use fixed_channel_plans::US915;
-
-pub(crate) trait ChannelRegion {
- fn datarates() -> &'static [Option; D];
-
- fn get_max_payload_length(datarate: DR, repeater_compatible: bool, dwell_time: bool) -> u8 {
- let Some(Some(dr)) = Self::datarates().get(datarate as usize) else {
- return 0;
- };
- let max_size = if dwell_time {
- dr.max_mac_payload_size_with_dwell_time
- } else {
- dr.max_mac_payload_size
- };
- if repeater_compatible && max_size > 230 {
- 230
- } else {
- max_size
- }
- }
-}
-
-#[derive(Clone)]
-/// Contains LoRaWAN region-specific configuration; is required for creating a LoRaWAN Device.
-/// Generally constructed using the `Region` enum, unless you need to fine-tune US915 or AU915.
-pub struct Configuration {
- state: State,
-}
-
-seq_macro::seq!(
- N in 0..=15 {
- #[derive(Debug, Clone, Copy, PartialEq, Eq)]
- #[cfg_attr(feature = "defmt", derive(defmt::Format))]
- #[repr(u8)]
- /// A restricted data rate type that exposes the number of variants to only what _may_ be
- /// potentially be possible. Note that not all data rates are valid in all regions.
- pub enum DR {
- #(
- _~N = N,
- )*
- }
- }
-);
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-/// Regions supported by this crate: AS923_1, AS923_2, AS923_3, AS923_4, AU915, EU868, EU433, IN865, US915.
-/// Each region is individually feature-gated (eg: `region-eu868`), however, by default, all regions are enabled.
-///
-pub enum Region {
- #[cfg(feature = "region-as923-1")]
- AS923_1,
- #[cfg(feature = "region-as923-2")]
- AS923_2,
- #[cfg(feature = "region-as923-3")]
- AS923_3,
- #[cfg(feature = "region-as923-4")]
- AS923_4,
- #[cfg(feature = "region-au915")]
- AU915,
- #[cfg(feature = "region-eu868")]
- EU868,
- #[cfg(feature = "region-eu433")]
- EU433,
- #[cfg(feature = "region-in865")]
- IN865,
- #[cfg(feature = "region-us915")]
- US915,
-}
-
-#[derive(Clone)]
-enum State {
- #[cfg(feature = "region-as923-1")]
- AS923_1(AS923_1),
- #[cfg(feature = "region-as923-2")]
- AS923_2(AS923_2),
- #[cfg(feature = "region-as923-3")]
- AS923_3(AS923_3),
- #[cfg(feature = "region-as923-4")]
- AS923_4(AS923_4),
- #[cfg(feature = "region-au915")]
- AU915(AU915),
- #[cfg(feature = "region-eu868")]
- EU868(EU868),
- #[cfg(feature = "region-eu433")]
- EU433(EU433),
- #[cfg(feature = "region-in865")]
- IN865(IN865),
- #[cfg(feature = "region-us915")]
- US915(US915),
-}
-
-impl State {
- pub fn new(region: Region) -> State {
- match region {
- #[cfg(feature = "region-as923-1")]
- Region::AS923_1 => State::AS923_1(AS923_1::default()),
- #[cfg(feature = "region-as923-2")]
- Region::AS923_2 => State::AS923_2(AS923_2::default()),
- #[cfg(feature = "region-as923-3")]
- Region::AS923_3 => State::AS923_3(AS923_3::default()),
- #[cfg(feature = "region-as923-4")]
- Region::AS923_4 => State::AS923_4(AS923_4::default()),
- #[cfg(feature = "region-au915")]
- Region::AU915 => State::AU915(AU915::default()),
- #[cfg(feature = "region-eu868")]
- Region::EU868 => State::EU868(EU868::default()),
- #[cfg(feature = "region-eu433")]
- Region::EU433 => State::EU433(EU433::default()),
- #[cfg(feature = "region-in865")]
- Region::IN865 => State::IN865(IN865::default()),
- #[cfg(feature = "region-us915")]
- Region::US915 => State::US915(US915::default()),
- }
- }
-
- #[allow(dead_code)]
- pub fn region(&self) -> Region {
- match self {
- #[cfg(feature = "region-as923-1")]
- Self::AS923_1(_) => Region::AS923_1,
- #[cfg(feature = "region-as923-2")]
- Self::AS923_2(_) => Region::AS923_2,
- #[cfg(feature = "region-as923-3")]
- Self::AS923_3(_) => Region::AS923_3,
- #[cfg(feature = "region-as923-4")]
- Self::AS923_4(_) => Region::AS923_4,
- #[cfg(feature = "region-au915")]
- Self::AU915(_) => Region::AU915,
- #[cfg(feature = "region-eu433")]
- Self::EU433(_) => Region::EU433,
- #[cfg(feature = "region-eu868")]
- Self::EU868(_) => Region::EU868,
- #[cfg(feature = "region-in865")]
- Self::IN865(_) => Region::IN865,
- #[cfg(feature = "region-us915")]
- Self::US915(_) => Region::US915,
- }
- }
-}
-
-/// This datarate type is used internally for defining bandwidth/sf per region
-#[derive(Debug, Clone)]
-pub(crate) struct Datarate {
- bandwidth: Bandwidth,
- spreading_factor: SpreadingFactor,
- max_mac_payload_size: u8,
- max_mac_payload_size_with_dwell_time: u8,
-}
-macro_rules! mut_region_dispatch {
- ($s:expr, $t:tt) => {
- match &mut $s.state {
- #[cfg(feature = "region-as923-1")]
- State::AS923_1(state) => state.$t(),
- #[cfg(feature = "region-as923-2")]
- State::AS923_2(state) => state.$t(),
- #[cfg(feature = "region-as923-3")]
- State::AS923_3(state) => state.$t(),
- #[cfg(feature = "region-as923-4")]
- State::AS923_4(state) => state.$t(),
- #[cfg(feature = "region-au915")]
- State::AU915(state) => state.0.$t(),
- #[cfg(feature = "region-eu868")]
- State::EU868(state) => state.$t(),
- #[cfg(feature = "region-eu433")]
- State::EU433(state) => state.$t(),
- #[cfg(feature = "region-in865")]
- State::IN865(state) => state.$t(),
- #[cfg(feature = "region-us915")]
- State::US915(state) => state.0.$t(),
- }
- };
- ($s:expr, $t:tt, $($arg:tt)*) => {
- match &mut $s.state {
- #[cfg(feature = "region-as923-1")]
- State::AS923_1(state) => state.$t($($arg)*),
- #[cfg(feature = "region-as923-2")]
- State::AS923_2(state) => state.$t($($arg)*),
- #[cfg(feature = "region-as923-3")]
- State::AS923_3(state) => state.$t($($arg)*),
- #[cfg(feature = "region-as923-4")]
- State::AS923_4(state) => state.$t($($arg)*),
- #[cfg(feature = "region-au915")]
- State::AU915(state) => state.0.$t($($arg)*),
- #[cfg(feature = "region-eu868")]
- State::EU868(state) => state.$t($($arg)*),
- #[cfg(feature = "region-eu433")]
- State::EU433(state) => state.$t($($arg)*),
- #[cfg(feature = "region-in865")]
- State::IN865(state) => state.$t($($arg)*),
- #[cfg(feature = "region-us915")]
- State::US915(state) => state.0.$t($($arg)*),
- }
- };
-}
-
-macro_rules! region_dispatch {
- ($s:expr, $t:tt) => {
- match &$s.state {
- #[cfg(feature = "region-as923-1")]
- State::AS923_1(state) => state.$t(),
- #[cfg(feature = "region-as923-2")]
- State::AS923_2(state) => state.$t(),
- #[cfg(feature = "region-as923-3")]
- State::AS923_3(state) => state.$t(),
- #[cfg(feature = "region-as923-4")]
- State::AS923_4(state) => state.$t(),
- #[cfg(feature = "region-au915")]
- State::AU915(state) => state.0.$t(),
- #[cfg(feature = "region-eu868")]
- State::EU868(state) => state.$t(),
- #[cfg(feature = "region-eu433")]
- State::EU433(state) => state.$t(),
- #[cfg(feature = "region-in865")]
- State::IN865(state) => state.$t(),
- #[cfg(feature = "region-us915")]
- State::US915(state) => state.0.$t(),
- }
- };
- ($s:expr, $t:tt, $($arg:tt)*) => {
- match &$s.state {
- #[cfg(feature = "region-as923-1")]
- State::AS923_1(state) => state.$t($($arg)*),
- #[cfg(feature = "region-as923-2")]
- State::AS923_2(state) => state.$t($($arg)*),
- #[cfg(feature = "region-as923-3")]
- State::AS923_3(state) => state.$t($($arg)*),
- #[cfg(feature = "region-as923-4")]
- State::AS923_4(state) => state.$t($($arg)*),
- #[cfg(feature = "region-au915")]
- State::AU915(state) => state.0.$t($($arg)*),
- #[cfg(feature = "region-eu868")]
- State::EU868(state) => state.$t($($arg)*),
- #[cfg(feature = "region-eu433")]
- State::EU433(state) => state.$t($($arg)*),
- #[cfg(feature = "region-in865")]
- State::IN865(state) => state.$t($($arg)*),
- #[cfg(feature = "region-us915")]
- State::US915(state) => state.0.$t($($arg)*),
- }
- };
-}
-
-macro_rules! region_static_dispatch {
- ($s:expr, $t:tt) => {
- match &$s.state {
- #[cfg(feature = "region-as923-1")]
- State::AS923_1(_) => dynamic_channel_plans::AS923_1::$t(),
- #[cfg(feature = "region-as923-2")]
- State::AS923_2(_) => dynamic_channel_plans::AS923_2::$t(),
- #[cfg(feature = "region-as923-3")]
- State::AS923_3(_) => dynamic_channel_plans::AS923_3::$t(),
- #[cfg(feature = "region-as923-4")]
- State::AS923_4(_) => dynamic_channel_plans::AS923_4::$t(),
- #[cfg(feature = "region-au915")]
- State::AU915(_) => fixed_channel_plans::AU915::$t(),
- #[cfg(feature = "region-eu868")]
- State::EU868(_) => dynamic_channel_plans::EU868::$t(),
- #[cfg(feature = "region-eu433")]
- State::EU433(_) => dynamic_channel_plans::EU433::$t(),
- #[cfg(feature = "region-in865")]
- State::IN865(_) => dynamic_channel_plans::IN865::$t(),
- #[cfg(feature = "region-us915")]
- State::US915(_) => fixed_channel_plans::US915::$t(),
- }
- };
- ($s:expr, $t:tt, $($arg:tt)*) => {
- match &$s.state {
- #[cfg(feature = "region-as923-1")]
- State::AS923_1(_) => dynamic_channel_plans::AS923_1::$t($($arg)*),
- #[cfg(feature = "region-as923-2")]
- State::AS923_2(_) => dynamic_channel_plans::AS923_2::$t($($arg)*),
- #[cfg(feature = "region-as923-3")]
- State::AS923_3(_) => dynamic_channel_plans::AS923_3::$t($($arg)*),
- #[cfg(feature = "region-as923-4")]
- State::AS923_4(_) => dynamic_channel_plans::AS923_4::$t($($arg)*),
- #[cfg(feature = "region-au915")]
- State::AU915(_) => fixed_channel_plans::AU915::$t($($arg)*),
- #[cfg(feature = "region-eu868")]
- State::EU868(_) => dynamic_channel_plans::EU868::$t($($arg)*),
- #[cfg(feature = "region-eu433")]
- State::EU433(_) => dynamic_channel_plans::EU433::$t($($arg)*),
- #[cfg(feature = "region-in865")]
- State::IN865(_) => dynamic_channel_plans::IN865::$t($($arg)*),
- #[cfg(feature = "region-us915")]
- State::US915(_) => fixed_channel_plans::US915::$t($($arg)*),
- }
- };
-}
-
-impl Configuration {
- pub fn new(region: Region) -> Configuration {
- Configuration::with_state(State::new(region))
- }
-
- fn with_state(state: State) -> Configuration {
- Configuration { state }
- }
-
- pub fn get_max_payload_length(
- &self,
- datarate: DR,
- repeater_compatible: bool,
- dwell_time: bool,
- ) -> u8 {
- region_static_dispatch!(
- self,
- get_max_payload_length,
- datarate,
- repeater_compatible,
- dwell_time
- )
- }
-
- pub(crate) fn create_tx_config(
- &mut self,
- rng: &mut RNG,
- datarate: DR,
- frame: &Frame,
- ) -> TxConfig {
- let (dr, frequency) = self.get_tx_dr_and_frequency(rng, datarate, frame);
- TxConfig {
- pw: self.get_dbm(),
- rf: RfConfig {
- frequency,
- bb: BaseBandModulationParams::new(
- dr.spreading_factor,
- dr.bandwidth,
- self.get_coding_rate(),
- ),
- },
- }
- }
-
- fn get_tx_dr_and_frequency(
- &mut self,
- rng: &mut RNG,
- datarate: DR,
- frame: &Frame,
- ) -> (Datarate, u32) {
- mut_region_dispatch!(self, get_tx_dr_and_frequency, rng, datarate, frame)
- }
-
- pub(crate) fn get_rx_config(&self, datarate: DR, frame: &Frame, window: &Window) -> RfConfig {
- let dr = self.get_rx_datarate(datarate, frame, window);
- RfConfig {
- frequency: self.get_rx_frequency(frame, window),
- bb: BaseBandModulationParams::new(
- dr.spreading_factor,
- dr.bandwidth,
- self.get_coding_rate(),
- ),
- }
- }
-
- pub(crate) fn process_join_accept, C>(
- &mut self,
- join_accept: &DecryptedJoinAcceptPayload,
- ) {
- mut_region_dispatch!(self, process_join_accept, join_accept)
- }
-
- pub(crate) fn set_channel_mask(
- &mut self,
- channel_mask_control: u8,
- channel_mask: ChannelMask<2>,
- ) {
- mut_region_dispatch!(self, handle_link_adr_channel_mask, channel_mask_control, channel_mask)
- }
-
- pub(crate) fn get_rx_frequency(&self, frame: &Frame, window: &Window) -> u32 {
- region_dispatch!(self, get_rx_frequency, frame, window)
- }
-
- pub(crate) fn get_default_datarate(&self) -> DR {
- region_dispatch!(self, get_default_datarate)
- }
-
- pub(crate) fn get_rx_datarate(&self, datarate: DR, frame: &Frame, window: &Window) -> Datarate {
- region_dispatch!(self, get_rx_datarate, datarate, frame, window)
- }
-
- // Unicast: The RXC parameters are identical to the RX2 parameters, and they use the same
- // channel and data rate. Modifying the RX2 parameters using the appropriate MAC
- // commands also modifies the RXC parameters.
- pub(crate) fn get_rxc_config(&self, datarate: DR) -> RfConfig {
- let dr = self.get_rx_datarate(datarate, &Frame::Data, &Window::_2);
- let frequency = self.get_rx_frequency(&Frame::Data, &Window::_2);
- RfConfig {
- frequency,
- bb: BaseBandModulationParams::new(
- dr.spreading_factor,
- dr.bandwidth,
- self.get_coding_rate(),
- ),
- }
- }
-
- pub(crate) fn get_dbm(&self) -> i8 {
- region_dispatch!(self, get_dbm)
- }
-
- pub(crate) fn get_coding_rate(&self) -> CodingRate {
- region_dispatch!(self, get_coding_rate)
- }
-
- #[allow(dead_code)]
- pub(crate) fn get_current_region(&self) -> super::region::Region {
- self.state.region()
- }
-}
-
-macro_rules! from_region {
- ($r:tt) => {
- impl From<$r> for Configuration {
- fn from(region: $r) -> Configuration {
- Configuration::with_state(State::$r(region))
- }
- }
- };
-}
-
-#[cfg(feature = "region-as923-1")]
-from_region!(AS923_1);
-#[cfg(feature = "region-as923-2")]
-from_region!(AS923_2);
-#[cfg(feature = "region-as923-3")]
-from_region!(AS923_3);
-#[cfg(feature = "region-as923-4")]
-from_region!(AS923_4);
-#[cfg(feature = "region-in865")]
-from_region!(IN865);
-#[cfg(feature = "region-au915")]
-from_region!(AU915);
-#[cfg(feature = "region-eu868")]
-from_region!(EU868);
-#[cfg(feature = "region-eu433")]
-from_region!(EU433);
-#[cfg(feature = "region-us915")]
-from_region!(US915);
-
-use lorawan::parser::DecryptedJoinAcceptPayload;
-
-pub(crate) trait RegionHandler {
- fn process_join_accept, C>(
- &mut self,
- join_accept: &DecryptedJoinAcceptPayload,
- );
-
- fn handle_link_adr_channel_mask(
- &mut self,
- channel_mask_control: u8,
- channel_mask: ChannelMask<2>,
- );
-
- fn get_default_datarate(&self) -> DR {
- DR::_0
- }
- fn get_tx_dr_and_frequency(
- &mut self,
- rng: &mut RNG,
- datarate: DR,
- frame: &Frame,
- ) -> (Datarate, u32);
-
- fn get_rx_frequency(&self, frame: &Frame, window: &Window) -> u32;
- fn get_rx_datarate(&self, datarate: DR, frame: &Frame, window: &Window) -> Datarate;
- fn get_dbm(&self) -> i8 {
- DEFAULT_DBM
- }
- fn get_coding_rate(&self) -> CodingRate {
- DEFAULT_CODING_RATE
- }
-}
diff --git a/lorawan-device-patch/src/rng.rs b/lorawan-device-patch/src/rng.rs
deleted file mode 100644
index 822182a..0000000
--- a/lorawan-device-patch/src/rng.rs
+++ /dev/null
@@ -1,54 +0,0 @@
-//! RNG based on the `wyrand` pseudorandom number generator.
-//!
-//! This crate uses the random number generator for exactly two things:
-//!
-//! * Generating DevNonces for join requests
-//! * Selecting random channels when transmitting uplinks.
-//!
-//! The good news is that both these operations don't require true
-//! cryptographic randomness. In fact, in both cases, we don't even care about
-//! predictability! A pseudorandom number generator initialized with a seed
-//! generated by a true random number generator is plenty enough:
-//!
-//! * DevNonces must only be unique with a low chance of collision.
-//! The 1.0.4 LoRaWAN spec even explicitly requires the DevNonces to be
-//! a sequence of incrementing integers, which is obviously predictable.
-//! * No one cares if the channel selected for the next uplink is predictable,
-//! as long as the channel selection yields an uniform distribution.
-//!
-//! By providing a PRNG `RngCore` implementation, we enable the crate users the
-//! flexibility of choosing whether they want to provide their own RNG, or just
-//! a seed to instantiate this PRNG to generate the random numbers for them.
-
-use fastrand::Rng;
-use rand_core::RngCore;
-
-#[derive(Clone)]
-/// A pseudorandom number generator utilizing Wyrand algorithm via
-/// the `fastrand` crate.
-pub struct Prng(Rng);
-
-impl Prng {
- pub(crate) fn new(seed: u64) -> Self {
- Self(Rng::with_seed(seed))
- }
-}
-
-impl RngCore for Prng {
- fn next_u32(&mut self) -> u32 {
- self.0.u32(..)
- }
-
- fn next_u64(&mut self) -> u64 {
- self.0.u64(..)
- }
-
- fn fill_bytes(&mut self, dest: &mut [u8]) {
- rand_core::impls::fill_bytes_via_next(self, dest)
- }
-
- fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
- self.fill_bytes(dest);
- Ok(())
- }
-}
diff --git a/lorawan-device-patch/src/test_util.rs b/lorawan-device-patch/src/test_util.rs
deleted file mode 100644
index d4b8f6f..0000000
--- a/lorawan-device-patch/src/test_util.rs
+++ /dev/null
@@ -1,272 +0,0 @@
-use super::*;
-use lorawan::maccommands::{
- ChannelMask, DownlinkMacCommand, MacCommandIterator, SerializableMacCommand, UplinkMacCommand,
-};
-use lorawan::parser::{self, DataHeader};
-use lorawan::{
- default_crypto::DefaultFactory,
- maccommandcreator::LinkADRReqCreator,
- maccommands::LinkADRReqPayload,
- parser::{parse, DataPayload, JoinAcceptPayload, PhyPayload},
-};
-use mac::Session;
-
-use parser::FCtrl;
-use radio::{RfConfig, TxConfig};
-use std::{collections::HashMap, sync::Mutex, vec::Vec};
-
-/// This module contains some functions for both async device and state machine driven devices
-/// to operate unit tests.
-///
-
-#[derive(Debug, Clone)]
-pub struct Uplink {
- data: Vec,
- #[allow(unused)]
- tx_config: TxConfig,
-}
-
-impl Uplink {
- /// Creates a copy from a reference and ensures the packet is at least parseable.
- pub fn new(data_in: &[u8], tx_config: TxConfig) -> Result {
- let mut data: Vec = Vec::new();
- data.extend_from_slice(data_in);
- let _parse = parse(data.as_mut_slice())?;
- Ok(Self { data, tx_config })
- }
-
- pub fn get_payload(&mut self) -> PhyPayload<&mut [u8], DefaultFactory> {
- match parse(self.data.as_mut_slice()) {
- Ok(p) => p,
- Err(e) => panic!("Failed to parse payload: {:?}", e),
- }
- }
-}
-
-/// Test functions shared by async_device and no_async_device tests
-pub fn get_key() -> [u8; 16] {
- [0; 16]
-}
-
-pub fn get_dev_addr() -> DevAddr<[u8; 4]> {
- DevAddr::from(0)
-}
-pub fn get_otaa_credentials() -> JoinMode {
- JoinMode::OTAA {
- deveui: DevEui::from([0; 8]),
- appeui: AppEui::from([0; 8]),
- appkey: AppKey::from(get_key()),
- }
-}
-
-pub fn get_abp_credentials() -> JoinMode {
- JoinMode::ABP {
- devaddr: get_dev_addr(),
- appskey: AppSKey::from(get_key()),
- newskey: NewSKey::from(get_key()),
- }
-}
-
-pub type RxTxHandler = fn(Option, RfConfig, &mut [u8]) -> usize;
-
-lazy_static::lazy_static! {
- static ref SESSION: Mutex> = Mutex::new(HashMap::new());
-
-}
-
-/// Handle join request and pack a JoinAccept into RxBuffer
-pub fn handle_join_request(
- uplink: Option,
- _config: RfConfig,
- rx_buffer: &mut [u8],
-) -> usize {
- if let Some(mut uplink) = uplink {
- if let PhyPayload::JoinRequest(join_request) = uplink.get_payload() {
- let devnonce = join_request.dev_nonce().to_owned();
- assert!(join_request.validate_mic(&get_key().into()));
- let mut buffer: [u8; 17] = [0; 17];
- let mut phy =
- lorawan::creator::JoinAcceptCreator::with_options(&mut buffer, DefaultFactory)
- .unwrap();
- let app_nonce_bytes = [1; 3];
- phy.set_app_nonce(&app_nonce_bytes);
- phy.set_net_id(&[1; 3]);
- phy.set_dev_addr(get_dev_addr());
- let finished = phy.build(&get_key().into()).unwrap();
- rx_buffer[..finished.len()].copy_from_slice(finished);
-
- let mut copy = rx_buffer[..finished.len()].to_vec();
- if let PhyPayload::JoinAccept(JoinAcceptPayload::Encrypted(encrypted)) =
- parse(copy.as_mut_slice()).unwrap()
- {
- let decrypt = encrypted.decrypt(&get_key().into());
- let session = Session::derive_new(
- &decrypt,
- devnonce,
- &NetworkCredentials::new(
- AppEui::from([0; 8]),
- DevEui::from([0; 8]),
- AppKey::from(get_key()),
- ),
- );
- {
- let mut session_map = SESSION.lock().unwrap();
- session_map.insert(I, session);
- }
- } else {
- panic!("Somehow unable to parse my own join accept?")
- }
- finished.len()
- } else {
- panic!("Did not parse join request from uplink");
- }
- } else {
- panic!("No uplink passed to handle_join_request");
- }
-}
-
-/// Handle an uplink and respond with two LinkAdrReq on Port 0
-pub fn handle_data_uplink_with_link_adr_req(
- uplink: Option,
- _config: RfConfig,
- rx_buffer: &mut [u8],
-) -> usize {
- if let Some(mut uplink) = uplink {
- if let PhyPayload::Data(DataPayload::Encrypted(data)) = uplink.get_payload() {
- let fcnt = data.fhdr().fcnt() as u32;
- assert!(data.validate_mic(&get_key().into(), fcnt));
- let uplink =
- data.decrypt(Some(&get_key().into()), Some(&get_key().into()), fcnt).unwrap();
- assert_eq!(uplink.fhdr().fcnt(), FCNT_UP);
- let mac_cmds = [link_adr_req_with_bank_ctrl(0b10), link_adr_req_with_bank_ctrl(0b100)];
- let mac_cmds = [
- // drop the CID byte when building the MAC Command (ie: [1..])
- DownlinkMacCommand::LinkADRReq(
- LinkADRReqPayload::new(&mac_cmds[0].build()[1..]).unwrap(),
- ),
- DownlinkMacCommand::LinkADRReq(
- LinkADRReqPayload::new(&mac_cmds[1].build()[1..]).unwrap(),
- ),
- ];
- let cmd: Vec<&dyn SerializableMacCommand> = vec![&mac_cmds[0], &mac_cmds[1]];
- let mut phy =
- lorawan::creator::DataPayloadCreator::with_options(rx_buffer, DefaultFactory)
- .unwrap();
- phy.set_confirmed(uplink.is_confirmed());
- phy.set_f_port(4);
- phy.set_dev_addr(&[0; 4]);
- phy.set_uplink(false);
- phy.set_fcnt(FCNT_DOWN);
- let finished =
- phy.build(&[3, 2, 1], &cmd, &get_key().into(), &get_key().into()).unwrap();
- finished.len()
- } else {
- panic!("Did not decode PhyPayload::Data!");
- }
- } else {
- panic!("No uplink passed to handle_data_uplink_with_link_adr_req");
- }
-}
-
-/// Handle an uplink and respond with two LinkAdrReq on Port 0
-pub fn handle_class_c_uplink_after_join(
- uplink: Option,
- _config: RfConfig,
- rx_buffer: &mut [u8],
-) -> usize {
- if let Some(mut uplink) = uplink {
- if let PhyPayload::Data(DataPayload::Encrypted(data)) = uplink.get_payload() {
- let fcnt = data.fhdr().fcnt() as u32;
- assert!(data.validate_mic(&get_key().into(), fcnt));
- let uplink =
- data.decrypt(Some(&get_key().into()), Some(&get_key().into()), fcnt).unwrap();
- assert_eq!(uplink.fhdr().fcnt(), 0);
- let mut phy =
- lorawan::creator::DataPayloadCreator::with_options(rx_buffer, DefaultFactory)
- .unwrap();
- let mut fctrl = FCtrl::new(0, false);
- fctrl.set_ack();
- phy.set_confirmed(false);
- phy.set_dev_addr(&[0; 4]);
- phy.set_uplink(false);
- phy.set_fctrl(&fctrl);
- // set ack bit
- let finished = phy.build(&[], &[], &get_key().into(), &get_key().into()).unwrap();
- finished.len()
- } else {
- panic!("Did not decode PhyPayload::Data!");
- }
- } else {
- panic!("No uplink passed to handle_data_uplink_with_link_adr_req");
- }
-}
-
-fn link_adr_req_with_bank_ctrl(cm: u16) -> LinkADRReqCreator {
- // prepare a confirmed downlink
- let mut adr_req = LinkADRReqCreator::new();
- adr_req.set_data_rate(0).unwrap();
- adr_req.set_tx_power(0).unwrap();
- // this should give us a chmask ctrl value of 5 which allows us to turn banks on and off
- adr_req.set_redundancy(0x50);
- // the second bit is the only high bit, so only bank 2 should be enabled
- let tmp = [cm as u8, (cm >> 8) as u8];
- let cm = ChannelMask::new(&tmp).unwrap();
- adr_req.set_channel_mask(cm);
- adr_req
-}
-
-/// Looks for LinkAdrAns
-pub fn handle_data_uplink_with_link_adr_ans(
- uplink: Option