Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add shared modulation types for gfsk, lora #24

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/blocking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//! These implementations use the radio's DelayUs implementation to
//! poll on completion of operations.
//!
//! ## https://github.com/ryankurte/rust-radio
//! ## <https://github.com/ryankurte/rust-radio>
//! ## Copyright 2020 Ryan Kurte

use core::fmt::Debug;
Expand Down
20 changes: 6 additions & 14 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,30 +34,22 @@ pub enum ConfigOption {
Promiscuous(bool),
}

/// Radio configuration errors
/// This should be extended with errors generally relevant to configuration,
/// with radio-specific errors passed through the Other(E) field.
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq)]
pub enum ConfigError<E> {
/// Configuration option not supported
NotSupported,

/// Other (device, non-configuration errors)
Other(E),
pub trait ConfigError {
/// Indicates a configuration option is not supported
fn not_supported() -> bool;
}

/// Configure trait implemented by configurable radios
pub trait Configure {
/// Radio error
type Error;
type Error: ConfigError;

/// Set a configuration option
/// Returns Ok(true) on set, Ok(false) for unsupported options, Err(Self::Error) for errors
fn set_option(&mut self, o: &ConfigOption) -> Result<(), ConfigError<Self::Error>>;
fn set_option(&mut self, o: &ConfigOption) -> Result<(), Self::Error>;

/// Fetch a configuration option
/// This will overwrite the value of the provided option enum
/// Returns Ok(true) on successful get, Ok(false) for unsupported options, Err(Self::Error) for errors
fn get_option(&mut self, o: &mut ConfigOption) -> Result<(), ConfigError<Self::Error>>;
fn get_option(&mut self, o: &mut ConfigOption) -> Result<(), Self::Error>;
}
26 changes: 14 additions & 12 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@
use core::convert::TryFrom;
use core::fmt::Debug;

#[cfg(feature = "std")]
use std::str::FromStr;

pub mod blocking;
pub mod config;
pub mod modulation;

#[cfg(feature = "helpers")]
pub mod helpers;
Expand All @@ -22,13 +26,12 @@ pub mod mock;
#[cfg(feature = "nonblocking")]
pub mod nonblocking;

/// Radio trait combines Base, Configure, Send and Receive for a generic radio object
/// Radio trait combines Transmit, Receive, and State for a generic radio object
pub trait Radio: Transmit + Receive + State {}

/// Transmit trait for radios that can transmit packets
///
/// `start_transmit` should be called to load data into the radio, with `check_transmit` called
/// periodically (or using interrupts) to continue and finalise the transmission.
/// periodically (or triggered by interrupts) to continue and finalise the transmission.
pub trait Transmit {
/// Radio error
type Error: Debug;
Expand All @@ -47,8 +50,10 @@ pub trait Transmit {
/// Receive trait for radios that can receive packets
///
/// `start_receive` should be used to setup the radio in receive mode, with `check_receive` called
/// periodically (or using interrupts) to poll for packet reception. Once a packet has been received,
/// `get_received` fetches the received packet (and associated info) from the radio.
/// periodically (or triggered by interrupts) to poll for packet reception. Once a packet has been received,
/// `get_received` fetches the received packet (and associated information) from the radio.
///
/// If you need to check for an receive operation in progress check out the [`Busy`] (or [`State`]) traits.
pub trait Receive {
/// Radio error
type Error: Debug;
Expand Down Expand Up @@ -125,15 +130,15 @@ impl From<u16> for BasicChannel {
}
}

impl Into<u16> for BasicChannel {
fn into(self) -> u16 {
self.0
impl From<BasicChannel> for u16 {
fn from(ch: BasicChannel) -> u16 {
ch.0
}
}

/// Channel trait for configuring radio channelization
pub trait Channel {
/// Channel information
/// Radio channel type
type Channel: Debug;
/// Radio error type
type Error: Debug;
Expand Down Expand Up @@ -250,9 +255,6 @@ pub trait Registers<Word> {
}
}

#[cfg(feature = "std")]
use std::str::FromStr;

#[cfg(feature = "std")]
fn duration_from_str(s: &str) -> Result<core::time::Duration, humantime::DurationError> {
let d = humantime::Duration::from_str(s)?;
Expand Down
17 changes: 17 additions & 0 deletions src/modulation/gfsk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//! Common GFSK modulation options

use super::Freq;

/// Basic GFSK channel configuration
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct GfskChannel {
/// Channel frequency
pub freq: Freq,

/// Channel bandwidth
pub bw_khz: Freq,

/// Bitrate in bps
pub bitrate_bps: u32,
}
55 changes: 55 additions & 0 deletions src/modulation/lora.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//! Common LoRa modulation options

use super::Freq;

/// LoRa mode channel configuration
#[derive(Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]

pub struct LoRaChannel {
/// LoRa frequency in kHz
pub freq: Freq,
/// LoRa channel bandwidth
pub bw: Freq,
/// LoRa Spreading Factor
pub sf: SpreadingFactor,
/// LoRa Coding rate
pub cr: CodingRate,
}

/// Spreading factor for LoRa mode
#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[non_exhaustive]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is #[non_exhaustive] necessary here and above CodingRate?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it means that adding additional spreading factors / coding rates in the future (what if we missed one!) would not be a breaking change and forces consumers to handle the unsupported case, i think it's worth it.

pub enum SpreadingFactor {
/// LoRa Spreading Factor 5, 32 chips / symbol
Sf5,
/// LoRa Spreading Factor 6, 64 chips / symbol
Sf6,
/// LoRa Spreading Factor 7, 128 chips / symbol
Sf7,
/// LoRa Spreading Factor 8, 256 chips / symbol
Sf8,
/// LoRa Spreading Factor 9, 512 chips / symbol
Sf9,
/// LoRa Spreading Factor 10 1024 chips / symbol
Sf10,
/// LoRa Spreading Factor 11 2048 chips / symbol
Sf11,
/// LoRa Spreading Factor 12 4096 chips / symbol
Sf12,
}

#[derive(Copy, Clone, PartialEq, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[non_exhaustive]
pub enum CodingRate {
/// LoRa Coding rate 4/5
Cr4_5,
/// LoRa Coding rate 4/6
Cr4_6,
/// LoRa Coding rate 4/7
Cr4_7,
/// LoRa Coding rate 4/8
Cr4_8,
}
160 changes: 160 additions & 0 deletions src/modulation/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
//! Shared types for radio channel / modulation configuration

use core::fmt::Debug;

pub mod gfsk;

pub mod lora;

/// Common modulation configuration errors
///
/// These are provided as a helper for `TryFrom` implementations,
/// and not intended to be prescriptive.
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum ModError {
UnsupportedBitrate,
UnsupportedFreq,
UnsupportedBandwidth,
}

/// Basic frequency type for use in radio definitions.
///
/// This splits frequencies into integer `khz` and `hz` components to achieve Hz granularity with >>GHz range.
/// Users above ~4 GHz should prefer [`Freq::parts`] over integer conversions.
/// ```rust
/// # use radio::modulation::{Freq, Frequency};
/// // Freq objects can be constructed from numeric types
/// let freq = 434.mhz();
/// assert_eq!(freq, 434_000.khz());
/// // And converted back into these numeric types as required
/// assert_eq!(434, freq.mhz());
/// ```
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Freq {
/// kHz component
khz: u32,
/// Hz portion (0-1000)
hz: u32,
}

/// Frequency trait for type conversions.
/// See [`Freq`] implementations for details
pub trait Frequency<T> {
fn hz(&self) -> T;
fn khz(&self) -> T;
fn mhz(&self) -> T;
fn ghz(&self) -> T;
}

impl Freq {
/// Create a new frequency from kHz and Hz components
pub const fn from_parts(khz: u32, hz: u32) -> Option<Self> {
if hz >= 1000 {
return None;
}
Some(Self { khz, hz })
}

/// Fetch frequency kHz and Hz components
pub const fn parts(&self) -> (u32, u32) {
(self.khz, self.hz)
}
}

/// Fetch u32 values from [`Freq`] types
impl Frequency<u32> for Freq {
/// Convert [`Freq`] to u32 Hz, note this will panic for frequencies over ~4GHz
/// ```
/// # use radio::modulation::{Freq, Frequency};
/// let f = Freq::from_parts(433_100, 200).unwrap();
/// assert_eq!(f.hz(), 433_100_200);
/// ```
fn hz(&self) -> u32 {
self.khz * 1000 + self.hz
}

/// Convert [`Freq`] to integer kHz
/// ```
/// # use radio::modulation::{Freq, Frequency};
/// let f = Freq::from_parts(433_100, 200).unwrap();
/// assert_eq!(f.khz(), 433_100);
/// ```
fn khz(&self) -> u32 {
self.khz
}

/// Convert [`Freq`] to integer MHz
/// ```
/// # use radio::modulation::{Freq, Frequency};
/// let f = Freq::from_parts(2_400_100, 200).unwrap();
/// assert_eq!(f.mhz(), 2_400);
/// ```
fn mhz(&self) -> u32 {
self.khz() / 1000
}

/// Convert [`Freq`] to integer GHz
/// ```
/// # use radio::modulation::{Freq, Frequency};
/// let f = Freq::from_parts(20_000_000, 200).unwrap();
/// assert_eq!(f.ghz(), 20);
/// ```
fn ghz(&self) -> u32 {
self.mhz() / 1000
}
}

/// Create [`Freq`] objects from [`u32`] frequencies
impl Frequency<Freq> for u32 {
/// Create [`Freq`] from integer Hz
/// ```
/// # use radio::modulation::{Freq, Frequency};
/// let f = 434_100_200.hz();
/// assert_eq!(f.khz(), 434_100);
/// assert_eq!(f.hz(), 434_100_200);
/// ```
fn hz(&self) -> Freq {
Freq {
khz: self / 1000,
hz: self % 1000,
}
}

/// Create [`Freq`] from integer kHz
/// ```
/// # use radio::modulation::{Freq, Frequency};
/// let f = 434_100.khz();
/// assert_eq!(f.hz(), 434_100_000);
/// ```
fn khz(&self) -> Freq {
Freq { khz: *self, hz: 0 }
}

/// Create [`Freq`] from integer MHz
/// ```
/// # use radio::modulation::{Freq, Frequency};
/// let f = 2_450.mhz();
/// assert_eq!(f.khz(), 2_450_000);
/// ```
fn mhz(&self) -> Freq {
Freq {
khz: self * 1000,
hz: 0,
}
}

/// Create [`Freq`] from integer GHz
/// ```
/// # use radio::modulation::{Freq, Frequency};
/// let f = 2.ghz();
/// assert_eq!(f.mhz(), 2_000);
/// ```
fn ghz(&self) -> Freq {
Freq {
khz: self * 1000 * 1000,
hz: 0,
}
}
}