From 3240a5a16eccc02f50a435da73f60a257cb8903a Mon Sep 17 00:00:00 2001 From: Broderick Carlin Date: Sun, 8 Dec 2024 19:53:09 -0600 Subject: [PATCH] feat!: Add Command trait to represent invocable commands --- README.md | 41 ++++++++++- regiface-macros/src/lib.rs | 18 ++--- regiface/src/byte_array.rs | 20 ++++++ regiface/src/command.rs | 66 +++++++++++++++++ regiface/src/id.rs | 11 +++ regiface/src/lib.rs | 137 ++++++++++++++---------------------- regiface/src/register.rs | 82 +++++++++++++++++++++ regiface/src/register_id.rs | 11 --- regiface/src/spi.rs | 2 +- 9 files changed, 282 insertions(+), 106 deletions(-) create mode 100644 regiface/src/command.rs create mode 100644 regiface/src/id.rs create mode 100644 regiface/src/register.rs delete mode 100644 regiface/src/register_id.rs diff --git a/README.md b/README.md index a2a2216..f96bcfd 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 invoking_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 { + let celsius = f32::from_be_bytes(bytes); + Ok(Self { celsius }) + } +} diff --git a/regiface-macros/src/lib.rs b/regiface-macros/src/lib.rs index f6ebaee..ac1ffd9 100644 --- a/regiface-macros/src/lib.rs +++ b/regiface-macros/src/lib.rs @@ -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, @@ -11,20 +11,20 @@ impl Parse for RegisterAttr { fn parse(input: ParseStream) -> syn::Result { // Parse the entire input as a single LitInt first let lit = input.parse::()?; - + // 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 }) } } diff --git a/regiface/src/byte_array.rs b/regiface/src/byte_array.rs index d3ecf79..1b4763b 100644 --- a/regiface/src/byte_array.rs +++ b/regiface/src/byte_array.rs @@ -1,5 +1,7 @@ use std::convert::Infallible; +use crate::NoParameters; + pub trait ByteArray: private::Sealed { fn new() -> Self; fn as_ref(&self) -> &[u8]; @@ -44,6 +46,15 @@ pub trait FromByteArray: Sized { fn from_bytes(bytes: Self::Array) -> Result; } +impl FromByteArray for NoParameters { + type Error = Infallible; + type Array = [u8; 0]; + + fn from_bytes(_: Self::Array) -> Result { + Ok(Self {}) + } +} + impl FromByteArray for u8 { type Error = Infallible; type Array = [u8; 1]; @@ -117,6 +128,15 @@ pub trait ToByteArray { fn to_bytes(self) -> Result; } +impl ToByteArray for NoParameters { + type Error = Infallible; + type Array = [u8; 0]; + + fn to_bytes(self) -> Result { + Ok([]) + } +} + impl ToByteArray for u8 { type Error = Infallible; type Array = [u8; 1]; diff --git a/regiface/src/command.rs b/regiface/src/command.rs new file mode 100644 index 0000000..1bbb9d3 --- /dev/null +++ b/regiface/src/command.rs @@ -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 invoking_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 { +/// 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 invoking_parameters(self) -> Self::CommandParameters; +} diff --git a/regiface/src/id.rs b/regiface/src/id.rs new file mode 100644 index 0000000..8b2ed48 --- /dev/null +++ b/regiface/src/id.rs @@ -0,0 +1,11 @@ +use std::convert::Infallible; + +use crate::ToByteArray; + +pub trait Id: ToByteArray {} + +impl Id for u8 {} +impl Id for u16 {} +impl Id for u32 {} +impl Id for u64 {} +impl Id for u128 {} diff --git a/regiface/src/lib.rs b/regiface/src/lib.rs index 3818982..dcdd64a 100644 --- a/regiface/src/lib.rs +++ b/regiface/src/lib.rs @@ -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 //! @@ -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 invoking_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 { +//! 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 { -/// 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 { -/// 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 {} diff --git a/regiface/src/register.rs b/regiface/src/register.rs new file mode 100644 index 0000000..8f7de32 --- /dev/null +++ b/regiface/src/register.rs @@ -0,0 +1,82 @@ +use crate::{id, FromByteArray, ToByteArray}; + +/// 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 [`Id`](id::Id) trait. This + /// trait provides default implementations for [`u8`], [`u16`], [`u32`], [`u64`], and [`u128`]. + type IdType: id::Id; + + /// 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 { +/// 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 { +/// 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() + } +} diff --git a/regiface/src/register_id.rs b/regiface/src/register_id.rs deleted file mode 100644 index 726ba4d..0000000 --- a/regiface/src/register_id.rs +++ /dev/null @@ -1,11 +0,0 @@ -use std::convert::Infallible; - -use crate::ToByteArray; - -pub trait RegisterId: ToByteArray {} - -impl RegisterId for u8 {} -impl RegisterId for u16 {} -impl RegisterId for u32 {} -impl RegisterId for u64 {} -impl RegisterId for u128 {} diff --git a/regiface/src/spi.rs b/regiface/src/spi.rs index 7b2492b..778304b 100644 --- a/regiface/src/spi.rs +++ b/regiface/src/spi.rs @@ -81,7 +81,7 @@ pub mod blocking { embedded_hal::spi::Operation::Read(buf.as_mut()), ]) .map_err(ReadRegisterError::BusError)?; - + R::from_bytes(buf).map_err(ReadRegisterError::DeserializationError) }