Skip to content

Commit

Permalink
feat!: Add Command trait to represent invocable commands
Browse files Browse the repository at this point in the history
  • Loading branch information
BroderickCarlin committed Dec 9, 2024
1 parent 1bb9099 commit 4c0ffc7
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 106 deletions.
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
This crate provides a handful of utility types for writing abstractions for interfacing with register based devices. Most commonly, this would be utilized when writing drivers for external peripherals within an embedded environment. As such, some utility functions are provided for reading and writing registers on devices across I2C or SPI buses.

This crate provides a single trait to be implemented by all types that represent a value that is stored within an addressable register, aptly named `Register`. This trait provides nothing more than a method for retrieving the ID associated with the given register.
This crate provides two core traits:
- `Register` for types that represent a value stored within an addressable register
- `Command` for types that represent an invokable command with parameters and response

### Register Implementation

Expand Down Expand Up @@ -90,3 +92,40 @@ A type that implements the `ReadableRegister` trait can then be used with provid
A register in which values can be written to is represented as any type that implements the `WritableRegister` trait. This trait is very little more than just a marker trait, but it represents a type that is both a `Register` and that can be serialized into a byte array through the `ToByteArray` trait. The bulk of the work in writing a type that can be written to a register will be in implementing the `ToByteArray` trait.

A type that implements the `WritableRegister` trait can then be used with provided utility methods such as those provided by the `i2c` or `spi` modules.

### Commands

A command represents an invokable action with optional parameters and response. Commands are implemented using the `Command` trait, which specifies both the command parameters and expected response type. For commands or responses without parameters, the `NoParameters` type can be used.

```rust
use regiface::{Command, ToByteArray, FromByteArray, NoParameters};

struct GetTemperature;

impl Command for GetTemperature {
type IdType = u8;
type CommandParameters = NoParameters;
type ResponseParameters = Temperature;

fn id() -> Self::IdType {
0x42
}

fn parameters(&self) -> Self::CommandParameters {
NoParameters::default()
}
}

struct Temperature {
celsius: f32
}

impl FromByteArray for Temperature {
type Error = core::convert::Infallible;
type Array = [u8; 4];

fn from_bytes(bytes: Self::Array) -> Result<Self, Self::Error> {
let celsius = f32::from_be_bytes(bytes);
Ok(Self { celsius })
}
}
18 changes: 9 additions & 9 deletions regiface-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, LitInt, parse::Parse, parse::ParseStream, Ident};
use syn::{parse::Parse, parse::ParseStream, parse_macro_input, DeriveInput, Ident, LitInt};

struct RegisterAttr {
value: LitInt,
Expand All @@ -11,20 +11,20 @@ impl Parse for RegisterAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
// Parse the entire input as a single LitInt first
let lit = input.parse::<LitInt>()?;

// Extract the type suffix from the literal
let suffix = lit.suffix();
if suffix.is_empty() {
return Err(syn::Error::new(lit.span(), "Expected type suffix (e.g., u8, u16)"));
return Err(syn::Error::new(
lit.span(),
"Expected type suffix (e.g., u8, u16)",
));
}

// Create an Ident from the suffix
let ty = Ident::new(suffix, lit.span());

Ok(RegisterAttr {
value: lit,
ty,
})

Ok(RegisterAttr { value: lit, ty })
}
}

Expand Down
20 changes: 20 additions & 0 deletions regiface/src/byte_array.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::convert::Infallible;

use crate::NoParameters;

pub trait ByteArray: private::Sealed {
fn new() -> Self;
fn as_ref(&self) -> &[u8];
Expand Down Expand Up @@ -44,6 +46,15 @@ pub trait FromByteArray: Sized {
fn from_bytes(bytes: Self::Array) -> Result<Self, Self::Error>;
}

impl FromByteArray for NoParameters {
type Error = Infallible;
type Array = [u8; 0];

fn from_bytes(_: Self::Array) -> Result<Self, Self::Error> {
Ok(Self {})
}
}

impl FromByteArray for u8 {
type Error = Infallible;
type Array = [u8; 1];
Expand Down Expand Up @@ -117,6 +128,15 @@ pub trait ToByteArray {
fn to_bytes(self) -> Result<Self::Array, Self::Error>;
}

impl ToByteArray for NoParameters {
type Error = Infallible;
type Array = [u8; 0];

fn to_bytes(self) -> Result<Self::Array, Self::Error> {
Ok([])
}
}

impl ToByteArray for u8 {
type Error = Infallible;
type Array = [u8; 1];
Expand Down
66 changes: 66 additions & 0 deletions regiface/src/command.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use crate::{id, FromByteArray, ToByteArray};

/// The core trait to be implemented for all types that represent an invokable command
///
/// All [`Command`]s specify the parameters for both the command and the response body. In the
/// case where either the command or response has no parameters, the [`NoParameters`](crate::NoParameters)
/// can be specified.
///
/// # Example
///
/// ```rust
/// use regiface::{Command, ToByteArray, FromByteArray, NoParameters};
///
/// struct GetTemperature;
///
/// impl Command for GetTemperature {
/// type IdType = u8;
/// type CommandParameters = NoParameters;
/// type ResponseParameters = Temperature;
///
/// fn id() -> Self::IdType {
/// 0x42
/// }
///
/// fn parameters(&self) -> Self::CommandParameters {
/// NoParameters::default()
/// }
/// }
///
/// struct Temperature {
/// celsius: f32
/// }
///
/// impl FromByteArray for Temperature {
/// type Error = core::convert::Infallible;
/// type Array = [u8; 4];
///
/// fn from_bytes(bytes: Self::Array) -> Result<Self, Self::Error> {
/// let celsius = f32::from_be_bytes(bytes);
/// Ok(Self { celsius })
/// }
/// }
/// ```
pub trait Command {
/// The type used to represent the command's ID.
///
/// Command ID types are any type that implement the [`Id`](id::Id) trait. This
/// trait provides default implementations for [`u8`], [`u16`], [`u32`], [`u64`], and [`u128`].
type IdType: id::Id;

/// The parameters included as part of the command invocation
///
/// If the command has no parameters, the [`NoParameters`](crate::NoParameters) type can be used
type CommandParameters: ToByteArray;

/// The parameters expected as the response to the command
///
/// If the response has no parameters, the [`NoParameters`](crate::NoParameters) type can be used
type ResponseParameters: FromByteArray;

/// A method that returns the ID of the [`Command`]
fn id() -> Self::IdType;

/// A method to retrieve the parameters from an instance of the [`Command`]
fn parameters(&self) -> Self::CommandParameters;
}
11 changes: 11 additions & 0 deletions regiface/src/id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use std::convert::Infallible;

use crate::ToByteArray;

pub trait Id: ToByteArray<Error = Infallible> {}

impl Id for u8 {}
impl Id for u16 {}
impl Id for u32 {}
impl Id for u64 {}
impl Id for u128 {}
137 changes: 53 additions & 84 deletions regiface/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
//! external peripherals within an embedded environment. As such, some utility functions
//! are provided for reading and writing registers on devices across I2C or SPI buses.
//!
//! This crate provides a single trait to be implemented by all types that represent a value that
//! is stored within an addressable register, aptly named [`Register`]. This trait provides nothing
//! more than a method for retrieving the ID associated with the given register.
//! This crate provides two core traits:
//! - [`Register`] for types that represent a value stored within an addressable register
//! - [`Command`] for types that represent an invokable command with parameters and response
//!
//! ### Readable Registers
//!
Expand Down Expand Up @@ -74,93 +74,62 @@
//! }
//! }
//! ```
//!
//! ### Commands
//!
//! A command represents an invokable action with optional parameters and response. Commands are
//! implemented using the [`Command`] trait, which specifies both the command parameters and expected
//! response type. For commands or responses without parameters, the [`NoParameters`] type can be used.
//!
//! #### Command Implementation Example
//!
//! ```rust
//! use regiface::{Command, ToByteArray, FromByteArray, NoParameters};
//!
//! struct GetTemperature;
//!
//! impl Command for GetTemperature {
//! type IdType = u8;
//! type CommandParameters = NoParameters;
//! type ResponseParameters = Temperature;
//!
//! fn id() -> Self::IdType {
//! 0x42
//! }
//!
//! fn parameters(&self) -> Self::CommandParameters {
//! NoParameters::default()
//! }
//! }
//!
//! struct Temperature {
//! celsius: f32
//! }
//!
//! impl FromByteArray for Temperature {
//! type Error = core::convert::Infallible;
//! type Array = [u8; 4];
//!
//! fn from_bytes(bytes: Self::Array) -> Result<Self, Self::Error> {
//! let celsius = f32::from_be_bytes(bytes);
//! Ok(Self { celsius })
//! }
//! }
//! ```
pub use byte_array::{FromByteArray, ToByteArray};
pub use command::*;
pub use regiface_macros::{register, ReadableRegister, WritableRegister};
pub use register::*;

pub mod byte_array;
mod command;
pub mod errors;
pub mod i2c;
pub mod register_id;
pub mod id;
mod register;
pub mod spi;

/// The core trait to be implemented for all types that represent readable or writable register values
///
/// This trait provides minimal value on its own, but is a building block to be combined with either [`ReadableRegister`]
/// or [`WritableRegister`].
pub trait Register {
/// The type used to represent the register's ID.
///
/// Register ID types are any type that implement the [`RegisterId`](register_id::RegisterId) trait. This
/// trait provides default implementations for [`u8`], [`u16`], [`u32`], [`u64`], and [`u128`].
type IdType: register_id::RegisterId;

/// A method that returns the ID of the register for the associated type
fn id() -> Self::IdType;
}

/// A marker trait that represents a type that can be retrieved by reading a register
///
/// This trait can be manually implemented, or may be derived as such
///
/// ```
/// use regiface::{register, ReadableRegister, FromByteArray};
///
/// #[register(42u8)]
/// #[derive(ReadableRegister, Debug)]
/// pub struct MyRegister {
/// foo: u8
/// }
///
/// impl FromByteArray for MyRegister {
/// type Error = core::convert::Infallible;
/// type Array = [u8; 1];
///
/// fn from_bytes(bytes: Self::Array) -> Result<Self, Self::Error> {
/// Ok(Self { foo: bytes[0] })
/// }
/// }
/// ```
pub trait ReadableRegister: Register + FromByteArray {
/// Some implementations may specify a different register ID to be used when reading the register.
///
/// Override the function if you need to specify an ID value different than that specified by the [`Register`]
/// implementation for the purpose of writing from the register
#[inline]
fn readable_id() -> Self::IdType {
Self::id()
}
}

/// A marker trait that represents a type that can be written into a register
///
/// This trait can be manually implemented, or may be derived as such
///
/// ```
/// use regiface::{register, WritableRegister, ToByteArray};
///
/// #[register(42u8)]
/// #[derive(WritableRegister, Debug)]
/// pub struct MyRegister {
/// foo: u8
/// }
///
/// impl ToByteArray for MyRegister {
/// type Error = core::convert::Infallible;
/// type Array = [u8; 1];
///
/// fn to_bytes(self) -> Result<Self::Array, Self::Error> {
/// Ok([self.foo])
/// }
/// }
/// ```
pub trait WritableRegister: Register + ToByteArray {
/// Some implementations may specify a different register ID to be used when writing the register.
///
/// Override the function if you need to specify an ID value different than that specified by the [`Register`]
/// implementation for the purpose of writing to the register
#[inline]
fn writeable_id() -> Self::IdType {
Self::id()
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Default)]
pub struct NoParameters {}
Loading

0 comments on commit 4c0ffc7

Please sign in to comment.