diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 51085c6..04a8783 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -13,13 +13,8 @@ jobs: strategy: fail-fast: false matrix: - rust: [1.62.0, stable] - features: ['use_alloc', 'use_alloc,defmt', 'use_heapless', 'use_heapless,defmt'] - exclude: - - rust: 1.62.0 - features: 'use_alloc,defmt' - - rust: 1.62.0 - features: 'use_heapless,defmt' + rust: [1.81.0, stable] + features: [''] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -31,17 +26,17 @@ jobs: - name: Install required cargo components run: cargo +stable install clippy-sarif sarif-fmt - name: build - run: cargo build --features ${{ matrix.features }} + run: cargo build ${{ matrix.features }} - name: check - run: cargo check --features ${{ matrix.features }} + run: cargo check ${{ matrix.features }} - name: test - run: cargo test --features ${{ matrix.features }} + run: cargo test ${{ matrix.features }} - name: check formatting run: cargo fmt --all -- --check - name: audit run: cargo audit - name: clippy (lib) - run: cargo clippy --features ${{ matrix.features }} --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt + run: cargo clippy ${{ matrix.features }} --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt continue-on-error: true - name: Upload analysis results to GitHub uses: github/codeql-action/upload-sarif@v3 @@ -87,3 +82,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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e37b31..fdca254 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [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 `parse` API has been replaced with `Parser::new` where `Parser` now implements `Iterator` and the `next` function returns each parsed command + * Accordingly, the features `use_alloc` and `use_heapless` have been removed. ## [0.2.0] - 2023-11-14 ### Added diff --git a/Cargo.toml b/Cargo.toml index 39c3a76..10c72e5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" @@ -12,7 +12,6 @@ license = "MIT OR Apache-2.0" [dependencies] defmt = { version = "0.3", optional = true } -heapless = { version = "0.8", optional = true } rgb = { version = "0.8", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } @@ -20,10 +19,7 @@ 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 = [] - -defmt = ["dep:defmt", "heapless?/defmt-03"] +defmt = ["dep:defmt"] accelerometer_event = [] button_event = [] diff --git a/README.md b/README.md index 47d6fd3..f192079 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,6 @@ which is e.g. used by the [Adafruit Bluefruit LE UART Friend](https://learn.adaf 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`. -If you select neither or both you'll get a compile error. - ## Optional Features * `defmt`: you can enable the [`defmt`](https://defmt.ferrous-systems.com/) 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 for RGB8` is implemented to support the [RGB crate](https://crates.io/crates/rgb). @@ -23,6 +17,10 @@ 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 `Parser` which implements `Iterator` to access the events in the input. +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 parser just expects a byte sequence. + ## Examples A simple example for the STM32F4 microcontrollers is [available](examples/stm32f4-event-printer/README.md). @@ -30,5 +28,5 @@ A simple example for the STM32F4 microcontrollers is [available](examples/stm32f 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. diff --git a/examples/stm32f4-event-printer/Cargo.lock b/examples/stm32f4-event-printer/Cargo.lock index 8df482e..0c76744 100644 --- a/examples/stm32f4-event-printer/Cargo.lock +++ b/examples/stm32f4-event-printer/Cargo.lock @@ -7,7 +7,6 @@ name = "adafruit-bluefruit-protocol" version = "0.2.0" dependencies = [ "defmt", - "heapless 0.8.0", ] [[package]] @@ -100,7 +99,7 @@ dependencies = [ "bare-metal 1.0.0", "cortex-m", "cortex-m-rtic-macros", - "heapless 0.7.17", + "heapless", "rtic-core", "rtic-monotonic", "version_check", @@ -271,15 +270,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "hash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" -dependencies = [ - "byteorder", -] - [[package]] name = "hashbrown" version = "0.12.3" @@ -293,23 +283,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" dependencies = [ "atomic-polyfill", - "hash32 0.2.1", + "hash32", "rustc_version 0.4.0", "spin", "stable_deref_trait", ] -[[package]] -name = "heapless" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" -dependencies = [ - "defmt", - "hash32 0.3.1", - "stable_deref_trait", -] - [[package]] name = "indexmap" version = "1.9.3" diff --git a/examples/stm32f4-event-printer/Cargo.toml b/examples/stm32f4-event-printer/Cargo.toml index 6b6afb4..d14a281 100644 --- a/examples/stm32f4-event-printer/Cargo.toml +++ b/examples/stm32f4-event-printer/Cargo.toml @@ -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"] } [profile.release] codegen-units = 1 diff --git a/examples/stm32f4-event-printer/src/adafruit_bluefruit_le_uart_friend.rs b/examples/stm32f4-event-printer/src/adafruit_bluefruit_le_uart_friend.rs index 0b72269..940d3cd 100644 --- a/examples/stm32f4-event-printer/src/adafruit_bluefruit_le_uart_friend.rs +++ b/examples/stm32f4-event-printer/src/adafruit_bluefruit_le_uart_friend.rs @@ -84,8 +84,10 @@ impl BluefruitLEUARTFriend { filled_buffer ); - let event = adafruit_bluefruit_protocol::parse::<4>(filled_buffer); - defmt::info!("received event(s) over bluetooth: {}", &event); + let parser = adafruit_bluefruit_protocol::Parser::new(filled_buffer); + for event in parser { + defmt::info!("received event over bluetooth: {:?}", &event); + } // switch out the buffers filled_buffer.fill(0); diff --git a/src/accelerometer_event.rs b/src/accelerometer_event.rs index 51f5fef..88ceda8 100644 --- a/src/accelerometer_event.rs +++ b/src/accelerometer_event.rs @@ -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 diff --git a/src/button_event.rs b/src/button_event.rs index 0cae4e3..311e0fe 100644 --- a/src/button_event.rs +++ b/src/button_event.rs @@ -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`]. @@ -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 { @@ -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 @@ -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 { diff --git a/src/color_event.rs b/src/color_event.rs index 386f4d6..0f1070b 100644 --- a/src/color_event.rs +++ b/src/color_event.rs @@ -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 diff --git a/src/gyro_event.rs b/src/gyro_event.rs index a081fde..6c2ee51 100644 --- a/src/gyro_event.rs +++ b/src/gyro_event.rs @@ -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 diff --git a/src/lib.rs b/src/lib.rs index 6a1fcac..5361df4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 [`Parser`]. 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 parser 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 for RGB8` is implemented to support the [RGB crate](https://crates.io/crates/rgb). @@ -29,12 +32,6 @@ )))] 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(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(feature = "accelerometer_event")] pub mod accelerometer_event; #[cfg(feature = "button_event")] @@ -59,13 +56,9 @@ use color_event::ColorEvent; use core::cmp::min; #[cfg(feature = "gyro_event")] use gyro_event::GyroEvent; -#[cfg(feature = "use_heapless")] -use heapless::Vec; -#[cfg(feature = "use_alloc")] -extern crate alloc; -#[cfg(feature = "use_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")] @@ -73,8 +66,8 @@ use magnetometer_event::MagnetometerEvent; #[cfg(feature = "quaternion_event")] 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)] +/// Lists all (supported) events which can be sent by the controller. These come with the parsed event data. +#[derive(PartialEq, Debug, Copy, Clone)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[allow(missing_docs)] // the names are already obvious enough pub enum ControllerEvent { @@ -95,14 +88,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), /// 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), @@ -117,8 +109,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 { @@ -168,52 +198,111 @@ impl TryFrom for ControllerDataPackageType { } } -#[cfg(feature = "use_heapless")] -type ParseResult = - Vec, MAX_RESULTS>; - -#[cfg(feature = "use_alloc")] -type ParseResult = Vec>; -#[cfg(feature = "use_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>( - input: &[u8], -) -> ParseResult { - /// Simple state machine for the parser, represents whether the parser is seeking a start or has found it. - enum ParserState { - SeekStart, - ParseCommand, +/// Parse the input string for commands. +/// +/// Null bytes (`b"\x00"`) will be skipped completely, unparseable content will return `Some(Err(ProtocolParseError))` +/// but will not fail the parsing completely (i.e. you can continue to get the next entry until you reach the end of the input). +/// +/// ## Example +/// ``` +/// # use adafruit_bluefruit_protocol::button_event::{Button, ButtonParseError, ButtonState}; +/// # use adafruit_bluefruit_protocol::ControllerEvent::ButtonEvent; +/// # use adafruit_bluefruit_protocol::{ControllerEvent, Parser, ProtocolParseError}; +/// +/// /// internal test helper +/// fn assert_is_button_event( +/// event: &Result, +/// button: Button, +/// button_state: ButtonState, +/// ) { +/// match event { +/// Ok(ButtonEvent(event)) => { +/// assert_eq!(event.button(), &button); +/// assert_eq!(event.state(), &button_state) +/// } +/// _ => assert!(false), +/// } +/// } +/// +/// // the example input contains some null bytes, two button events and two malformed events. +/// let input = b"\x00!B11:!B10;\x00\x00!\x00\x00\x00\x00!B138"; +/// let mut parser = Parser::new(input); +/// +/// assert_is_button_event( +/// &parser.next().unwrap(), +/// Button::Button1, +/// ButtonState::Pressed, +/// ); +/// assert_is_button_event( +/// &parser.next().unwrap(), +/// Button::Button1, +/// ButtonState::Released, +/// ); +/// assert_eq!( +/// parser.next().unwrap(), +/// Err(ProtocolParseError::UnknownEvent(Some(0))) +/// ); +/// if let Err(e) = &parser.next().unwrap() { +/// assert_eq!( +/// e, +/// &ProtocolParseError::ButtonParseError(ButtonParseError::UnknownButtonState(b'3')) +/// ); +/// # { +/// // test the `core::error::Error` implementation +/// # extern crate 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"); +/// } +/// assert_eq!(parser.next(), None); +/// ``` +#[derive(Debug, Copy, Clone)] +pub struct Parser<'a> { + input: &'a [u8], + curr_pos: usize, +} + +impl<'a> Parser<'a> { + /// Create a new parser. The input is parsed step by step on each invocation of `next`. + pub fn new(input: &'a [u8]) -> Self { + Self { input, curr_pos: 0 } } - let mut state = ParserState::SeekStart; +} - let mut result = Vec::new(); +impl Iterator for Parser<'_> { + type Item = Result; - for pos in 0..input.len() { - let byte = input[pos]; - match state { - ParserState::SeekStart => { - if byte == b'!' { - state = ParserState::ParseCommand + fn next(&mut self) -> Option { + /// Simple state machine for the parser, represents whether the parser is seeking a start or has found it. + enum ParserState { + SeekStart, + ParseCommand, + } + let mut state = ParserState::SeekStart; + + for pos in self.curr_pos..self.input.len() { + let byte = self.input[pos]; + match state { + ParserState::SeekStart => { + if byte == b'!' { + state = ParserState::ParseCommand + } } - } - ParserState::ParseCommand => { - let data_package = extract_and_parse_command(&input[(pos - 1)..]); - #[cfg(feature = "use_alloc")] - result.push(data_package); - #[cfg(feature = "use_heapless")] - result.push(data_package).ok(); - #[cfg(feature = "use_heapless")] - if result.len() == MAX_RESULTS { - return result; + ParserState::ParseCommand => { + self.curr_pos = pos; + return Some(extract_and_parse_command(&self.input[(pos - 1)..])); } - state = ParserState::SeekStart; - } - }; - } + }; + } - result + None + } } /// Extract a command and then try to parse it. @@ -340,41 +429,12 @@ fn try_f32_from_le_bytes(input: &[u8]) -> Result { #[cfg(test)] mod tests { - use crate::button_event::{Button, ButtonState}; - use crate::{check_crc, parse, try_f32_from_le_bytes, ControllerEvent, ProtocolParseError}; - - fn assert_is_button_event( - event: &Result, - button: Button, - button_state: ButtonState, - ) { - match event { - Ok(ControllerEvent::ButtonEvent(event)) => { - assert_eq!(event.button(), &button); - assert_eq!(event.state(), &button_state) - } - _ => assert!(false), - } - } - - #[test] - fn test_parse() { - let input = b"\x00!B11:!B10;\x00\x00!\x00\x00\x00\x00"; - #[cfg(feature = "use_heapless")] - let result = parse::<4>(input); - #[cfg(feature = "use_alloc")] - let result = parse(input); - - assert_eq!(result.len(), 3); - 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)))); - } + use crate::{check_crc, try_f32_from_le_bytes, ProtocolParseError}; #[test] fn test_check_crc_ok() { let input = b"!B11:"; - let data = &input[0..input.len() - 1]; + let data = &input[..input.len() - 1]; let crc = input.last().unwrap(); assert!(check_crc(data, &crc).is_ok()); @@ -384,7 +444,7 @@ mod tests { fn test_check_crc_err() { let input = b"!B11;"; // should either be "!B11:" or "!B10;" let correct_crc = b':'; - let data = &input[0..input.len() - 1]; + let data = &input[..input.len() - 1]; let crc = input.last().unwrap(); assert_eq!( diff --git a/src/location_event.rs b/src/location_event.rs index 8095dc7..cd798b2 100644 --- a/src/location_event.rs +++ b/src/location_event.rs @@ -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 diff --git a/src/magnetometer_event.rs b/src/magnetometer_event.rs index b4fd063..56bc7cf 100644 --- a/src/magnetometer_event.rs +++ b/src/magnetometer_event.rs @@ -3,7 +3,7 @@ use super::{try_f32_from_le_bytes, ProtocolParseError}; /// Represents a magnetometer 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 diff --git a/src/quaternion_event.rs b/src/quaternion_event.rs index 379eb2c..640c887 100644 --- a/src/quaternion_event.rs +++ b/src/quaternion_event.rs @@ -3,7 +3,7 @@ use super::{try_f32_from_le_bytes, ProtocolParseError}; /// Represents a [quaternion](https://en.wikipedia.org/wiki/Quaternion) 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