├── .github ├── dependabot.yml └── workflows │ └── run-test.yml ├── .gitignore ├── .travis.yml ├── Cargo.toml ├── LICENSE.txt ├── README.md └── src ├── blocking.rs ├── config.rs ├── helpers.rs ├── lib.rs ├── mock.rs └── nonblocking.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "04:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /.github/workflows/run-test.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | clippy: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout sources 9 | uses: actions/checkout@v2 10 | - name: Install Rust (stable) 11 | uses: actions-rs/toolchain@v1 12 | with: 13 | toolchain: stable 14 | components: clippy 15 | override: true 16 | - name: Clippy 17 | uses: actions-rs/cargo@v1 18 | with: 19 | command: clippy 20 | format: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Checkout sources 24 | uses: actions/checkout@v2 25 | - name: Install Rust (stable) 26 | uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: stable 29 | components: rustfmt 30 | override: true 31 | - name: Check Format 32 | uses: actions-rs/cargo@v1 33 | with: 34 | command: fmt 35 | args: --all -- --check 36 | build-armv6m: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - name: Checkout sources 40 | uses: actions/checkout@v2 41 | - name: Install Rust (stable) 42 | uses: actions-rs/toolchain@v1 43 | with: 44 | profile: minimal 45 | toolchain: stable 46 | override: true 47 | target: thumbv6m-none-eabi 48 | - name: Build 49 | run: | 50 | set -ex 51 | cargo build 52 | test: 53 | runs-on: ubuntu-latest 54 | steps: 55 | - name: Checkout sources 56 | uses: actions/checkout@v2 57 | - name: Install Rust (stable) 58 | uses: actions-rs/toolchain@v1 59 | with: 60 | profile: minimal 61 | toolchain: stable 62 | override: true 63 | - name: Test 64 | run: | 65 | set -ex 66 | cargo test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | /target 3 | **/*.rs.bk 4 | Cargo.lock 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | stable 5 | nightly 6 | 7 | jobs: 8 | include: 9 | - rust: stable 10 | env: FLAGS="" 11 | - rust: nightly 12 | env: FLAGS="--all-features" 13 | 14 | script: 15 | - cargo build $FLAGS 16 | - cargo test $FLAGS 17 | 18 | notifications: 19 | email: 20 | on_success: never 21 | on_failure: never 22 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "radio" 3 | description = "Generic traits for embedded packet radio devices" 4 | repository = "https://github.com/rust-iot/radio-hal" 5 | authors = ["Ryan Kurte "] 6 | license = "MIT" 7 | edition = "2018" 8 | version = "0.12.1" 9 | 10 | [package.metadata.docs.rs] 11 | features = [ "std", "nonblocking", "mock", "helpers" ] 12 | 13 | [features] 14 | std = [ ] 15 | nonblocking = [ ] 16 | mock = [ "embedded-hal-mock" ] 17 | helpers = [ "clap", "humantime", "std", "pcap-file", "libc", "byteorder", "rolling-stats" ] 18 | default = [ ] 19 | 20 | [dependencies] 21 | embedded-hal = "1.0.0" 22 | embedded-hal-mock = { version = "0.10.0", optional = true } 23 | nb = "1.0.0" 24 | 25 | log = { version = "0.4.14", default_features = false } 26 | defmt = { version = "0.3.0", optional = true } 27 | 28 | chrono = { version = "0.4.19", default_features = false } 29 | humantime = { version = "2.0.1", optional = true } 30 | pcap-file = { version = "1.1.1", optional = true } 31 | async-std = { version = "1.4.0", optional = true } 32 | libc = { version = "0.2.71", optional = true } 33 | byteorder = { version = "1.3.4", optional = true } 34 | rolling-stats = { version = "0.7.0", optional = true } 35 | thiserror = { version = "1.0.30", optional = true } 36 | clap = { version = "4.4.7", optional = true, features = [ "derive" ] } 37 | 38 | [dev-dependencies] 39 | anyhow = "1.0.44" 40 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018 Ryan Kurte 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust IoT Radio Abstraction(s) 2 | 3 | An [embedded-hal](https://github.com/rust-embedded/embedded-hal) like abstraction for digital radio devices, this is intended to provide a common basis for implementing packet radio drivers, and for extension to support 802.15.4 / BLE etc. in the hope that we can construct embedded network stacks using this common abstraction. 4 | 5 | Radio devices should implement the [core traits](https://docs.rs/radio/), and then gain automatic [blocking](https://docs.rs/radio/latest/radio/blocking/index.html) helper functions. Experimental [async/await](https://docs.rs/radio/latest/radio/nonblocking/index.html) helpers are available behind the `nonblocking` feature flag, this uses [dtolnay/async-trait](https://github.com/dtolnay/async-trait), imports `std` and `async-std`, and requires a nightly compiler, and a `MockRadio` implementation for testing is available behind the `mock` feature flag (also requiring nightly). 6 | 7 | 8 | ## Status 9 | 10 | **Work In Progress, expect major API changes** 11 | 12 | [![GitHub tag](https://img.shields.io/github/tag/ryankurte/rust-radio.svg)](https://github.com/ryankurte/rust-radio) 13 | [![Build Status](https://travis-ci.com/ryankurte/rust-radio.svg?token=s4CML2iJ2hd54vvqz5FP&branch=master)](https://travis-ci.com/ryankurte/rust-radio) 14 | [![Crates.io](https://img.shields.io/crates/v/radio.svg)](https://crates.io/crates/radio) 15 | [![Docs.rs](https://docs.rs/radio/badge.svg)](https://docs.rs/radio) 16 | 17 | [Open Issues](https://github.com/ryankurte/rust-radio/issues) 18 | 19 | 20 | ### Features: 21 | 22 | - [ ] Generic Traits 23 | - [x] Transmit 24 | - [x] Receive 25 | - [x] Set Channel 26 | - [x] Fetch RSSI 27 | - [x] Register Access 28 | - [ ] Configuration (?) 29 | - [ ] Mode Specific Traits (and definitions) 30 | - [ ] 802.15.4 31 | - [ ] BLE 32 | - [ ] LoRa 33 | - [x] Helpers 34 | - [x] Blocking 35 | - [x] Async 36 | 37 | 38 | ### Examples 39 | 40 | - [ryankurte/rust-radio-sx127x](https://github.com/ryankurte/rust-radio-sx127x) 41 | - [ryankurte/rust-radio-sx128x](https://github.com/ryankurte/rust-radio-sx128x) 42 | - [ryankurte/rust-radio-at86rf212](https://github.com/ryankurte/rust-radio-at86rf212) 43 | - [ryankurte/rust-radio-s2lp](https://github.com/ryankurte/rust-radio-s2lp) 44 | 45 | 46 | **For similar interfaces, check out:** 47 | - Riot-OS 48 | - [netdev.h](https://github.com/RIOT-OS/RIOT/blob/master/drivers/include/net/netdev.h) 49 | - [ieee802154.h](https://github.com/RIOT-OS/RIOT/blob/master/drivers/include/net/netdev/ieee802154.h) 50 | [netdev_ieee802154.c](https://github.com/RIOT-OS/RIOT/blob/master/drivers/netdev_ieee802154/netdev_ieee802154.c) 51 | - Contiki-OS 52 | - [core/dev/radio.h](https://github.com/contiki-os/contiki/blob/master/core/dev/radio.h) 53 | - Tock-PS 54 | - [ieee802154/device.rs](https://github.com/tock/tock/blob/master/capsules/src/ieee802154/device.rs) 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /src/blocking.rs: -------------------------------------------------------------------------------- 1 | //! Blocking APIs on top of the base radio traits 2 | //! 3 | //! These implementations use the radio's DelayUs implementation to 4 | //! poll on completion of operations. 5 | //! 6 | //! ## 7 | //! ## Copyright 2020-2022 Ryan Kurte 8 | 9 | use core::fmt::Debug; 10 | use core::time::Duration; 11 | 12 | use embedded_hal::delay::DelayNs; 13 | 14 | #[cfg(feature = "defmt")] 15 | use defmt::debug; 16 | 17 | #[cfg(feature = "clap")] 18 | use clap::Parser; 19 | 20 | #[cfg(feature = "std")] 21 | use std::string::ToString; 22 | 23 | use crate::{Receive, State, Transmit}; 24 | 25 | /// BlockingOptions for blocking radio functions 26 | #[derive(Clone, PartialEq, Debug)] 27 | #[cfg_attr(feature = "clap", derive(Parser))] 28 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 29 | pub struct BlockingOptions { 30 | /// Interval for polling for device state 31 | #[cfg_attr(feature="clap", clap(long, default_value="100us", value_parser=crate::duration_from_str))] 32 | pub poll_interval: Duration, 33 | 34 | /// Timeout for blocking operation 35 | #[cfg_attr(feature="clap", clap(long, default_value="100ms", value_parser=crate::duration_from_str))] 36 | pub timeout: Duration, 37 | } 38 | 39 | impl Default for BlockingOptions { 40 | fn default() -> Self { 41 | Self { 42 | poll_interval: Duration::from_micros(100), 43 | timeout: Duration::from_millis(100), 44 | } 45 | } 46 | } 47 | 48 | /// BlockingError wraps radio error type to provie a `Timeout` variant 49 | #[derive(Clone, Debug, PartialEq)] 50 | #[cfg_attr(feature = "thiserror", derive(thiserror::Error))] 51 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] 52 | pub enum BlockingError { 53 | #[cfg_attr(feature = "thiserror", error("Inner: {0}"))] 54 | Inner(E), 55 | #[cfg_attr(feature = "thiserror", error("Timeout"))] 56 | Timeout, 57 | } 58 | 59 | impl From for BlockingError { 60 | fn from(e: E) -> Self { 61 | BlockingError::Inner(e) 62 | } 63 | } 64 | 65 | /// Blocking transmit function implemented over `radio::Transmit` and `radio::Power` using the provided 66 | /// `BlockingOptions` and radio-internal `DelayUs` impl to poll for completion 67 | #[cfg_attr( 68 | feature = "mock", 69 | doc = r##" 70 | ``` 71 | # use radio::*; 72 | # use radio::mock::*; 73 | use radio::{BlockingTransmit, BlockingOptions}; 74 | 75 | # let mut radio = MockRadio::new(&[ 76 | # Transaction::start_transmit(vec![0xaa, 0xbb], None), 77 | # Transaction::check_transmit(Ok(false)), 78 | # Transaction::delay_us(100), 79 | # Transaction::check_transmit(Ok(true)), 80 | # ]); 81 | # 82 | // Transmit using a blocking call 83 | let res = radio.do_transmit(&[0xaa, 0xbb], BlockingOptions::default()); 84 | 85 | assert_eq!(res, Ok(())); 86 | 87 | # radio.done(); 88 | ``` 89 | "## 90 | )] 91 | /// 92 | pub trait BlockingTransmit { 93 | fn do_transmit( 94 | &mut self, 95 | data: &[u8], 96 | tx_options: BlockingOptions, 97 | ) -> Result<(), BlockingError>; 98 | } 99 | 100 | impl BlockingTransmit for T 101 | where 102 | T: Transmit + DelayNs, 103 | E: Debug, 104 | { 105 | fn do_transmit( 106 | &mut self, 107 | data: &[u8], 108 | tx_options: BlockingOptions, 109 | ) -> Result<(), BlockingError> { 110 | // Enter transmit mode 111 | self.start_transmit(data)?; 112 | 113 | let t = tx_options.timeout.as_micros(); 114 | let mut c = 0; 115 | loop { 116 | // Check for transmit complete 117 | if self.check_transmit()? { 118 | #[cfg(any(feature = "log", feature = "defmt"))] 119 | debug!("Blocking send complete"); 120 | break; 121 | } 122 | 123 | // Update poll time and timeout if overrun 124 | c += tx_options.poll_interval.as_micros(); 125 | if c > t { 126 | #[cfg(any(feature = "log", feature = "defmt"))] 127 | debug!("Blocking send timeout"); 128 | return Err(BlockingError::Timeout); 129 | } 130 | 131 | // Wait for next poll 132 | self.delay_us(tx_options.poll_interval.as_micros() as u32); 133 | } 134 | 135 | Ok(()) 136 | } 137 | } 138 | 139 | /// Blocking receive function implemented over `radio::Receive` using the provided `BlockingOptions` 140 | /// and radio-internal `DelayUs` impl to poll for completion 141 | #[cfg_attr( 142 | feature = "mock", 143 | doc = r##" 144 | ``` 145 | # use radio::*; 146 | # use radio::mock::*; 147 | use radio::{BlockingReceive, BlockingOptions}; 148 | 149 | let data = [0xaa, 0xbb]; 150 | let info = BasicInfo::new(-81, 0); 151 | 152 | # let mut radio = MockRadio::new(&[ 153 | # Transaction::start_receive(None), 154 | # Transaction::check_receive(true, Ok(false)), 155 | # Transaction::delay_us(100), 156 | # Transaction::check_receive(true, Ok(true)), 157 | # Transaction::get_received(Ok((data.to_vec(), info.clone()))), 158 | # ]); 159 | # 160 | 161 | // Setup buffer to read into 162 | let mut buff = [0u8; 128]; 163 | 164 | // Receive using a blocking call 165 | let (n, info) = radio.do_receive(&mut buff, BlockingOptions::default())?; 166 | 167 | assert_eq!(n, data.len()); 168 | assert_eq!(&buff[..data.len()], &data); 169 | 170 | # radio.done(); 171 | 172 | # Ok::<(), anyhow::Error>(()) 173 | ``` 174 | "## 175 | )] 176 | /// 177 | pub trait BlockingReceive { 178 | fn do_receive( 179 | &mut self, 180 | buff: &mut [u8], 181 | rx_options: BlockingOptions, 182 | ) -> Result<(usize, I), BlockingError>; 183 | } 184 | 185 | impl BlockingReceive for T 186 | where 187 | T: Receive + DelayNs, 188 | ::Info: Debug, 189 | I: Debug, 190 | E: Debug, 191 | { 192 | fn do_receive( 193 | &mut self, 194 | buff: &mut [u8], 195 | rx_options: BlockingOptions, 196 | ) -> Result<(usize, I), BlockingError> { 197 | // Start receive mode 198 | self.start_receive()?; 199 | 200 | let t = rx_options.timeout.as_micros(); 201 | let mut c = 0; 202 | loop { 203 | if self.check_receive(true)? { 204 | let (n, i) = self.get_received(buff)?; 205 | return Ok((n, i)); 206 | } 207 | 208 | c += rx_options.poll_interval.as_micros(); 209 | if c > t { 210 | #[cfg(any(feature = "log", feature = "defmt"))] 211 | debug!("Blocking receive timeout"); 212 | return Err(BlockingError::Timeout); 213 | } 214 | 215 | self.delay_us(rx_options.poll_interval.as_micros() as u32); 216 | } 217 | } 218 | } 219 | 220 | /// BlockingSetState sets the radio state and polls until command completion 221 | pub trait BlockingSetState { 222 | fn set_state_checked( 223 | &mut self, 224 | state: S, 225 | options: BlockingOptions, 226 | ) -> Result<(), BlockingError>; 227 | } 228 | 229 | impl BlockingSetState for T 230 | where 231 | T: State + DelayNs, 232 | S: Debug + core::cmp::PartialEq + Copy, 233 | E: Debug, 234 | { 235 | fn set_state_checked( 236 | &mut self, 237 | state: S, 238 | options: BlockingOptions, 239 | ) -> Result<(), BlockingError> { 240 | // Send set state command 241 | self.set_state(state)?; 242 | 243 | let t = options.timeout.as_micros(); 244 | let mut c = 0; 245 | 246 | loop { 247 | // Fetch state 248 | let s = self.get_state()?; 249 | 250 | // Check for expected state 251 | if state == s { 252 | return Ok(()); 253 | } 254 | 255 | // Timeout eventually 256 | c += options.poll_interval.as_micros(); 257 | if c > t { 258 | #[cfg(any(feature = "log", feature = "defmt"))] 259 | debug!("Blocking receive timeout"); 260 | return Err(BlockingError::Timeout); 261 | } 262 | 263 | // Delay before next loop 264 | self.delay_us(options.poll_interval.as_micros() as u32); 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | //! Config provides traits for standard radio configuration 2 | 3 | /// Radio configuration options 4 | #[non_exhaustive] 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub enum ConfigOption { 7 | /// MAC address 8 | MAC([u8; 6]), 9 | /// IPv4 address 10 | IPv4([u8; 4]), 11 | /// IPv6 address 12 | IPv6([u8; 16]), 13 | 14 | /// IEEE802.15.4(g) / ZigBee address options 15 | /// Short (16-bit) Address 16 | ShortAddress(u16), 17 | /// Long (64-bit) Address 18 | LongAddress(u64), 19 | /// PAN ID 20 | PAN(u16), 21 | 22 | /// Maximum Transmission Unit (MTU) 23 | MTU(u16), 24 | /// Transmit power (dBm) 25 | TXPower(i16), 26 | 27 | /// Await Clear Channel before TX (if supported) 28 | AwaitCCA(bool), 29 | /// CCA threshold in dBm (used if AwaitCCA is set) 30 | CCAThreshold(i16), 31 | /// Automatic Acknowledgement (if supported) sends 802.15.4 acknowledgements automatically 32 | AutoAck(bool), 33 | /// Promiscuous mode (if supported) disables hardware address filtering 34 | Promiscuous(bool), 35 | } 36 | 37 | /// Radio configuration errors 38 | /// This should be extended with errors generally relevant to configuration, 39 | /// with radio-specific errors passed through the Other(E) field. 40 | #[non_exhaustive] 41 | #[derive(Clone, Debug, PartialEq)] 42 | pub enum ConfigError { 43 | /// Configuration option not supported 44 | NotSupported, 45 | 46 | /// Other (device, non-configuration errors) 47 | Other(E), 48 | } 49 | 50 | /// Configure trait implemented by configurable radios 51 | pub trait Configure { 52 | /// Radio error 53 | type Error; 54 | 55 | /// Set a configuration option 56 | /// Returns Ok(true) on set, Ok(false) for unsupported options, Err(Self::Error) for errors 57 | fn set_option(&mut self, o: &ConfigOption) -> Result<(), ConfigError>; 58 | 59 | /// Fetch a configuration option 60 | /// This will overwrite the value of the provided option enum 61 | /// Returns Ok(true) on successful get, Ok(false) for unsupported options, Err(Self::Error) for errors 62 | fn get_option(&mut self, o: &mut ConfigOption) -> Result<(), ConfigError>; 63 | } 64 | -------------------------------------------------------------------------------- /src/helpers.rs: -------------------------------------------------------------------------------- 1 | //! Provides common helpers for implementing radio utilities 2 | //! 3 | //! ## 4 | //! ## Copyright 2020-2022 Ryan Kurte 5 | 6 | use std::ffi::CString; 7 | use std::fs::{File, OpenOptions}; 8 | use std::prelude::v1::*; 9 | use std::string::String; 10 | use std::time::SystemTime; 11 | 12 | use libc::{self}; 13 | 14 | #[cfg(not(feature = "defmt"))] 15 | use log::{debug, info}; 16 | 17 | #[cfg(feature = "defmt")] 18 | use defmt::{debug, info}; 19 | 20 | use clap::Parser; 21 | use embedded_hal::delay::DelayNs; 22 | use humantime::Duration as HumanDuration; 23 | 24 | use byteorder::{ByteOrder, NetworkEndian}; 25 | use pcap_file::{pcap::PcapHeader, DataLink, PcapWriter}; 26 | use rolling_stats::Stats; 27 | 28 | use crate::*; 29 | use crate::{ 30 | blocking::{BlockingError, BlockingOptions, BlockingReceive, BlockingTransmit}, 31 | Power, Receive, ReceiveInfo, Rssi, Transmit, 32 | }; 33 | 34 | /// Basic operations supported by the helpers package 35 | #[derive(Clone, Parser, PartialEq, Debug)] 36 | pub enum Operation { 37 | #[clap(name = "tx")] 38 | /// Transmit a packet 39 | Transmit(TransmitOptions), 40 | 41 | #[clap(name = "rx")] 42 | /// Receive a packet 43 | Receive(ReceiveOptions), 44 | 45 | #[clap(name = "rssi")] 46 | /// Poll RSSI on the configured channel 47 | Rssi(RssiOptions), 48 | 49 | #[clap(name = "echo")] 50 | /// Echo back received messages (useful with Link Test mode) 51 | Echo(EchoOptions), 52 | 53 | #[clap(name = "ping-pong")] 54 | /// Link test (ping-pong) mode 55 | LinkTest(PingPongOptions), 56 | } 57 | 58 | pub fn do_operation(radio: &mut T, operation: Operation) -> Result<(), BlockingError> 59 | where 60 | T: Transmit 61 | + Power 62 | + Receive 63 | + Rssi 64 | + Power 65 | + DelayNs, 66 | I: ReceiveInfo + Default + std::fmt::Debug, 67 | E: std::fmt::Debug, 68 | { 69 | let mut buff = [0u8; 1024]; 70 | 71 | // TODO: the rest 72 | match operation { 73 | Operation::Transmit(options) => do_transmit(radio, options)?, 74 | Operation::Receive(options) => do_receive(radio, &mut buff, options).map(|_| ())?, 75 | Operation::Echo(options) => do_echo(radio, &mut buff, options).map(|_| ())?, 76 | Operation::Rssi(options) => do_rssi(radio, options).map(|_| ())?, 77 | Operation::LinkTest(options) => do_ping_pong(radio, options).map(|_| ())?, 78 | //_ => warn!("unsuppored command: {:?}", opts.command), 79 | } 80 | 81 | Ok(()) 82 | } 83 | 84 | /// Configuration for Transmit operation 85 | #[derive(Clone, Parser, PartialEq, Debug)] 86 | pub struct TransmitOptions { 87 | /// Data to be transmitted 88 | #[clap(long)] 89 | pub data: Vec, 90 | 91 | /// Power in dBm (range -18dBm to 13dBm) 92 | #[clap(long)] 93 | pub power: Option, 94 | 95 | /// Specify period for repeated transmission 96 | #[clap(long)] 97 | pub period: Option, 98 | 99 | #[clap(flatten)] 100 | pub blocking_options: BlockingOptions, 101 | } 102 | 103 | pub fn do_transmit(radio: &mut T, options: TransmitOptions) -> Result<(), BlockingError> 104 | where 105 | T: Transmit + Power + DelayNs, 106 | E: core::fmt::Debug, 107 | { 108 | // Set output power if specified 109 | if let Some(p) = options.power { 110 | radio.set_power(p)?; 111 | } 112 | 113 | loop { 114 | // Transmit packet 115 | radio.do_transmit(&options.data, options.blocking_options.clone())?; 116 | 117 | // Delay for repeated transmission or exit 118 | match &options.period { 119 | Some(p) => radio.delay_us(p.as_micros() as u32), 120 | None => break, 121 | } 122 | } 123 | 124 | Ok(()) 125 | } 126 | 127 | /// Configuration for Receive operation 128 | #[derive(Clone, Parser, PartialEq, Debug)] 129 | pub struct ReceiveOptions { 130 | /// Run continuously 131 | #[clap(long = "continuous")] 132 | pub continuous: bool, 133 | 134 | #[clap(flatten)] 135 | pub pcap_options: PcapOptions, 136 | 137 | #[clap(flatten)] 138 | pub blocking_options: BlockingOptions, 139 | } 140 | 141 | #[derive(Clone, Parser, PartialEq, Debug)] 142 | 143 | pub struct PcapOptions { 144 | /// Create and write capture output to a PCAP file 145 | #[clap(long, group = "1")] 146 | pub pcap_file: Option, 147 | 148 | /// Create and write to a unix pipe for connection to wireshark 149 | #[clap(long, group = "1")] 150 | pub pcap_pipe: Option, 151 | } 152 | 153 | impl PcapOptions { 154 | pub fn open(&self) -> Result>, std::io::Error> { 155 | // Open file or pipe if specified 156 | let pcap_file = match (&self.pcap_file, &self.pcap_pipe) { 157 | // Open as file 158 | (Some(file), None) => { 159 | let f = File::create(file)?; 160 | Some(f) 161 | } 162 | // Open as pipe 163 | #[cfg(target_family = "unix")] 164 | (None, Some(pipe)) => { 165 | // Ensure file doesn't already exist 166 | let _ = std::fs::remove_file(pipe); 167 | 168 | // Create pipe 169 | let n = CString::new(pipe.as_str()).unwrap(); 170 | let status = unsafe { libc::mkfifo(n.as_ptr(), 0o644) }; 171 | 172 | // Manual status code handling 173 | // TODO: return io::Error 174 | if status != 0 { 175 | panic!("Error creating fifo: {}", status); 176 | } 177 | 178 | // Open pipe 179 | let f = OpenOptions::new() 180 | .write(true) 181 | .open(pipe) 182 | .expect("Error opening PCAP pipe"); 183 | 184 | Some(f) 185 | } 186 | 187 | (None, None) => None, 188 | 189 | _ => unimplemented!(), 190 | }; 191 | 192 | #[cfg(any(feature = "log", feature = "defmt"))] 193 | info!("pcap pipe open, awaiting connection"); 194 | 195 | // Setup pcap writer and write header 196 | // (This is a blocking operation on pipes) 197 | let pcap_writer = match pcap_file { 198 | None => None, 199 | Some(f) => { 200 | // Setup pcap header 201 | let mut h = PcapHeader::default(); 202 | h.datalink = DataLink::IEEE802_15_4; 203 | 204 | // Write header 205 | let w = PcapWriter::with_header(h, f).expect("Error writing to PCAP file"); 206 | Some(w) 207 | } 208 | }; 209 | 210 | Ok(pcap_writer) 211 | } 212 | } 213 | 214 | /// Receive from the radio using the provided configuration 215 | pub fn do_receive( 216 | radio: &mut T, 217 | mut buff: &mut [u8], 218 | options: ReceiveOptions, 219 | ) -> Result 220 | where 221 | T: Receive + DelayNs, 222 | I: std::fmt::Debug, 223 | E: std::fmt::Debug, 224 | { 225 | // Create and open pcap file for writing 226 | let mut pcap_writer = options 227 | .pcap_options 228 | .open() 229 | .expect("Error opening pcap file / pipe"); 230 | 231 | // Start receive mode 232 | radio.start_receive()?; 233 | 234 | loop { 235 | if radio.check_receive(true)? { 236 | let (n, i) = radio.get_received(&mut buff)?; 237 | 238 | match std::str::from_utf8(&buff[0..n as usize]) { 239 | Ok(s) => info!("Received: '{}' info: {:?}", s, i), 240 | #[cfg(not(feature = "defmt"))] 241 | Err(_) => info!("Received: '{:02x?}' info: {:?}", &buff[0..n as usize], i), 242 | #[cfg(feature = "defmt")] 243 | Err(_) => info!("Received: '{:?}' info: {:?}", &buff[0..n as usize], i), 244 | } 245 | 246 | if let Some(p) = &mut pcap_writer { 247 | let t = SystemTime::now() 248 | .duration_since(SystemTime::UNIX_EPOCH) 249 | .unwrap(); 250 | 251 | p.write( 252 | t.as_secs() as u32, 253 | t.as_nanos() as u32 % 1_000_000, 254 | &buff[0..n], 255 | n as u32, 256 | ) 257 | .expect("Error writing pcap file"); 258 | } 259 | 260 | if !options.continuous { 261 | return Ok(n); 262 | } 263 | 264 | radio.start_receive()?; 265 | } 266 | 267 | radio.delay_us(options.blocking_options.poll_interval.as_micros() as u32); 268 | } 269 | } 270 | 271 | /// Configuration for RSSI operation 272 | #[derive(Clone, Parser, PartialEq, Debug)] 273 | pub struct RssiOptions { 274 | /// Specify period for RSSI polling 275 | #[clap(long = "period", default_value = "1s")] 276 | pub period: HumanDuration, 277 | 278 | /// Run continuously 279 | #[clap(long = "continuous")] 280 | pub continuous: bool, 281 | } 282 | 283 | pub fn do_rssi(radio: &mut T, options: RssiOptions) -> Result<(), E> 284 | where 285 | T: Receive + Rssi + DelayNs, 286 | I: std::fmt::Debug, 287 | E: std::fmt::Debug, 288 | { 289 | // Enter receive mode 290 | radio.start_receive()?; 291 | 292 | // Poll for RSSI 293 | loop { 294 | let rssi = radio.poll_rssi()?; 295 | 296 | info!("rssi: {}", rssi); 297 | 298 | radio.check_receive(true)?; 299 | 300 | radio.delay_us(options.period.as_micros() as u32); 301 | 302 | if !options.continuous { 303 | break; 304 | } 305 | } 306 | 307 | Ok(()) 308 | } 309 | 310 | /// Configuration for Echo operation 311 | #[derive(Clone, Parser, PartialEq, Debug)] 312 | pub struct EchoOptions { 313 | /// Run continuously 314 | #[clap(long = "continuous")] 315 | pub continuous: bool, 316 | 317 | /// Power in dBm (range -18dBm to 13dBm) 318 | #[clap(long = "power")] 319 | pub power: Option, 320 | 321 | /// Specify delay for response message 322 | #[clap(long = "delay", default_value = "100ms")] 323 | pub delay: HumanDuration, 324 | 325 | /// Append RSSI and LQI to repeated message 326 | #[clap(long = "append-info")] 327 | pub append_info: bool, 328 | 329 | #[clap(flatten)] 330 | pub blocking_options: BlockingOptions, 331 | } 332 | 333 | pub fn do_echo( 334 | radio: &mut T, 335 | mut buff: &mut [u8], 336 | options: EchoOptions, 337 | ) -> Result> 338 | where 339 | T: Receive + Transmit + Power + DelayNs, 340 | I: ReceiveInfo + std::fmt::Debug, 341 | E: std::fmt::Debug, 342 | { 343 | // Set output power if specified 344 | if let Some(p) = options.power { 345 | radio.set_power(p)?; 346 | } 347 | 348 | // Start receive mode 349 | radio.start_receive()?; 350 | 351 | loop { 352 | if radio.check_receive(true)? { 353 | // Fetch received packet 354 | let (mut n, i) = radio.get_received(&mut buff)?; 355 | 356 | // Parse out string if possible, otherwise print hex 357 | match std::str::from_utf8(&buff[0..n as usize]) { 358 | Ok(s) => info!("Received: '{}' info: {:?}", s, i), 359 | #[cfg(not(feature = "defmt"))] 360 | Err(_) => info!("Received: '{:02x?}' info: {:?}", &buff[0..n as usize], i), 361 | #[cfg(feature = "defmt")] 362 | Err(_) => info!("Received: '{:?}' info: {:?}", &buff[0..n as usize], i), 363 | } 364 | 365 | // Append info if provided 366 | if options.append_info { 367 | NetworkEndian::write_i16(&mut buff[n..], i.rssi()); 368 | n += 2; 369 | } 370 | 371 | // Wait for turnaround delay 372 | radio.delay_us(options.delay.as_micros() as u32); 373 | 374 | // Transmit respobnse 375 | radio.do_transmit(&buff[..n], options.blocking_options.clone())?; 376 | 377 | // Exit if non-continuous 378 | if !options.continuous { 379 | return Ok(n); 380 | } 381 | } 382 | 383 | // Wait for poll delay 384 | radio.delay_us(options.blocking_options.poll_interval.as_micros() as u32); 385 | } 386 | } 387 | 388 | /// Configuration for Echo operation 389 | #[derive(Clone, Parser, PartialEq, Debug)] 390 | pub struct PingPongOptions { 391 | /// Specify the number of rounds to tx/rx 392 | #[clap(long, default_value = "100")] 393 | pub rounds: u32, 394 | 395 | /// Power in dBm (range -18dBm to 13dBm) 396 | #[clap(long)] 397 | pub power: Option, 398 | 399 | /// Specify delay for response message 400 | #[clap(long, default_value = "100ms")] 401 | pub delay: HumanDuration, 402 | 403 | /// Parse RSSI and other info from response messages 404 | /// (echo server must have --append-info set) 405 | #[clap(long)] 406 | pub parse_info: bool, 407 | 408 | #[clap(flatten)] 409 | pub blocking_options: BlockingOptions, 410 | } 411 | 412 | pub struct LinkTestInfo { 413 | pub sent: u32, 414 | pub received: u32, 415 | pub local_rssi: Stats, 416 | pub remote_rssi: Stats, 417 | } 418 | 419 | pub fn do_ping_pong( 420 | radio: &mut T, 421 | options: PingPongOptions, 422 | ) -> Result> 423 | where 424 | T: Receive + Transmit + Power + DelayNs, 425 | I: ReceiveInfo, 426 | E: std::fmt::Debug, 427 | { 428 | let mut link_info = LinkTestInfo { 429 | sent: options.rounds, 430 | received: 0, 431 | local_rssi: Stats::new(), 432 | remote_rssi: Stats::new(), 433 | }; 434 | 435 | let mut buff = [0u8; 32]; 436 | 437 | // Set output power if specified 438 | if let Some(p) = options.power { 439 | radio.set_power(p)?; 440 | } 441 | 442 | for i in 0..options.rounds { 443 | // Encode message 444 | NetworkEndian::write_u32(&mut buff[0..], i as u32); 445 | let n = 4; 446 | 447 | debug!("Sending message {}", i); 448 | 449 | // Send message 450 | radio.do_transmit(&buff[0..n], options.blocking_options.clone())?; 451 | 452 | // Await response 453 | let (n, info) = match radio.do_receive(&mut buff, options.blocking_options.clone()) { 454 | Ok(r) => r, 455 | Err(BlockingError::Timeout) => { 456 | debug!("Timeout awaiting response {}", i); 457 | continue; 458 | } 459 | Err(e) => return Err(e), 460 | }; 461 | 462 | let receive_index = NetworkEndian::read_u32(&buff[0..n]); 463 | if receive_index != i { 464 | debug!("Invalid receive index"); 465 | continue; 466 | } 467 | 468 | // Parse info if provided 469 | let remote_rssi = match options.parse_info { 470 | true => Some(NetworkEndian::read_i16(&buff[4..n])), 471 | false => None, 472 | }; 473 | 474 | debug!( 475 | "Received response {} with local rssi: {} and remote rssi: {:?}", 476 | receive_index, 477 | info.rssi(), 478 | remote_rssi 479 | ); 480 | 481 | link_info.received += 1; 482 | link_info.local_rssi.update(info.rssi() as f32); 483 | if let Some(rssi) = remote_rssi { 484 | link_info.remote_rssi.update(rssi as f32); 485 | } 486 | 487 | // Wait for send delay 488 | radio.delay_us(options.delay.as_micros() as u32); 489 | } 490 | 491 | Ok(link_info) 492 | } 493 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Abstract packet radio interfaces 2 | //! 3 | //! This package defines traits for packet radio devices, as well as blocking and async 4 | //! implementations using these traits, and a mock device to support application level testing. 5 | //! 6 | //! ## 7 | //! ## Copyright 2020-2022 Ryan Kurte 8 | 9 | // Set `no_std` where `std` feature is disabled 10 | #![cfg_attr(not(feature = "std"), no_std)] 11 | 12 | use core::convert::TryFrom; 13 | use core::fmt::Debug; 14 | 15 | pub mod blocking; 16 | pub mod config; 17 | 18 | #[cfg(feature = "helpers")] 19 | pub mod helpers; 20 | #[cfg(feature = "mock")] 21 | pub mod mock; 22 | #[cfg(feature = "nonblocking")] 23 | pub mod nonblocking; 24 | 25 | /// Radio trait combines Base, Configure, Send and Receive for a generic radio object 26 | pub trait Radio: Transmit + Receive + State {} 27 | 28 | /// Transmit trait for radios that can transmit packets 29 | /// 30 | /// `start_transmit` should be called to load data into the radio, with `check_transmit` called 31 | /// periodically (or using interrupts) to continue and finalise the transmission. 32 | pub trait Transmit { 33 | /// Radio error 34 | type Error: Debug; 35 | 36 | /// Start sending a packet on the provided channel 37 | /// 38 | /// Returns an error if send was not started 39 | fn start_transmit(&mut self, data: &[u8]) -> Result<(), Self::Error>; 40 | 41 | /// Check for send completion 42 | /// 43 | /// Returns true for send complete, false otherwise 44 | fn check_transmit(&mut self) -> Result; 45 | } 46 | 47 | /// Receive trait for radios that can receive packets 48 | /// 49 | /// `start_receive` should be used to setup the radio in receive mode, with `check_receive` called 50 | /// periodically (or using interrupts) to poll for packet reception. Once a packet has been received, 51 | /// `get_received` fetches the received packet (and associated info) from the radio. 52 | pub trait Receive { 53 | /// Radio error 54 | type Error: Debug; 55 | /// Packet received info 56 | type Info: ReceiveInfo; 57 | 58 | /// Set receiving on the specified channel 59 | /// 60 | /// Returns an error if receive mode was not entered 61 | fn start_receive(&mut self) -> Result<(), Self::Error>; 62 | 63 | /// Check for reception 64 | /// 65 | /// The restart flag indicates on (recoverable) error conditions (such as invalid CRC) 66 | /// the radio should re-enter receive mode if required and continue reception. 67 | /// 68 | /// This returns true for received, false for not received, or the provided error 69 | fn check_receive(&mut self, restart: bool) -> Result; 70 | 71 | /// Fetch a received packet if rx is complete 72 | /// 73 | /// This copies received data into the provided buffer and returns the number of bytes received 74 | /// as well as information about the received packet 75 | fn get_received(&mut self, buff: &mut [u8]) -> Result<(usize, Self::Info), Self::Error>; 76 | } 77 | 78 | /// ReceiveInfo trait for receive information objects 79 | /// 80 | /// This sup[ports the constraint of generic `Receive::Info`, allowing generic middleware 81 | /// to access the rssi of received packets 82 | pub trait ReceiveInfo: Debug + Default { 83 | fn rssi(&self) -> i16; 84 | } 85 | 86 | /// Default / Standard packet information structure for radio devices that provide only rssi 87 | /// and lqi information 88 | #[derive(Debug, Clone, PartialEq)] 89 | pub struct BasicInfo { 90 | /// Received Signal Strength Indicator (RSSI) of received packet in dBm 91 | rssi: i16, 92 | /// Link Quality Indicator (LQI) of received packet 93 | lqi: u16, 94 | } 95 | 96 | impl Default for BasicInfo { 97 | fn default() -> Self { 98 | Self { 99 | rssi: core::i16::MIN, 100 | lqi: core::u16::MIN, 101 | } 102 | } 103 | } 104 | 105 | impl BasicInfo { 106 | pub fn new(rssi: i16, lqi: u16) -> Self { 107 | Self { rssi, lqi } 108 | } 109 | } 110 | 111 | /// Default / Standard radio channel object for radio devices with simple integer channels 112 | impl ReceiveInfo for BasicInfo { 113 | fn rssi(&self) -> i16 { 114 | self.rssi 115 | } 116 | } 117 | 118 | /// Default / Standard radio channel object for radio devices with integer channels 119 | #[derive(Debug, Clone, PartialEq)] 120 | pub struct BasicChannel(pub u16); 121 | 122 | impl From for BasicChannel { 123 | fn from(u: u16) -> Self { 124 | BasicChannel(u) 125 | } 126 | } 127 | 128 | impl From for u16 { 129 | fn from(val: BasicChannel) -> Self { 130 | val.0 131 | } 132 | } 133 | 134 | /// Channel trait for configuring radio channelization 135 | pub trait Channel { 136 | /// Channel information 137 | type Channel: Debug; 138 | /// Radio error type 139 | type Error: Debug; 140 | 141 | /// Set the radio channel for future transmit and receive operations 142 | fn set_channel(&mut self, channel: &Self::Channel) -> Result<(), Self::Error>; 143 | } 144 | 145 | /// Power trait for configuring radio power 146 | pub trait Power { 147 | /// Radio error type 148 | type Error: Debug; 149 | 150 | /// Set the radio power in dBm 151 | fn set_power(&mut self, power: i8) -> Result<(), Self::Error>; 152 | } 153 | 154 | /// Rssi trait allows polling for RSSI on the current channel 155 | /// 156 | /// Note that the radio should be in receive mode prior to polling for this. 157 | pub trait Rssi { 158 | /// Radio error 159 | type Error: Debug; 160 | 161 | /// Fetch the current RSSI value from the radio 162 | /// Note that the radio MUST be in RX mode (or capable of measuring RSSI) when this is called 163 | /// or an error should be returned 164 | fn poll_rssi(&mut self) -> Result; 165 | } 166 | 167 | /// State trait for configuring and reading radio states 168 | /// 169 | /// Note that drivers will internally configure and read radio states to manage 170 | /// radio operations. 171 | pub trait State { 172 | /// Radio state 173 | type State: RadioState; 174 | /// Radio error type 175 | type Error: Debug; 176 | 177 | /// Set the radio to a specified state 178 | fn set_state(&mut self, state: Self::State) -> Result<(), Self::Error>; 179 | 180 | /// Fetch the current radio state 181 | fn get_state(&mut self) -> Result; 182 | } 183 | 184 | pub trait RadioState: Debug { 185 | fn idle() -> Self; 186 | 187 | fn sleep() -> Self; 188 | } 189 | 190 | /// Busy trait for checking whether the radio is currently busy 191 | /// and should not be interrupted 192 | pub trait Busy { 193 | /// Radio error type 194 | type Error: Debug; 195 | 196 | /// Indicates the radio is busy in the current state 197 | /// (for example, currently transmitting or receiving) 198 | fn is_busy(&mut self) -> Result; 199 | } 200 | 201 | /// Interrupts trait allows for reading interrupt state from the device, 202 | /// as well as configuring interrupt pins. 203 | /// 204 | /// Note that drivers may internally use interrupts and interrupt states 205 | /// to manage radio operations. 206 | pub trait Interrupts { 207 | /// Interrupt object 208 | type Irq: Debug; 209 | /// Radio error 210 | type Error: Debug; 211 | 212 | /// Fetch any pending interrupts from the device 213 | /// If the clear option is set, this will also clear any returned flags 214 | fn get_interrupts(&mut self, clear: bool) -> Result; 215 | } 216 | 217 | /// Register contains the address and value of a register. 218 | /// 219 | /// It is primarily intended as a type constraint for the [Registers] trait. 220 | pub trait Register: 221 | Copy + TryFrom::Error> + Into 222 | { 223 | type Word; 224 | type Error; 225 | const ADDRESS: u8; 226 | } 227 | 228 | /// Registers trait provides register level access to the radio device. 229 | /// 230 | /// This is generally too low level for use by higher abstractions, however, 231 | /// is provided for completeness. 232 | pub trait Registers { 233 | type Error: Debug; 234 | 235 | /// Read a register value 236 | fn read_register>(&mut self) -> Result; 237 | 238 | /// Write a register value 239 | fn write_register>(&mut self, value: R) -> Result<(), Self::Error>; 240 | 241 | /// Update a register value 242 | fn update_register, F: Fn(R) -> R>( 243 | &mut self, 244 | f: F, 245 | ) -> Result { 246 | let existing = self.read_register()?; 247 | let updated = f(existing); 248 | self.write_register(updated)?; 249 | Ok(updated) 250 | } 251 | } 252 | 253 | #[cfg(feature = "std")] 254 | use std::str::FromStr; 255 | 256 | #[cfg(feature = "std")] 257 | fn duration_from_str(s: &str) -> Result { 258 | let d = humantime::Duration::from_str(s)?; 259 | Ok(*d) 260 | } 261 | 262 | #[cfg(test)] 263 | mod tests { 264 | use crate::{Register, Registers}; 265 | 266 | use core::convert::{Infallible, TryInto}; 267 | 268 | #[derive(Clone, Copy, Debug, PartialEq)] 269 | struct TestRegister1 { 270 | value: u8, 271 | } 272 | 273 | impl From for TestRegister1 { 274 | fn from(value: u8) -> Self { 275 | Self { value } 276 | } 277 | } 278 | 279 | impl From for u8 { 280 | fn from(reg: TestRegister1) -> Self { 281 | reg.value 282 | } 283 | } 284 | 285 | impl Register for TestRegister1 { 286 | type Word = u8; 287 | type Error = Infallible; 288 | const ADDRESS: u8 = 0; 289 | } 290 | 291 | #[derive(Clone, Copy, Debug, PartialEq)] 292 | struct TestRegister2 { 293 | value: [u8; 2], 294 | } 295 | 296 | impl From<[u8; 2]> for TestRegister2 { 297 | fn from(value: [u8; 2]) -> Self { 298 | Self { value } 299 | } 300 | } 301 | 302 | impl From for [u8; 2] { 303 | fn from(reg: TestRegister2) -> Self { 304 | reg.value 305 | } 306 | } 307 | 308 | impl Register for TestRegister2 { 309 | type Word = [u8; 2]; 310 | type Error = Infallible; 311 | const ADDRESS: u8 = 1; 312 | } 313 | 314 | struct TestDevice { 315 | device_register: [u8; 3], 316 | } 317 | 318 | impl Registers for TestDevice { 319 | type Error = (); 320 | fn read_register>(&mut self) -> Result { 321 | self.device_register[R::ADDRESS as usize] 322 | .try_into() 323 | .map_err(|_| ()) 324 | } 325 | 326 | fn write_register>(&mut self, value: R) -> Result<(), Self::Error> { 327 | self.device_register[R::ADDRESS as usize] = value.into(); 328 | Ok(()) 329 | } 330 | } 331 | 332 | impl Registers<[u8; 2]> for TestDevice { 333 | type Error = (); 334 | fn read_register>(&mut self) -> Result { 335 | let addr = R::ADDRESS as usize; 336 | let mut result = [0u8; 2]; 337 | result.copy_from_slice(&self.device_register[addr..addr + 2]); 338 | result.try_into().map_err(|_| ()) 339 | } 340 | 341 | fn write_register>( 342 | &mut self, 343 | value: R, 344 | ) -> Result<(), Self::Error> { 345 | let addr = R::ADDRESS as usize; 346 | self.device_register[addr..addr + 2].copy_from_slice(&value.into()); 347 | Ok(()) 348 | } 349 | } 350 | 351 | #[test] 352 | fn update_register1() { 353 | let mut device = TestDevice { 354 | device_register: [0; 3], 355 | }; 356 | device.write_register(TestRegister1 { value: 1 }).unwrap(); 357 | device 358 | .update_register(|r: TestRegister1| (if r.value == 1 { 2 } else { 3 }).into()) 359 | .unwrap(); 360 | assert_eq!( 361 | device.read_register::().unwrap(), 362 | TestRegister1 { value: 2 } 363 | ); 364 | } 365 | 366 | #[test] 367 | fn update_register2() { 368 | let mut device = TestDevice { 369 | device_register: [0; 3], 370 | }; 371 | device 372 | .write_register(TestRegister2 { value: [1, 2] }) 373 | .unwrap(); 374 | device 375 | .update_register(|r: TestRegister2| { 376 | (if r.value == [1, 2] { [2, 3] } else { [3, 4] }).into() 377 | }) 378 | .unwrap(); 379 | assert_eq!( 380 | device.read_register::().unwrap(), 381 | TestRegister2 { value: [2, 3] } 382 | ); 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /src/mock.rs: -------------------------------------------------------------------------------- 1 | //! Mock radio driver for application testing 2 | //! 3 | //! This provides a generic and specific mock implementation of the radio traits 4 | //! to support network and application level testing. 5 | //! 6 | //! ## 7 | //! ## Copyright 2020-2022 Ryan Kurte 8 | 9 | extern crate std; 10 | use std::convert::Infallible; 11 | use std::fmt::Debug; 12 | use std::vec::Vec; 13 | 14 | use log::debug; 15 | 16 | use embedded_hal::delay::DelayNs; 17 | 18 | use embedded_hal_mock::common::Generic; 19 | 20 | use crate::{ 21 | BasicInfo, Busy, Channel, Interrupts, Power, RadioState, Receive, ReceiveInfo, Rssi, State, 22 | Transmit, 23 | }; 24 | 25 | /// Generic mock radio 26 | /// 27 | /// Based on `embedded_hal_mock::common::Generic` 28 | #[derive(Debug, Clone)] 29 | pub struct Radio< 30 | St: Debug + Clone + PartialEq, 31 | Reg: Debug + Clone + PartialEq, 32 | Ch: Debug + Clone + PartialEq, 33 | Inf: Debug + Clone + PartialEq, 34 | Irq: Debug + Clone + PartialEq, 35 | E: Debug + Clone + PartialEq, 36 | > { 37 | inner: Generic>, 38 | } 39 | 40 | impl Radio 41 | where 42 | St: PartialEq + Debug + Clone, 43 | Reg: PartialEq + Debug + Clone, 44 | Ch: PartialEq + Debug + Clone, 45 | Inf: PartialEq + Debug + Clone, 46 | Irq: PartialEq + Debug + Clone, 47 | E: PartialEq + Debug + Clone, 48 | { 49 | pub fn new(expectations: &[Transaction]) -> Self { 50 | let inner = Generic::new(expectations); 51 | 52 | Self { inner } 53 | } 54 | 55 | pub fn expect(&mut self, expectations: &[Transaction]) { 56 | self.inner.expect(expectations); 57 | } 58 | 59 | pub fn next(&mut self) -> Option> { 60 | self.inner.next() 61 | } 62 | 63 | pub fn done(&mut self) { 64 | self.inner.done() 65 | } 66 | } 67 | 68 | /// Concrete mock radio using mock types 69 | pub type MockRadio = Radio; 70 | 71 | /// MockState for use with mock radio 72 | #[derive(Debug, Clone, PartialEq)] 73 | pub enum MockState { 74 | Idle, 75 | Sleep, 76 | Receive, 77 | Receiving, 78 | Transmitting, 79 | } 80 | 81 | impl crate::RadioState for MockState { 82 | fn idle() -> Self { 83 | Self::Idle 84 | } 85 | 86 | fn sleep() -> Self { 87 | Self::Sleep 88 | } 89 | } 90 | 91 | /// MockError for use with mock radio 92 | #[derive(Debug, Clone, PartialEq)] 93 | #[cfg_attr(feature = "thiserror", derive(thiserror::Error))] 94 | pub enum MockError { 95 | #[cfg_attr(feature = "thiserror", error("Timeout"))] 96 | Timeout, 97 | } 98 | 99 | /// Transactions describe interactions with a radio device 100 | #[derive(Debug, Clone, PartialEq)] 101 | pub struct Transaction { 102 | request: Request, 103 | response: Response, 104 | } 105 | 106 | impl Transaction { 107 | /// Set the radio state 108 | pub fn set_state(state: St, err: Option) -> Self { 109 | Self { 110 | request: Request::SetState(state), 111 | response: err.into(), 112 | } 113 | } 114 | 115 | /// Get the radio state 116 | pub fn get_state(res: Result) -> Self { 117 | Self { 118 | request: Request::GetState, 119 | response: res.map_or_else(Response::Err, Response::State), 120 | } 121 | } 122 | 123 | /// Check whether radio is currently busy 124 | pub fn is_busy(res: Result) -> Self { 125 | Self { 126 | request: Request::IsBusy, 127 | response: res.map_or_else(Response::Err, Response::Bool), 128 | } 129 | } 130 | 131 | /// Set a radio register 132 | pub fn set_register(reg: Reg, value: u8, err: Option) -> Self { 133 | Self { 134 | request: Request::SetRegister(reg, value), 135 | response: err.into(), 136 | } 137 | } 138 | 139 | /// Get a radio register 140 | pub fn get_register(res: Result) -> Self { 141 | Self { 142 | request: Request::GetRegister, 143 | response: res.map_or_else(Response::Err, Response::Register), 144 | } 145 | } 146 | 147 | /// Set a radio channel 148 | pub fn set_channel(ch: Ch, err: Option) -> Self { 149 | Self { 150 | request: Request::SetChannel(ch), 151 | response: err.into(), 152 | } 153 | } 154 | 155 | /// Set radio power 156 | pub fn set_power(power: i8, err: Option) -> Self { 157 | Self { 158 | request: Request::SetPower(power), 159 | response: err.into(), 160 | } 161 | } 162 | 163 | /// Start radio transmission 164 | pub fn start_transmit(data: Vec, err: Option) -> Self { 165 | Self { 166 | request: Request::StartTransmit(data), 167 | response: err.into(), 168 | } 169 | } 170 | 171 | /// Check for transmission completed 172 | pub fn check_transmit(res: Result) -> Self { 173 | Self { 174 | request: Request::CheckTransmit, 175 | response: res.map_or_else(Response::Err, Response::Bool), 176 | } 177 | } 178 | 179 | /// Start radio reception 180 | pub fn start_receive(err: Option) -> Self { 181 | Self { 182 | request: Request::StartReceive, 183 | response: err.into(), 184 | } 185 | } 186 | 187 | /// Check for radio reception 188 | pub fn check_receive(restart: bool, res: Result) -> Self { 189 | Self { 190 | request: Request::CheckReceive(restart), 191 | response: res.map_or_else(Response::Err, Response::Bool), 192 | } 193 | } 194 | 195 | /// Fetch received data and information 196 | pub fn get_received(res: Result<(Vec, Inf), E>) -> Self { 197 | Self { 198 | request: Request::GetReceived, 199 | response: res.map_or_else(Response::Err, |(d, i)| Response::Received(d, i)), 200 | } 201 | } 202 | 203 | /// Fetch radio IRQs 204 | pub fn get_irq(clear: bool, res: Result) -> Self { 205 | Self { 206 | request: Request::GetIrq(clear), 207 | response: res.map_or_else(Response::Err, Response::Irq), 208 | } 209 | } 210 | 211 | /// Poll for RSSI 212 | pub fn poll_rssi(res: Result) -> Self { 213 | Self { 214 | request: Request::PollRssi, 215 | response: res.map_or_else(Response::Err, Response::Rssi), 216 | } 217 | } 218 | 219 | /// Delay for a certain time 220 | pub fn delay_ns(ns: u32) -> Self { 221 | Self { 222 | request: Request::DelayNs(ns), 223 | response: Response::Ok, 224 | } 225 | } 226 | } 227 | 228 | #[derive(Debug, Clone, PartialEq)] 229 | enum Request { 230 | SetState(St), 231 | GetState, 232 | IsBusy, 233 | 234 | SetRegister(Reg, u8), 235 | GetRegister, 236 | 237 | GetIrq(bool), 238 | PollRssi, 239 | 240 | SetChannel(Ch), 241 | SetPower(i8), 242 | 243 | StartTransmit(Vec), 244 | CheckTransmit, 245 | 246 | StartReceive, 247 | CheckReceive(bool), 248 | GetReceived, 249 | 250 | DelayNs(u32), 251 | } 252 | 253 | #[derive(Debug, Clone, PartialEq)] 254 | enum Response { 255 | Ok, 256 | State(St), 257 | Register(u8), 258 | Irq(Irq), 259 | Rssi(i16), 260 | Received(Vec, Inf), 261 | Bool(bool), 262 | Err(E), 263 | } 264 | 265 | impl From> for Response { 266 | fn from(e: Option) -> Self { 267 | match e { 268 | Some(v) => Response::Err(v), 269 | None => Response::Ok, 270 | } 271 | } 272 | } 273 | 274 | impl DelayNs for Radio 275 | where 276 | St: PartialEq + Debug + Clone, 277 | Reg: PartialEq + Debug + Clone, 278 | Ch: PartialEq + Debug + Clone, 279 | Inf: PartialEq + Debug + Clone, 280 | Irq: PartialEq + Debug + Clone, 281 | E: PartialEq + Debug + Clone, 282 | { 283 | fn delay_ns(&mut self, ns: u32) { 284 | let n = self.next().expect("no expectation for delay_ns call"); 285 | 286 | assert_eq!(&n.request, &Request::DelayNs(ns)); 287 | } 288 | } 289 | 290 | impl State for Radio 291 | where 292 | St: RadioState + PartialEq + Debug + Clone, 293 | Reg: PartialEq + Debug + Clone, 294 | Ch: PartialEq + Debug + Clone, 295 | Inf: PartialEq + Debug + Clone, 296 | Irq: PartialEq + Debug + Clone, 297 | E: PartialEq + Debug + Clone, 298 | { 299 | type State = St; 300 | type Error = E; 301 | 302 | fn set_state(&mut self, state: Self::State) -> Result<(), Self::Error> { 303 | let n = self 304 | .next() 305 | .expect("no expectation for State::set_state call"); 306 | 307 | assert_eq!(&n.request, &Request::SetState(state.clone())); 308 | 309 | let res = match &n.response { 310 | Response::Ok => Ok(()), 311 | Response::Err(e) => Err(e.clone()), 312 | _ => unreachable!(), 313 | }; 314 | 315 | debug!("Set state: {:?}: {:?}", state, res); 316 | 317 | res 318 | } 319 | 320 | fn get_state(&mut self) -> Result { 321 | let n = self 322 | .next() 323 | .expect("no expectation for State::get_state call"); 324 | 325 | assert_eq!(&n.request, &Request::GetState); 326 | 327 | let res = match &n.response { 328 | Response::Err(e) => Err(e.clone()), 329 | Response::State(s) => Ok(s.clone()), 330 | _ => unreachable!(), 331 | }; 332 | 333 | debug!("Get state {:?}", res); 334 | 335 | res 336 | } 337 | } 338 | 339 | impl Busy for Radio 340 | where 341 | St: PartialEq + Debug + Clone, 342 | Reg: PartialEq + Debug + Clone, 343 | Ch: PartialEq + Debug + Clone, 344 | Inf: PartialEq + Debug + Clone, 345 | Irq: PartialEq + Debug + Clone, 346 | E: PartialEq + Debug + Clone, 347 | { 348 | type Error = E; 349 | 350 | fn is_busy(&mut self) -> Result { 351 | let n = self.next().expect("no expectation for is_busy call"); 352 | 353 | assert_eq!(&n.request, &Request::IsBusy); 354 | 355 | let res = match &n.response { 356 | Response::Err(e) => Err(e.clone()), 357 | Response::Bool(s) => Ok(s.clone()), 358 | _ => unreachable!(), 359 | }; 360 | 361 | debug!("Is busy {:?}", res); 362 | 363 | res 364 | } 365 | } 366 | 367 | impl Channel for Radio 368 | where 369 | St: PartialEq + Debug + Clone, 370 | Reg: PartialEq + Debug + Clone, 371 | Ch: PartialEq + Debug + Clone, 372 | Inf: PartialEq + Debug + Clone, 373 | Irq: PartialEq + Debug + Clone, 374 | E: PartialEq + Debug + Clone, 375 | { 376 | type Channel = Ch; 377 | type Error = E; 378 | 379 | fn set_channel(&mut self, channel: &Self::Channel) -> Result<(), Self::Error> { 380 | debug!("Set channel {:?}", channel); 381 | 382 | let n = self 383 | .next() 384 | .expect("no expectation for State::set_channel call"); 385 | 386 | assert_eq!(&n.request, &Request::SetChannel(channel.clone())); 387 | 388 | match &n.response { 389 | Response::Ok => Ok(()), 390 | Response::Err(e) => Err(e.clone()), 391 | _ => unreachable!(), 392 | } 393 | } 394 | } 395 | 396 | impl Power for Radio 397 | where 398 | St: PartialEq + Debug + Clone, 399 | Reg: PartialEq + Debug + Clone, 400 | Ch: PartialEq + Debug + Clone, 401 | Inf: PartialEq + Debug + Clone, 402 | Irq: PartialEq + Debug + Clone, 403 | E: PartialEq + Debug + Clone, 404 | { 405 | type Error = E; 406 | 407 | fn set_power(&mut self, power: i8) -> Result<(), Self::Error> { 408 | debug!("Set power {:?}", power); 409 | 410 | let n = self 411 | .next() 412 | .expect("no expectation for Power::set_power call"); 413 | 414 | assert_eq!(&n.request, &Request::SetPower(power)); 415 | 416 | match &n.response { 417 | Response::Ok => Ok(()), 418 | Response::Err(e) => Err(e.clone()), 419 | _ => unreachable!(), 420 | } 421 | } 422 | } 423 | 424 | impl Rssi for Radio 425 | where 426 | St: PartialEq + Debug + Clone, 427 | Reg: PartialEq + Debug + Clone, 428 | Ch: PartialEq + Debug + Clone, 429 | Inf: PartialEq + Debug + Clone, 430 | Irq: PartialEq + Debug + Clone, 431 | E: PartialEq + Debug + Clone, 432 | { 433 | type Error = E; 434 | 435 | fn poll_rssi(&mut self) -> Result { 436 | let n = self 437 | .next() 438 | .expect("no expectation for Rssi::poll_rssi call"); 439 | 440 | assert_eq!(&n.request, &Request::PollRssi); 441 | 442 | let res = match &n.response { 443 | Response::Rssi(v) => Ok(v.clone()), 444 | Response::Err(e) => Err(e.clone()), 445 | _ => unreachable!(), 446 | }; 447 | 448 | debug!("Poll RSSI {:?}", res); 449 | 450 | res 451 | } 452 | } 453 | 454 | impl Interrupts for Radio 455 | where 456 | St: PartialEq + Debug + Clone, 457 | Reg: PartialEq + Debug + Clone, 458 | Ch: PartialEq + Debug + Clone, 459 | Inf: PartialEq + Debug + Clone, 460 | Irq: PartialEq + Debug + Clone, 461 | E: PartialEq + Debug + Clone, 462 | { 463 | type Error = E; 464 | type Irq = Irq; 465 | 466 | fn get_interrupts(&mut self, clear: bool) -> Result { 467 | let n = self 468 | .next() 469 | .expect("no expectation for Transmit::check_transmit call"); 470 | 471 | assert_eq!(&n.request, &Request::GetIrq(clear)); 472 | 473 | let res = match &n.response { 474 | Response::Irq(v) => Ok(v.clone()), 475 | Response::Err(e) => Err(e.clone()), 476 | _ => unreachable!(), 477 | }; 478 | 479 | debug!("Get Interrupts {:?}", res); 480 | 481 | res 482 | } 483 | } 484 | 485 | impl Transmit for Radio 486 | where 487 | St: PartialEq + Debug + Clone, 488 | Reg: PartialEq + Debug + Clone, 489 | Ch: PartialEq + Debug + Clone, 490 | Inf: PartialEq + Debug + Clone, 491 | Irq: PartialEq + Debug + Clone, 492 | E: PartialEq + Debug + Clone, 493 | { 494 | type Error = E; 495 | 496 | fn start_transmit(&mut self, data: &[u8]) -> Result<(), Self::Error> { 497 | let n = self 498 | .next() 499 | .expect("no expectation for Transmit::start_transmit call"); 500 | 501 | assert_eq!(&n.request, &Request::StartTransmit(data.to_vec())); 502 | 503 | let res = match &n.response { 504 | Response::Ok => Ok(()), 505 | Response::Err(e) => Err(e.clone()), 506 | _ => unreachable!(), 507 | }; 508 | 509 | debug!("Start transmit {:?}: {:?}", data, res); 510 | 511 | res 512 | } 513 | 514 | fn check_transmit(&mut self) -> Result { 515 | let n = self 516 | .next() 517 | .expect("no expectation for Transmit::check_transmit call"); 518 | 519 | assert_eq!(&n.request, &Request::CheckTransmit); 520 | 521 | let res = match &n.response { 522 | Response::Bool(v) => Ok(*v), 523 | Response::Err(e) => Err(e.clone()), 524 | _ => unreachable!(), 525 | }; 526 | 527 | debug!("Check transmit {:?}", res); 528 | 529 | res 530 | } 531 | } 532 | 533 | impl Receive for Radio 534 | where 535 | St: PartialEq + Debug + Clone, 536 | Reg: PartialEq + Debug + Clone, 537 | Ch: PartialEq + Debug + Clone, 538 | Inf: ReceiveInfo + PartialEq + Debug + Clone, 539 | Irq: PartialEq + Debug + Clone, 540 | E: PartialEq + Debug + Clone, 541 | { 542 | type Info = Inf; 543 | type Error = E; 544 | 545 | fn start_receive(&mut self) -> Result<(), Self::Error> { 546 | let n = self 547 | .next() 548 | .expect("no expectation for Receive::start_receive call"); 549 | 550 | assert_eq!(&n.request, &Request::StartReceive); 551 | 552 | let res = match &n.response { 553 | Response::Ok => Ok(()), 554 | Response::Err(e) => Err(e.clone()), 555 | _ => unreachable!(), 556 | }; 557 | 558 | debug!("Start receive {:?}", res); 559 | 560 | res 561 | } 562 | 563 | fn check_receive(&mut self, restart: bool) -> Result { 564 | let n = self 565 | .next() 566 | .expect("no expectation for Receive::check_receive call"); 567 | 568 | assert_eq!(&n.request, &Request::CheckReceive(restart)); 569 | 570 | let res = match &n.response { 571 | Response::Bool(v) => Ok(*v), 572 | Response::Err(e) => Err(e.clone()), 573 | _ => unreachable!(), 574 | }; 575 | 576 | debug!("Check receive {:?}", res); 577 | 578 | res 579 | } 580 | 581 | fn get_received(&mut self, buff: &mut [u8]) -> Result<(usize, Self::Info), Self::Error> { 582 | let n = self 583 | .next() 584 | .expect("no expectation for Receive::get_received call"); 585 | 586 | assert_eq!(&n.request, &Request::GetReceived); 587 | 588 | let res = match &n.response { 589 | Response::Received(d, i) => { 590 | buff[..d.len()].copy_from_slice(&d); 591 | 592 | Ok((d.len(), i.clone())) 593 | } 594 | Response::Err(e) => Err(e.clone()), 595 | _ => unreachable!(), 596 | }; 597 | 598 | debug!("Get received {:?}", res); 599 | 600 | res 601 | } 602 | } 603 | 604 | #[cfg(test)] 605 | mod test { 606 | use std::vec; 607 | 608 | use super::*; 609 | 610 | #[test] 611 | fn test_radio_mock_set_state() { 612 | let mut radio = MockRadio::new(&[Transaction::set_state(MockState::Idle, None)]); 613 | 614 | radio.set_state(MockState::Idle).unwrap(); 615 | 616 | radio.done(); 617 | } 618 | 619 | #[test] 620 | #[should_panic] 621 | fn test_radio_mock_set_incorrect_state() { 622 | let mut radio = MockRadio::new(&[Transaction::set_state(MockState::Idle, None)]); 623 | 624 | radio.set_state(MockState::Sleep).unwrap(); 625 | 626 | radio.done(); 627 | } 628 | 629 | #[test] 630 | fn test_radio_mock_get_state() { 631 | let mut radio = MockRadio::new(&[Transaction::get_state(Ok(MockState::Idle))]); 632 | 633 | let res = radio.get_state().unwrap(); 634 | assert_eq!(res, MockState::Idle); 635 | 636 | radio.done(); 637 | } 638 | 639 | #[test] 640 | fn test_radio_mock_set_channel() { 641 | let mut radio = MockRadio::new(&[Transaction::set_channel(10, None)]); 642 | 643 | let _res = radio.set_channel(&10).unwrap(); 644 | 645 | radio.done(); 646 | } 647 | 648 | #[test] 649 | fn test_radio_mock_set_power() { 650 | let mut radio = MockRadio::new(&[Transaction::set_power(10, None)]); 651 | 652 | let _res = radio.set_power(10).unwrap(); 653 | 654 | radio.done(); 655 | } 656 | 657 | #[test] 658 | fn test_radio_mock_start_transmit() { 659 | let mut radio = 660 | MockRadio::new(&[Transaction::start_transmit(vec![0xaa, 0xbb, 0xcc], None)]); 661 | 662 | let _res = radio.start_transmit(&[0xaa, 0xbb, 0xcc]).unwrap(); 663 | 664 | radio.done(); 665 | } 666 | 667 | #[test] 668 | fn test_radio_mock_check_transmit() { 669 | let mut radio = MockRadio::new(&[ 670 | Transaction::check_transmit(Ok(false)), 671 | Transaction::check_transmit(Ok(true)), 672 | ]); 673 | 674 | let res = radio.check_transmit().unwrap(); 675 | assert_eq!(false, res); 676 | 677 | let res = radio.check_transmit().unwrap(); 678 | assert_eq!(true, res); 679 | 680 | radio.done(); 681 | } 682 | 683 | #[test] 684 | fn test_radio_mock_start_receive() { 685 | let mut radio = MockRadio::new(&[Transaction::start_receive(None)]); 686 | 687 | let _res = radio.start_receive().unwrap(); 688 | 689 | radio.done(); 690 | } 691 | 692 | #[test] 693 | fn test_radio_mock_check_receive() { 694 | let mut radio = MockRadio::new(&[ 695 | Transaction::check_receive(true, Ok(false)), 696 | Transaction::check_receive(true, Ok(true)), 697 | ]); 698 | 699 | let res = radio.check_receive(true).unwrap(); 700 | assert_eq!(false, res); 701 | 702 | let res = radio.check_receive(true).unwrap(); 703 | assert_eq!(true, res); 704 | 705 | radio.done(); 706 | } 707 | 708 | #[test] 709 | fn test_radio_mock_get_received() { 710 | let mut radio = MockRadio::new(&[Transaction::get_received(Ok(( 711 | vec![0xaa, 0xbb], 712 | BasicInfo::new(10, 12), 713 | )))]); 714 | 715 | let mut buff = vec![0u8; 3]; 716 | 717 | let (n, _i) = radio.get_received(&mut buff).unwrap(); 718 | 719 | assert_eq!(2, n); 720 | assert_eq!(&buff[..2], &[0xaa, 0xbb]); 721 | 722 | radio.done(); 723 | } 724 | } 725 | -------------------------------------------------------------------------------- /src/nonblocking.rs: -------------------------------------------------------------------------------- 1 | //! Non-blocking (async/await) APIs on top of the base radio traits 2 | //! Note that this _requires_ use of unstable `feature(generic_associated_types)` 3 | //! 4 | //! ## 5 | //! ## Copyright 2020-2022 Ryan Kurte 6 | 7 | use core::fmt::Debug; 8 | use core::future::Future; 9 | use core::marker::PhantomData; 10 | use core::pin::Pin; 11 | use core::task::{Context, Poll}; 12 | use core::time::Duration; 13 | 14 | use crate::{Power, Receive, ReceiveInfo, Transmit}; 15 | 16 | /// Options for async driver calls 17 | pub struct AsyncOptions { 18 | /// Power option, for transmit operations 19 | pub power: Option, 20 | 21 | /// Timeout option for underlying radio operations 22 | #[deprecated(note = "Timeouts must (currently) be implemented outside this module")] 23 | pub timeout: Option, 24 | 25 | /// Period for polling on operation status with custom wakers 26 | pub poll_period: Duration, 27 | 28 | /// Waker function to be called in the `Poll` method 29 | pub wake_fn: Option<&'static fn(cx: &mut Context, d: Duration)>, 30 | } 31 | 32 | impl Default for AsyncOptions { 33 | #[allow(deprecated)] 34 | fn default() -> Self { 35 | Self { 36 | power: None, 37 | timeout: None, 38 | poll_period: Duration::from_millis(10), 39 | wake_fn: None, 40 | } 41 | } 42 | } 43 | 44 | /// AsyncError wraps radio errors and provides notification of timeouts 45 | #[derive(Clone, Debug, PartialEq)] 46 | #[cfg_attr(feature = "thiserror", derive(thiserror::Error))] 47 | pub enum AsyncError { 48 | #[cfg_attr(feature = "thiserror", error("Inner: {0}"))] 49 | Inner(E), 50 | #[cfg_attr(feature = "thiserror", error("Timeout"))] 51 | Timeout, 52 | } 53 | 54 | impl From for AsyncError { 55 | fn from(e: E) -> Self { 56 | AsyncError::Inner(e) 57 | } 58 | } 59 | 60 | /// Async transmit function implemented over `radio::Transmit` and `radio::Power` using the provided `AsyncOptions` 61 | /// 62 | #[cfg_attr( 63 | feature = "mock", 64 | doc = r##" 65 | ``` 66 | extern crate async_std; 67 | use async_std::task; 68 | 69 | # use radio::*; 70 | # use radio::mock::*; 71 | use radio::nonblocking::{AsyncTransmit, AsyncOptions}; 72 | 73 | # let mut radio = MockRadio::new(&[ 74 | # Transaction::start_transmit(vec![0xaa, 0xbb], None), 75 | # Transaction::check_transmit(Ok(false)), 76 | # Transaction::check_transmit(Ok(true)), 77 | # ]); 78 | # 79 | let res = task::block_on(async { 80 | // Transmit using a future 81 | radio.async_transmit(&[0xaa, 0xbb], AsyncOptions::default())?.await 82 | }); 83 | 84 | assert_eq!(res, Ok(())); 85 | 86 | # radio.done(); 87 | ``` 88 | "## 89 | )] 90 | 91 | /// AsyncTransmit function provides an async implementation for transmitting packets 92 | pub trait AsyncTransmit<'a, E> { 93 | type Output: Future>>; 94 | 95 | fn async_transmit( 96 | &'a mut self, 97 | data: &'a [u8], 98 | tx_options: AsyncOptions, 99 | ) -> Result; 100 | } 101 | 102 | /// Future object containing a radio for transmit operation 103 | pub struct TransmitFuture<'a, T, E> { 104 | radio: &'a mut T, 105 | options: AsyncOptions, 106 | _err: PhantomData, 107 | } 108 | 109 | /// `AsyncTransmit` object for all `Transmit` devices 110 | impl<'a, T, E> AsyncTransmit<'a, E> for T 111 | where 112 | T: Transmit + Power + 'a, 113 | E: Debug + Unpin, 114 | { 115 | type Output = TransmitFuture<'a, T, E>; 116 | 117 | fn async_transmit( 118 | &'a mut self, 119 | data: &'a [u8], 120 | tx_options: AsyncOptions, 121 | ) -> Result { 122 | // Set output power if specified 123 | if let Some(p) = tx_options.power { 124 | self.set_power(p)?; 125 | } 126 | 127 | // Start transmission 128 | self.start_transmit(data)?; 129 | 130 | // Create transmit future 131 | let f: TransmitFuture<_, E> = TransmitFuture { 132 | radio: self, 133 | options: tx_options, 134 | _err: PhantomData, 135 | }; 136 | 137 | Ok(f) 138 | } 139 | } 140 | 141 | impl<'a, T, E> Future for TransmitFuture<'a, T, E> 142 | where 143 | T: Transmit + Power, 144 | E: Debug + Unpin, 145 | { 146 | type Output = Result<(), AsyncError>; 147 | 148 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 149 | let s = self.get_mut(); 150 | let period = s.options.poll_period.clone(); 151 | 152 | // Check for completion 153 | if s.radio.check_transmit()? { 154 | return Poll::Ready(Ok(())); 155 | }; 156 | 157 | // Spawn task to re-execute waker 158 | if let Some(w) = s.options.wake_fn { 159 | w(cx, period); 160 | } else { 161 | cx.waker().clone().wake(); 162 | } 163 | 164 | // Indicate there is still work to be done 165 | Poll::Pending 166 | } 167 | } 168 | 169 | /// Async transmit function implemented over `radio::Transmit` and `radio::Power` using the provided `AsyncOptions` 170 | /// 171 | #[cfg_attr( 172 | feature = "mock", 173 | doc = r##" 174 | ``` 175 | extern crate async_std; 176 | use async_std::task; 177 | 178 | # use radio::*; 179 | # use radio::mock::*; 180 | use radio::nonblocking::{AsyncReceive, AsyncOptions}; 181 | 182 | let data = [0xaa, 0xbb]; 183 | let info = BasicInfo::new(-81, 0); 184 | 185 | # let mut radio = MockRadio::new(&[ 186 | # Transaction::start_receive(None), 187 | # Transaction::check_receive(true, Ok(false)), 188 | # Transaction::check_receive(true, Ok(true)), 189 | # Transaction::get_received(Ok((data.to_vec(), info.clone()))), 190 | # ]); 191 | # 192 | 193 | // Setup buffer and receive info 194 | let mut buff = [0u8; 128]; 195 | let mut i = BasicInfo::new(0, 0); 196 | 197 | let (n, i) = task::block_on(async { 198 | // Receive using a future 199 | radio.async_receive(&mut buff, AsyncOptions::default())?.await 200 | })?; 201 | 202 | assert_eq!(n, data.len()); 203 | assert_eq!(&buff[..data.len()], &data); 204 | 205 | # radio.done(); 206 | 207 | Ok::<(), anyhow::Error>(()) 208 | ``` 209 | "## 210 | )] 211 | 212 | /// AsyncReceive trait support futures-based polling on receive 213 | pub trait AsyncReceive<'a, I, E> { 214 | type Output: Future>>; 215 | 216 | fn async_receive( 217 | &'a mut self, 218 | buff: &'a mut [u8], 219 | rx_options: AsyncOptions, 220 | ) -> Result; 221 | } 222 | 223 | /// Receive future wraps a radio and buffer to provide a pollable future for receiving packets 224 | pub struct ReceiveFuture<'a, T, I, E> { 225 | radio: &'a mut T, 226 | buff: &'a mut [u8], 227 | options: AsyncOptions, 228 | _inf: PhantomData, 229 | _err: PhantomData, 230 | } 231 | 232 | /// Generic implementation of `AsyncReceive` for all `Receive` capable radio devices 233 | impl<'a, T, I, E> AsyncReceive<'a, I, E> for T 234 | where 235 | T: Receive + 'a, 236 | I: ReceiveInfo + Unpin + 'a, 237 | E: Debug + Unpin, 238 | { 239 | type Output = ReceiveFuture<'a, T, I, E>; 240 | 241 | fn async_receive( 242 | &'a mut self, 243 | buff: &'a mut [u8], 244 | rx_options: AsyncOptions, 245 | ) -> Result { 246 | // Start receive mode 247 | self.start_receive()?; 248 | 249 | // Create receive future 250 | let f: ReceiveFuture<_, I, E> = ReceiveFuture { 251 | radio: self, 252 | buff, 253 | options: rx_options, 254 | _inf: PhantomData, 255 | _err: PhantomData, 256 | }; 257 | 258 | Ok(f) 259 | } 260 | } 261 | 262 | impl<'a, T, I, E> Future for ReceiveFuture<'a, T, I, E> 263 | where 264 | T: Receive, 265 | I: ReceiveInfo + Unpin, 266 | E: Debug + Unpin, 267 | { 268 | type Output = Result<(usize, I), AsyncError>; 269 | 270 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 271 | let s = self.get_mut(); 272 | 273 | // Check for completion 274 | if s.radio.check_receive(true)? { 275 | // Retrieve data 276 | let (n, i) = s.radio.get_received(s.buff)?; 277 | 278 | return Poll::Ready(Ok((n, i))); 279 | } 280 | 281 | // TODO: should timeouts be internal or external? 282 | 283 | // Execute wake function 284 | if let Some(w) = s.options.wake_fn { 285 | w(cx, s.options.poll_period) 286 | } else { 287 | cx.waker().clone().wake(); 288 | } 289 | 290 | // Indicate there is still work to be done 291 | Poll::Pending 292 | } 293 | } 294 | 295 | /// Task waker using async_std task::spawn with a task::sleep. 296 | /// Note that this cannot be relied on for accurate timing 297 | #[cfg(feature = "async-std")] 298 | pub fn async_std_task_waker(cx: &mut Context, period: Duration) { 299 | let waker = cx.waker().clone(); 300 | async_std::task::spawn(async move { 301 | async_std::task::sleep(period).await; 302 | waker.wake(); 303 | }); 304 | } 305 | --------------------------------------------------------------------------------