Skip to content
Merged
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
4 changes: 4 additions & 0 deletions .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ configuration:
- --features=
- --features=debug
- --features=fugit
- --features=backtrace
- --features=debug,fugit
- --features=fugit,backtrace
- --features=backtrace,debug
- --features=debug,fugit,backtrace


# General environment vars
Expand Down
7 changes: 4 additions & 3 deletions rfm95/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[package]
name = "embedded-lora-rfm95"
version = "0.1.3"
version = "0.2.0"
edition = "2021"
authors = ["KizzyCode Software Labs./Keziah Biermann <development@kizzycode.de>"]
keywords = []
categories = []
keywords = ["no-std", "embedded", "hardware-support", "lora", "rfm95"]
categories = ["no-std", "embedded", "hardware-support"]
description = "A `no-std`-compatible, opinionated driver for the RFM95 LoRa modem"
license = "BSD-2-Clause OR MIT"
repository = "https://github.com/KizzyCode/embedded-lora-rust"
Expand All @@ -17,6 +17,7 @@ readme = "README.md"
[features]
default = []
debug = []
backtrace = []
fugit = ["dep:fugit"]


Expand Down
5 changes: 5 additions & 0 deletions rfm95/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ The `fugit`-feature implements simple `From`/`Into`-conversions between the buil
[`fugit`'s](https://crates.io/crates/fugit) [`HertzU32` type](https://docs.rs/fugit/latest/fugit/type.HertzU32.html).
This is a comfort-feature only, and does not enable additional functionality.

### `backtrace` (disabled by default)
The `backtrace`-feature can be used to get more verbose errors. If this feature is enabled, errors will contain a human
readable description as well as file and line information about where the error occurred. This is useful for debugging
or better logging, but can be disabled if library size matters.

### `debug` (disabled by default)
The `debug` feature enables some debug functionality, namely an SPI debug callback which can be used to log all SPI
transactions with the RFM95 modem, and provides some helper functions to dump the register state and FIFO contents. The
Expand Down
136 changes: 136 additions & 0 deletions rfm95/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
//! The crate's error types

/// Creates an error
#[macro_export]
macro_rules! err {
($kind:tt, $desc:expr) => {{
$kind {
#[cfg(feature = "backtrace")]
file: file!(),
#[cfg(feature = "backtrace")]
line: line!(),
#[cfg(feature = "backtrace")]
description: $desc,
}
}};
}

/// An I/O error
#[derive(Debug, Clone, Copy)]
pub struct IoError {
/// The file where the error was created
#[cfg(feature = "backtrace")]
pub file: &'static str,
/// The line at which the error was created
#[cfg(feature = "backtrace")]
pub line: u32,
/// A human readable error description
#[cfg(feature = "backtrace")]
pub description: &'static str,
}

/// A timeout error
#[derive(Debug, Clone, Copy)]
pub struct TimeoutError {
/// The file where the error was created
#[cfg(feature = "backtrace")]
pub file: &'static str,
/// The line at which the error was created
#[cfg(feature = "backtrace")]
pub line: u32,
/// A human readable error description
#[cfg(feature = "backtrace")]
pub description: &'static str,
}

/// A CRC-validation or format error
#[derive(Debug, Clone, Copy)]
pub struct InvalidMessageError {
/// The file where the error was created
#[cfg(feature = "backtrace")]
pub file: &'static str,
/// The line at which the error was created
#[cfg(feature = "backtrace")]
pub line: u32,
/// A human readable error description
#[cfg(feature = "backtrace")]
pub description: &'static str,
}

/// An invalid-argument error
#[derive(Debug, Clone, Copy)]
pub struct InvalidArgumentError {
/// The file where the error was created
#[cfg(feature = "backtrace")]
pub file: &'static str,
/// The line at which the error was created
#[cfg(feature = "backtrace")]
pub line: u32,
/// A human readable error description
#[cfg(feature = "backtrace")]
pub description: &'static str,
}

/// An TX-start error
#[derive(Debug, Clone, Copy)]
pub enum TxStartError {
/// An I/O error
IoError(IoError),
/// An invalid-argument error
InvalidArgumentError(InvalidArgumentError),
}
impl From<IoError> for TxStartError {
fn from(error: IoError) -> Self {
Self::IoError(error)
}
}
impl From<InvalidArgumentError> for TxStartError {
fn from(error: InvalidArgumentError) -> Self {
Self::InvalidArgumentError(error)
}
}

/// An RX-start error
#[derive(Debug, Clone, Copy)]
pub enum RxStartError {
/// An I/O error
IoError(IoError),
/// An invalid-argument error
InvalidArgumentError(InvalidArgumentError),
}
impl From<IoError> for RxStartError {
fn from(error: IoError) -> Self {
Self::IoError(error)
}
}
impl From<InvalidArgumentError> for RxStartError {
fn from(error: InvalidArgumentError) -> Self {
Self::InvalidArgumentError(error)
}
}

/// An RX-completion specific error
#[derive(Debug, Clone, Copy)]
pub enum RxCompleteError {
/// An I/O error
IoError(IoError),
/// A timeout error
TimeoutError(TimeoutError),
/// A CRC-validation or format error
InvalidMessageError(InvalidMessageError),
}
impl From<IoError> for RxCompleteError {
fn from(error: IoError) -> Self {
Self::IoError(error)
}
}
impl From<TimeoutError> for RxCompleteError {
fn from(error: TimeoutError) -> Self {
Self::TimeoutError(error)
}
}
impl From<InvalidMessageError> for RxCompleteError {
fn from(error: InvalidMessageError) -> Self {
Self::InvalidMessageError(error)
}
}
1 change: 1 addition & 0 deletions rfm95/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
#![warn(clippy::allow_attributes_without_reason)]
#![warn(clippy::cognitive_complexity)]

pub mod error;
pub mod lora;
pub mod rfm95;
57 changes: 27 additions & 30 deletions rfm95/src/lora/types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! Small wrappers for type safety

use crate::err;
use crate::error::IoError;

/// A LoRa spreading factor
///
/// # Implementation Note
Expand All @@ -25,18 +28,17 @@ pub enum SpreadingFactor {
/// Spreading factor 12 aka 4096 chirps per symbol
S12 = 12,
}
impl TryFrom<u8> for SpreadingFactor {
type Error = &'static str;

fn try_from(value: u8) -> Result<Self, Self::Error> {
impl SpreadingFactor {
/// Parses `self` from a register value
pub(crate) fn parse(value: u8) -> Result<Self, IoError> {
match value {
sf if sf == Self::S7 as u8 => Ok(Self::S7),
sf if sf == Self::S8 as u8 => Ok(Self::S8),
sf if sf == Self::S9 as u8 => Ok(Self::S9),
sf if sf == Self::S10 as u8 => Ok(Self::S10),
sf if sf == Self::S11 as u8 => Ok(Self::S11),
sf if sf == Self::S12 as u8 => Ok(Self::S12),
_ => Err("Invalid or unsupported spreading factor"),
_ => Err(err!(IoError, "Invalid or unsupported spreading factor")),
}
}
}
Expand Down Expand Up @@ -70,10 +72,9 @@ pub enum Bandwidth {
/// 7.8 kHz bandwidth
B7_8 = 0b0000,
}
impl TryFrom<u8> for Bandwidth {
type Error = &'static str;

fn try_from(value: u8) -> Result<Self, Self::Error> {
impl Bandwidth {
/// Parses `self` from a register value
pub(crate) fn parse(value: u8) -> Result<Self, IoError> {
match value {
bw if bw == Self::B500 as u8 => Ok(Self::B500),
bw if bw == Self::B250 as u8 => Ok(Self::B250),
Expand All @@ -85,7 +86,7 @@ impl TryFrom<u8> for Bandwidth {
bw if bw == Self::B15_6 as u8 => Ok(Self::B15_6),
bw if bw == Self::B10_4 as u8 => Ok(Self::B10_4),
bw if bw == Self::B7_8 as u8 => Ok(Self::B7_8),
_ => Err("Invalid or unsupported bandwidth"),
_ => Err(err!(IoError, "Invalid or unsupported bandwidth")),
}
}
}
Expand All @@ -107,16 +108,15 @@ pub enum CodingRate {
/// Coding rate 4/8 aka 2x overhead
C4_8 = 0b100,
}
impl TryFrom<u8> for CodingRate {
type Error = &'static str;

fn try_from(value: u8) -> Result<Self, Self::Error> {
impl CodingRate {
/// Parses `self` from a register value
pub(crate) fn parse(value: u8) -> Result<Self, IoError> {
match value {
cr if cr == Self::C4_5 as u8 => Ok(Self::C4_5),
cr if cr == Self::C4_6 as u8 => Ok(Self::C4_6),
cr if cr == Self::C4_7 as u8 => Ok(Self::C4_7),
cr if cr == Self::C4_8 as u8 => Ok(Self::C4_8),
_ => Err("Invalid coding rate"),
_ => Err(err!(IoError, "Invalid coding rate")),
}
}
}
Expand All @@ -134,14 +134,13 @@ pub enum Polarity {
/// Inverted polarity, usually used for downlinks
Inverted = 1,
}
impl TryFrom<u8> for Polarity {
type Error = &'static str;

fn try_from(value: u8) -> Result<Self, Self::Error> {
impl Polarity {
/// Parses `self` from a register value
pub(crate) fn parse(value: u8) -> Result<Self, IoError> {
match value {
polarity if polarity == Self::Normal as u8 => Ok(Self::Normal),
polarity if polarity == Self::Inverted as u8 => Ok(Self::Inverted),
_ => Err("Invalid IQ polarity value"),
_ => Err(err!(IoError, "Invalid IQ polarity value")),
}
}
}
Expand All @@ -159,14 +158,13 @@ pub enum HeaderMode {
/// Implicit header mode to omit the header if decoding parameters are known
Implicit = 1,
}
impl TryFrom<u8> for HeaderMode {
type Error = &'static str;

fn try_from(value: u8) -> Result<Self, Self::Error> {
impl HeaderMode {
/// Parses `self` from a register value
pub(crate) fn parse(value: u8) -> Result<Self, IoError> {
match value {
mode if mode == Self::Explicit as u8 => Ok(Self::Explicit),
mode if mode == Self::Implicit as u8 => Ok(Self::Implicit),
_ => Err("Invalid header mode"),
_ => Err(err!(IoError, "Invalid header mode")),
}
}
}
Expand All @@ -184,14 +182,13 @@ pub enum CrcMode {
/// CRC enabled
Enabled = 1,
}
impl TryFrom<u8> for CrcMode {
type Error = &'static str;

fn try_from(value: u8) -> Result<Self, Self::Error> {
impl CrcMode {
/// Parses `self` from a register value
pub(crate) fn parse(value: u8) -> Result<Self, IoError> {
match value {
mode if mode == Self::Disabled as u8 => Ok(Self::Disabled),
mode if mode == Self::Enabled as u8 => Ok(Self::Enabled),
_ => Err("Invalid CRC mode"),
_ => Err(err!(IoError, "Invalid CRC mode")),
}
}
}
Expand Down
14 changes: 8 additions & 6 deletions rfm95/src/rfm95/connection.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! RFM95 SPI connection

use crate::err;
use crate::error::IoError;
use crate::rfm95::registers::Register;
use core::fmt::{Debug, Formatter};
use embedded_hal::digital::OutputPin;
Expand Down Expand Up @@ -32,7 +34,7 @@ where
}

/// Reads a RFM95 register via SPI
pub fn read<T>(&mut self, register: T) -> Result<u8, &'static str>
pub fn read<T>(&mut self, register: T) -> Result<u8, IoError>
where
T: Register,
{
Expand All @@ -41,7 +43,7 @@ where
Ok((register_value & register.mask()) >> register.offset())
}
/// Updates a RFM95 register via SPI
pub fn write<T>(&mut self, register: T, value: u8) -> Result<(), &'static str>
pub fn write<T>(&mut self, register: T, value: u8) -> Result<(), IoError>
where
T: Register,
{
Expand All @@ -61,15 +63,15 @@ where
}

/// Performs RFM95-specific SPI register access
fn register(&mut self, operation: u8, address: u8, payload: u8) -> Result<u8, &'static str> {
fn register(&mut self, operation: u8, address: u8, payload: u8) -> Result<u8, IoError> {
// Build command
let address = address & 0b0111_1111;
let mut command = [operation | address, payload];

// Do transaction
self.select.set_low().map_err(|_| "Failed to pull chip-select line to low")?;
self.bus.transfer_in_place(&mut command).map_err(|_| "Failed to do SPI transaction")?;
self.select.set_high().map_err(|_| "Failed to pull chip-select line to high")?;
self.select.set_low().map_err(|_| err!(IoError, "Failed to pull chip-select line to low"))?;
self.bus.transfer_in_place(&mut command).map_err(|_| err!(IoError, "Failed to do SPI transaction"))?;
self.select.set_high().map_err(|_| err!(IoError, "Failed to pull chip-select line to high"))?;

// SPI debug callback
#[cfg(feature = "debug")]
Expand Down
Loading