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

prepare v0.3.0: change API to Iterator, implement core::error::Error & add some Copy, Clone, Hash where applicable #38

Merged
merged 7 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
26 changes: 20 additions & 6 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ jobs:
strategy:
fail-fast: false
matrix:
rust: [1.62.0, stable]
features: ['use_alloc', 'use_alloc,defmt', 'use_heapless', 'use_heapless,defmt']
rust: [1.81.0, stable]
features: ['alloc', 'alloc,defmt', 'heapless', 'heapless,defmt']
exclude:
- rust: 1.62.0
features: 'use_alloc,defmt'
- rust: 1.62.0
features: 'use_heapless,defmt'
- rust: 1.81.0
features: 'alloc,defmt'
- rust: 1.81.0
features: 'heapless,defmt'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down Expand Up @@ -87,3 +87,17 @@ jobs:
with:
sarif_file: examples/stm32f4-event-printer/rust-clippy-results.sarif
wait-for-processing: true

# simplify GH settings: have one single build to be required
build-results:
name: Final Results
if: ${{ always() }}
runs-on: ubuntu-latest
needs: [lib, stm32f4-event-printer]
steps:
- name: check for failed builds of the library
if: ${{ needs.lib.result != 'success' }}
run: exit 1
- name: check for failed builds of the example
if: ${{ needs.stm32f4-event-printer.result != 'success' }}
run: exit 1
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

<!-- next-header -->
## [Unreleased] - ReleaseDate
### Added
* `Copy`, `Clone` and `Hash` on error & event types (where possible)
### Changed
* The MSRV has been updated to 1.81.0 due to `core::error::Error` being implemented
* **BREAKING**: the features `use_alloc` and `use_heapless` have been renamed to `alloc` and `heapless` respectively.

## [0.2.0] - 2023-11-14
### Added
Expand Down
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "adafruit-bluefruit-protocol"
version = "0.2.0"
edition = "2021"
rust-version = "1.62"
rust-version = "1.81"

description = "A `no_std` parser for the Adafruit Bluefruit LE Connect controller protocol."
repository = "https://github.com/rust-embedded-community/adafruit-bluefruit-protocol-rs"
Expand All @@ -20,8 +20,8 @@ serde = { version = "1.0", features = ["derive"], optional = true }
[features]
default = ["accelerometer_event", "button_event", "color_event", "gyro_event", "location_event", "magnetometer_event", "quaternion_event"]

use_heapless = ["dep:heapless"]
use_alloc = []
heapless = ["dep:heapless"]
alloc = []

defmt = ["dep:defmt", "heapless?/defmt-03"]

Expand Down
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ Note that this work is not affiliated with Adafruit.

## Mandatory Features
This crate is `no_std` and you can choose whether you want to use
[`heapless::Vec`](https://docs.rs/heapless/0.8.0/heapless/struct.Vec.html) by selecting the feature `use_heapless` or
[`alloc::vec::Vec`](https://doc.rust-lang.org/alloc/vec/struct.Vec.html) by selecting the feature `use_alloc`.
[`heapless::Vec`](https://docs.rs/heapless/0.8.0/heapless/struct.Vec.html) by selecting the feature `heapless` or
[`alloc::vec::Vec`](https://doc.rust-lang.org/alloc/vec/struct.Vec.html) by selecting the feature `alloc`.
If you select neither or both you'll get a compile error.

## Optional Features
Expand All @@ -23,12 +23,16 @@ If you select neither or both you'll get a compile error.
but you can opt to only select the event(s) you are interested in which will result in a small binary size.
If other events are received, a `ProtocolParseError::DisabledControllerDataPackageType` will be returned.

## Usage
The entry point to use this crate is the `parse` function. Note that this is a [sans I/O](https://sans-io.readthedocs.io/)
crate, i.e. you have to talk to the Adafruit device, the `parse` function just expects a byte sequence.

## Examples
A simple example for the STM32F4 microcontrollers is [available](examples/stm32f4-event-printer/README.md).

## Changelog
For the changelog please see the dedicated [CHANGELOG.md](CHANGELOG.md).

## Minimum Supported Rust Version (MSRV)
This crate is guaranteed to compile on stable Rust 1.62 and up. It *might*
This crate is guaranteed to compile on stable Rust 1.81 and up. It *might*
compile with older versions but that may change in any new patch release.
2 changes: 1 addition & 1 deletion examples/stm32f4-event-printer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ defmt = "0.3.6"
defmt-rtt = "0.4"

# use `adafruit-bluefruit-protocol = "0.1"` in reality; path used here to ensure that the example always compiles against the latest master
adafruit-bluefruit-protocol = { path = "../..", features = ["defmt", "use_heapless"] }
adafruit-bluefruit-protocol = { path = "../..", features = ["defmt", "heapless"] }

[profile.release]
codegen-units = 1
Expand Down
2 changes: 1 addition & 1 deletion src/accelerometer_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use super::{try_f32_from_le_bytes, ProtocolParseError};

/// Represents an accelerometer event from the protocol.
#[derive(PartialEq, Debug)]
#[derive(PartialEq, Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(missing_docs)] // the names are already obvious enough
Expand Down
22 changes: 18 additions & 4 deletions src/button_event.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
//! Implements the [`ButtonEvent`] and its parsing from the protocol.

use super::ProtocolParseError;
use core::error::Error;
use core::fmt::{Display, Formatter};

/// Errors which can be raised while parsing a button event.
#[derive(PartialEq, Eq, Debug)]
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ButtonParseError {
/// The message contained an unknown button. For the known buttons see [`Button`].
Expand All @@ -12,8 +14,20 @@ pub enum ButtonParseError {
UnknownButtonState(u8),
}

impl Display for ButtonParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
use ButtonParseError::*;
match self {
UnknownButton(button) => write!(f, "Unknown button: {:#x}", button),
UnknownButtonState(state) => write!(f, "Unknown button state: {:#x}", state),
}
}
}

impl Error for ButtonParseError {}

/// Lists all possible buttons which can be sent in the event.
#[derive(PartialEq, Eq, Debug)]
#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(missing_docs)] // the names are already obvious enough
pub enum Button {
Expand Down Expand Up @@ -45,7 +59,7 @@ impl Button {
}

/// The state of the button.
#[derive(PartialEq, Eq, Debug)]
#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(missing_docs)] // the names are already obvious enough
Expand All @@ -66,7 +80,7 @@ impl ButtonState {
}

/// Represents a button event from the protocol.
#[derive(PartialEq, Eq, Debug)]
#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(missing_docs)] // the names are already obvious enough
pub struct ButtonEvent {
Expand Down
2 changes: 1 addition & 1 deletion src/color_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use super::ProtocolParseError;
use rgb::RGB8;

/// Represents a color event from the protocol.
#[derive(PartialEq, Eq, Debug)]
#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(missing_docs)] // the names are already obvious enough
Expand Down
2 changes: 1 addition & 1 deletion src/gyro_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use super::{try_f32_from_le_bytes, ProtocolParseError};

/// Represents a gyro event from the protocol.
#[derive(PartialEq, Debug)]
#[derive(PartialEq, Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(missing_docs)] // the names are already obvious enough
Expand Down
107 changes: 83 additions & 24 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! This implements the [Adafruit Bluefruit LE Connect controller protocol](https://learn.adafruit.com/bluefruit-le-connect/controller)
//! which is e.g. used by the [Adafruit Bluefruit LE UART Friend](https://learn.adafruit.com/introducing-the-adafruit-bluefruit-le-uart-friend).
//!
//! The entry point to use this crate is the [`parse`] function. Note that this is a [sans I/O](https://sans-io.readthedocs.io/)
//! crate, i.e. you have to talk to the Adafruit device, the `parse` function just expects a byte sequence.
//!
//! ## Optional features
//! * `defmt`: you can enable the `defmt` feature to get a `defmt::Format` implementation for all structs & enums and a `defmt::debug!` call for each command being parsed.
//! * `rgb`: if enabled, `From<ColorEvent> for RGB8` is implemented to support the [RGB crate](https://crates.io/crates/rgb).
Expand Down Expand Up @@ -29,11 +32,11 @@
)))]
compile_error!("at least one event type must be selected in the features!");

#[cfg(not(any(feature = "use_alloc", feature = "use_heapless")))]
compile_error!("you must choose either 'use_alloc' or 'use_heapless' as a feature!");
#[cfg(not(any(feature = "alloc", feature = "heapless")))]
compile_error!("you must choose either 'alloc' or 'heapless' as a feature!");

#[cfg(all(feature = "use_alloc", feature = "use_heapless"))]
compile_error!("you must choose either 'use_alloc' or 'use_heapless' as a feature but not both!");
#[cfg(all(feature = "alloc", feature = "heapless"))]
compile_error!("you must choose either 'alloc' or 'heapless' as a feature but not both!");

#[cfg(feature = "accelerometer_event")]
pub mod accelerometer_event;
Expand All @@ -59,13 +62,15 @@ use color_event::ColorEvent;
use core::cmp::min;
#[cfg(feature = "gyro_event")]
use gyro_event::GyroEvent;
#[cfg(feature = "use_heapless")]
#[cfg(feature = "heapless")]
use heapless::Vec;

#[cfg(feature = "use_alloc")]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "use_alloc")]
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::error::Error;
use core::fmt::{Display, Formatter};
#[cfg(feature = "location_event")]
use location_event::LocationEvent;
#[cfg(feature = "magnetometer_event")]
Expand All @@ -74,7 +79,7 @@ use magnetometer_event::MagnetometerEvent;
use quaternion_event::QuaternionEvent;

/// Lists all (supported) events which can be sent by the controller. These come with the parsed event data and are the result of a [`parse`] call.
#[derive(PartialEq, Debug)]
#[derive(PartialEq, Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(missing_docs)] // the names are already obvious enough
pub enum ControllerEvent {
Expand All @@ -95,14 +100,13 @@ pub enum ControllerEvent {
}

/// Represents the different kinds of errors which can happen when the protocol is being parsed.
#[derive(PartialEq, Eq, Debug)]
#[derive(PartialEq, Eq, Debug, Copy, Clone, Hash)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum ProtocolParseError {
/// The message contained an event which is not known to the current implementation.
/// This can mean that:
/// * the message was malformed or
/// * that a newer protocol version has been used or
/// * that the event type has not been enabled as a feature.
/// * that a newer protocol version has been used.
UnknownEvent(Option<u8>),
/// The message contained an event which is known to the library but has not been selected as a feature and can thus not be parsed. Select the feature when compiling the library to handle this message.
DisabledControllerDataPackageType(ControllerDataPackageType),
Expand All @@ -117,8 +121,46 @@ pub enum ProtocolParseError {
InvalidFloatSize(usize),
}

impl Display for ProtocolParseError {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
use ProtocolParseError::*;
match self {
UnknownEvent(event) => write!(f, "Unknown event type: {:?}", event),
DisabledControllerDataPackageType(event) => {
write!(f, "Disabled event type: {:?}", event)
}
ButtonParseError(_) => write!(f, "Error while parsing button event"),
InvalidLength(expected, actual) => write!(
f,
"Invalid message length: expected {} but received {}",
expected, actual
),
InvalidCrc(expected, actual) => write!(
f,
"Invalid CRC: expected {:#x} but calculated {:#x}",
expected, actual
),
InvalidFloatSize(length) => write!(
f,
"Failed to parse float from a message with size {}",
length
),
}
}
}

impl Error for ProtocolParseError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
use ProtocolParseError::*;
match self {
ButtonParseError(e) => Some(e),
_ => None,
}
}
}

/// Lists all data packages which can be sent by the controller. Internal state used during parsing. Use [`ControllerEvent`] to return the actual event.
#[derive(PartialEq, Eq, Debug)]
#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[allow(missing_docs)] // the names are already obvious enough
pub enum ControllerDataPackageType {
Expand Down Expand Up @@ -168,17 +210,17 @@ impl TryFrom<u8> for ControllerDataPackageType {
}
}

#[cfg(feature = "use_heapless")]
#[cfg(feature = "heapless")]
type ParseResult<const MAX_RESULTS: usize> =
Vec<Result<ControllerEvent, ProtocolParseError>, MAX_RESULTS>;

#[cfg(feature = "use_alloc")]
#[cfg(feature = "alloc")]
type ParseResult<const MAX_RESULTS: usize> = Vec<Result<ControllerEvent, ProtocolParseError>>;
#[cfg(feature = "use_alloc")]
#[cfg(feature = "alloc")]
const MAX_RESULTS: usize = 0;

/// Parse the input string for commands. Unexpected content will be ignored if it's not formatted like a command!
pub fn parse<#[cfg(feature = "use_heapless")] const MAX_RESULTS: usize>(
pub fn parse<#[cfg(feature = "heapless")] const MAX_RESULTS: usize>(
input: &[u8],
) -> ParseResult<MAX_RESULTS> {
/// Simple state machine for the parser, represents whether the parser is seeking a start or has found it.
Expand All @@ -200,11 +242,11 @@ pub fn parse<#[cfg(feature = "use_heapless")] const MAX_RESULTS: usize>(
}
ParserState::ParseCommand => {
let data_package = extract_and_parse_command(&input[(pos - 1)..]);
#[cfg(feature = "use_alloc")]
#[cfg(feature = "alloc")]
result.push(data_package);
#[cfg(feature = "use_heapless")]
#[cfg(feature = "heapless")]
result.push(data_package).ok();
#[cfg(feature = "use_heapless")]
#[cfg(feature = "heapless")]
if result.len() == MAX_RESULTS {
return result;
}
Expand Down Expand Up @@ -340,7 +382,7 @@ fn try_f32_from_le_bytes(input: &[u8]) -> Result<f32, ProtocolParseError> {

#[cfg(test)]
mod tests {
use crate::button_event::{Button, ButtonState};
use crate::button_event::{Button, ButtonParseError, ButtonState};
use crate::{check_crc, parse, try_f32_from_le_bytes, ControllerEvent, ProtocolParseError};

fn assert_is_button_event(
Expand All @@ -359,16 +401,33 @@ mod tests {

#[test]
fn test_parse() {
let input = b"\x00!B11:!B10;\x00\x00!\x00\x00\x00\x00";
#[cfg(feature = "use_heapless")]
let input = b"\x00!B11:!B10;\x00\x00!\x00\x00\x00\x00!B138";
#[cfg(feature = "heapless")]
let result = parse::<4>(input);
rursprung marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(feature = "use_alloc")]
#[cfg(feature = "alloc")]
let result = parse(input);

assert_eq!(result.len(), 3);
assert_eq!(result.len(), 4);
assert_is_button_event(&result[0], Button::Button1, ButtonState::Pressed);
assert_is_button_event(&result[1], Button::Button1, ButtonState::Released);
assert_eq!(result[2], Err(ProtocolParseError::UnknownEvent(Some(0))));
if let Err(e) = &result[3] {
assert_eq!(
e,
&ProtocolParseError::ButtonParseError(ButtonParseError::UnknownButtonState(b'3'))
);
#[cfg(feature = "alloc")]
{
use alloc::string::ToString;
use core::error::Error;
assert_eq!(
e.source().unwrap().to_string(),
"Unknown button state: 0x33"
);
}
} else {
assert!(false, "expected an error");
}
}

#[test]
Expand Down
2 changes: 1 addition & 1 deletion src/location_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use super::{try_f32_from_le_bytes, ProtocolParseError};

/// Represents a location event from the protocol.
#[derive(PartialEq, Debug)]
#[derive(PartialEq, Debug, Copy, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[allow(missing_docs)] // the names are already obvious enough
Expand Down
Loading