From c58be44d1eb2c5bbc735e5bb3a7dbf783d402c3b Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Thu, 17 Oct 2024 12:02:32 +0200 Subject: [PATCH 01/21] feat: introduce a sensor abstraction --- .github/workflows/build-deploy-docs.yml | 2 +- .github/workflows/main.yml | 6 +- Cargo.lock | 31 ++ Cargo.toml | 4 + src/riot-rs-macros/Cargo.toml | 10 + .../src/define_count_adjusted_enums.rs | 128 ++++++++ src/riot-rs-macros/src/lib.rs | 1 + src/riot-rs-sensors/Cargo.toml | 28 ++ src/riot-rs-sensors/src/category.rs | 17 + src/riot-rs-sensors/src/label.rs | 39 +++ src/riot-rs-sensors/src/lib.rs | 70 ++++ src/riot-rs-sensors/src/measurement_unit.rs | 31 ++ src/riot-rs-sensors/src/registry.rs | 41 +++ src/riot-rs-sensors/src/sensor.rs | 303 ++++++++++++++++++ src/riot-rs-sensors/src/value.rs | 106 ++++++ src/riot-rs/Cargo.toml | 4 + src/riot-rs/src/lib.rs | 3 + 17 files changed, 820 insertions(+), 4 deletions(-) create mode 100644 src/riot-rs-macros/src/define_count_adjusted_enums.rs create mode 100644 src/riot-rs-sensors/Cargo.toml create mode 100644 src/riot-rs-sensors/src/category.rs create mode 100644 src/riot-rs-sensors/src/label.rs create mode 100644 src/riot-rs-sensors/src/lib.rs create mode 100644 src/riot-rs-sensors/src/measurement_unit.rs create mode 100644 src/riot-rs-sensors/src/registry.rs create mode 100644 src/riot-rs-sensors/src/sensor.rs create mode 100644 src/riot-rs-sensors/src/value.rs diff --git a/.github/workflows/build-deploy-docs.yml b/.github/workflows/build-deploy-docs.yml index f2832f6db..e92c5787e 100644 --- a/.github/workflows/build-deploy-docs.yml +++ b/.github/workflows/build-deploy-docs.yml @@ -48,7 +48,7 @@ jobs: - name: Build rustdoc docs run: | - cargo doc -p riot-rs --features bench,csprng,executor-thread,external-interrupts,hwrng,i2c,no-boards,random,threading,usb + cargo doc -p riot-rs --features bench,csprng,executor-thread,external-interrupts,hwrng,i2c,no-boards,random,sensors,threading,usb echo "" > target/doc/index.html mkdir -p ./_site/dev/docs/api && mv target/doc/* ./_site/dev/docs/api diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3ce7bcf4d..42b1ed253 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -116,7 +116,7 @@ jobs: # TODO: we'll eventually want to enable relevant features - name: Run crate tests run: | - cargo test --no-default-features --features i2c,no-boards -p riot-rs -p riot-rs-embassy -p riot-rs-embassy-common -p riot-rs-runqueue -p riot-rs-threads -p riot-rs-macros + cargo test --no-default-features --features i2c,no-boards -p riot-rs -p riot-rs-embassy -p riot-rs-embassy-common -p riot-rs-runqueue -p riot-rs-sensors -p riot-rs-threads -p riot-rs-macros cargo test -p rbi -p ringbuffer -p coapcore # We need to set `RUSTDOCFLAGS` as well in the following jobs, because it @@ -176,7 +176,7 @@ jobs: - name: clippy uses: clechasseur/rs-clippy-check@v3 with: - args: --verbose --locked --features no-boards,external-interrupts -p riot-rs -p riot-rs-boards -p riot-rs-chips -p riot-rs-debug -p riot-rs-embassy -p riot-rs-embassy-common -p riot-rs-macros -p riot-rs-random -p riot-rs-rt -p riot-rs-threads -p riot-rs-utils -- --deny warnings + args: --verbose --locked --features no-boards,external-interrupts,sensors -p riot-rs -p riot-rs-boards -p riot-rs-chips -p riot-rs-debug -p riot-rs-embassy -p riot-rs-embassy-common -p riot-rs-macros -p riot-rs-random -p riot-rs-rt -p riot-rs-sensors -p riot-rs-threads -p riot-rs-utils -- --deny warnings - run: echo 'RUSTFLAGS=--cfg context="esp32c6"' >> $GITHUB_ENV - name: clippy for ESP32 @@ -206,7 +206,7 @@ jobs: - run: echo 'RUSTFLAGS=' >> $GITHUB_ENV - name: rustdoc - run: RUSTDOCFLAGS='-D warnings' cargo doc -p riot-rs --features bench,csprng,executor-thread,external-interrupts,hwrng,i2c,no-boards,random,threading,usb + run: RUSTDOCFLAGS='-D warnings' cargo doc -p riot-rs --features bench,csprng,executor-thread,external-interrupts,hwrng,i2c,no-boards,random,sensors,threading,usb - name: rustdoc for ESP32 run: RUSTDOCFLAGS='-D warnings --cfg context="esp32c6"' cargo doc --target=riscv32imac-unknown-none-elf --features external-interrupts,i2c,esp-hal/esp32c6,esp-hal-embassy/esp32c6 -p riot-rs-esp diff --git a/Cargo.lock b/Cargo.lock index 9ead0189c..691187c90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3262,6 +3262,26 @@ dependencies = [ "serde-json-core", ] +[[package]] +name = "pin-project" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -3610,6 +3630,7 @@ dependencies = [ "riot-rs-macros", "riot-rs-random", "riot-rs-rt", + "riot-rs-sensors", "riot-rs-threads", "riot-rs-utils", "static_cell", @@ -3822,6 +3843,16 @@ dependencies = [ "hax-lib", ] +[[package]] +name = "riot-rs-sensors" +version = "0.1.0" +dependencies = [ + "embassy-sync 0.6.0", + "linkme", + "pin-project", + "riot-rs-macros", +] + [[package]] name = "riot-rs-stm32" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 873713c71..e27194a79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "src/riot-rs-nrf", "src/riot-rs-random", "src/riot-rs-rp", + "src/riot-rs-sensors", "src/riot-rs-stm32", "tests/benchmarks/bench_sched_flags", "tests/benchmarks/bench_sched_yield", @@ -85,9 +86,11 @@ riot-rs-boards = { path = "src/riot-rs-boards", default-features = false } riot-rs-debug = { path = "src/riot-rs-debug", default-features = false } riot-rs-embassy = { path = "src/riot-rs-embassy", default-features = false } riot-rs-embassy-common = { path = "src/riot-rs-embassy-common" } +riot-rs-macros = { path = "src/riot-rs-macros" } riot-rs-random = { path = "src/riot-rs-random" } riot-rs-rt = { path = "src/riot-rs-rt" } riot-rs-runqueue = { path = "src/riot-rs-runqueue" } +riot-rs-sensors = { path = "src/riot-rs-sensors" } riot-rs-stm32 = { path = "src/riot-rs-stm32" } riot-rs-utils = { path = "src/riot-rs-utils", default-features = false } @@ -102,6 +105,7 @@ once_cell = { version = "=1.19.0", default-features = false, features = [ "critical-section", ] } paste = { version = "1.0" } +pin-project = "1.1.6" static_cell = { version = "2.0.0", features = ["nightly"] } [profile.dev] diff --git a/src/riot-rs-macros/Cargo.toml b/src/riot-rs-macros/Cargo.toml index 9bfd250be..f7134b322 100644 --- a/src/riot-rs-macros/Cargo.toml +++ b/src/riot-rs-macros/Cargo.toml @@ -32,3 +32,13 @@ trybuild = "1.0.89" [lib] proc-macro = true + +[features] +# These features could be codegened +max-reading-value-min-count-2 = [] +max-reading-value-min-count-3 = [] +max-reading-value-min-count-4 = [] +max-reading-value-min-count-6 = [] +max-reading-value-min-count-7 = [] +max-reading-value-min-count-9 = [] +max-reading-value-min-count-12 = [] diff --git a/src/riot-rs-macros/src/define_count_adjusted_enums.rs b/src/riot-rs-macros/src/define_count_adjusted_enums.rs new file mode 100644 index 000000000..028bd492d --- /dev/null +++ b/src/riot-rs-macros/src/define_count_adjusted_enums.rs @@ -0,0 +1,128 @@ +#[proc_macro] +pub fn define_count_adjusted_enums(_item: TokenStream) -> TokenStream { + use quote::quote; + + #[allow(clippy::wildcard_imports)] + use define_count_adjusted_enum::*; + + // The order of these feature-gated statements is important as these features are not meant to + // be mutually exclusive. + #[allow(unused_variables, reason = "overridden by feature selection")] + let count = 1; + #[cfg(feature = "max-reading-value-min-count-2")] + let count = 2; + #[cfg(feature = "max-reading-value-min-count-3")] + let count = 3; + #[cfg(feature = "max-reading-value-min-count-4")] + let count = 4; + #[cfg(feature = "max-reading-value-min-count-6")] + let count = 6; + #[cfg(feature = "max-reading-value-min-count-7")] + let count = 7; + #[cfg(feature = "max-reading-value-min-count-9")] + let count = 9; + #[cfg(feature = "max-reading-value-min-count-12")] + let count = 12; + + let physical_values_variants = (1..=count).map(|i| { + let variant = variant_name(i); + quote! { #variant([Value; #i]) } + }); + let physical_values_first_value = (1..=count).map(|i| { + let variant = variant_name(i); + quote! { + Self::#variant(values) => { + if let Some(value) = values.first() { + *value + } else { + // NOTE(no-panic): there is always at least one value + unreachable!(); + } + } + } + }); + + let reading_axes_variants = (1..=count).map(|i| { + let variant = variant_name(i); + quote! { #variant([ReadingAxis; #i]) } + }); + + let values_iter = (1..=count) + .map(|i| { + let variant = variant_name(i); + quote! { Self::#variant(values) => values.iter().copied() } + }) + .collect::>(); + + let expanded = quote! { + /// Values returned by a sensor driver. + /// + /// This type implements [`Reading`] to iterate over the values. + /// + /// # Note + /// + /// This type is automatically generated, the number of variants is automatically adjusted. + #[derive(Debug, Copy, Clone)] + pub enum Values { + #[doc(hidden)] + #(#physical_values_variants),* + } + + impl Reading for Values { + fn value(&self) -> Value { + match self { + #(#physical_values_first_value),* + } + } + + fn values(&self) -> impl ExactSizeIterator { + match self { + #(#values_iter),* + } + } + } + + /// Metadata required to interpret values returned by [`Sensor::wait_for_reading()`]. + /// + /// # Note + /// + /// This type is automatically generated, the number of variants is automatically adjusted. + #[derive(Debug, Copy, Clone)] + pub enum ReadingAxes { + #[doc(hidden)] + #(#reading_axes_variants),*, + } + + impl ReadingAxes { + /// Returns an iterator over the underlying [`ReadingAxis`] items. + /// + /// For a given sensor driver, the number and order of items match the one of + /// [`Values`]. + /// [`Iterator::zip()`] can be useful to zip the returned iterator with the one + /// obtained with [`Reading::values()`]. + pub fn iter(&self) -> impl Iterator + '_ { + match self { + #(#values_iter),*, + } + } + + /// Returns the first [`ReadingAxis`]. + pub fn first(&self) -> ReadingAxis { + if let Some(value) = self.iter().next() { + value + } else { + // NOTE(no-panic): there is always at least one value. + unreachable!(); + } + } + } + }; + + TokenStream::from(expanded) +} + +mod define_count_adjusted_enum { + pub fn variant_name(index: usize) -> syn::Ident { + quote::format_ident!("V{index}") + } +} diff --git a/src/riot-rs-macros/src/lib.rs b/src/riot-rs-macros/src/lib.rs index d1a72394c..aee9ec7c5 100644 --- a/src/riot-rs-macros/src/lib.rs +++ b/src/riot-rs-macros/src/lib.rs @@ -5,6 +5,7 @@ mod utils; use proc_macro::TokenStream; include!("config.rs"); +include!("define_count_adjusted_enums.rs"); include!("spawner.rs"); include!("task.rs"); include!("thread.rs"); diff --git a/src/riot-rs-sensors/Cargo.toml b/src/riot-rs-sensors/Cargo.toml new file mode 100644 index 000000000..af60845c2 --- /dev/null +++ b/src/riot-rs-sensors/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "riot-rs-sensors" +version.workspace = true +authors.workspace = true +license.workspace = true +edition.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +embassy-sync = { workspace = true } +linkme = { workspace = true } +pin-project = { workspace = true } +riot-rs-macros = { workspace = true } + +[features] +# These features could be codegened +max-reading-value-min-count-2 = ["riot-rs-macros/max-reading-value-min-count-2"] +max-reading-value-min-count-3 = ["riot-rs-macros/max-reading-value-min-count-3"] +max-reading-value-min-count-4 = ["riot-rs-macros/max-reading-value-min-count-4"] +max-reading-value-min-count-6 = ["riot-rs-macros/max-reading-value-min-count-6"] +max-reading-value-min-count-7 = ["riot-rs-macros/max-reading-value-min-count-7"] +max-reading-value-min-count-9 = ["riot-rs-macros/max-reading-value-min-count-9"] +max-reading-value-min-count-12 = [ + "riot-rs-macros/max-reading-value-min-count-12", +] diff --git a/src/riot-rs-sensors/src/category.rs b/src/riot-rs-sensors/src/category.rs new file mode 100644 index 000000000..ef58c1c99 --- /dev/null +++ b/src/riot-rs-sensors/src/category.rs @@ -0,0 +1,17 @@ +/// Categories a sensor can be part of. +/// +/// A sensor can be part of multiple categories. +// Built upon https://doc.riot-os.org/group__drivers__saul.html#ga8f2dfec7e99562dbe5d785467bb71bbb +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Category { + /// Accelerometer. + Accelerometer, + /// Humidity sensor. + Humidity, + /// Humidity and temperature sensor. + HumidityTemperature, + /// Push button. + PushButton, + /// Temperature sensor. + Temperature, +} diff --git a/src/riot-rs-sensors/src/label.rs b/src/riot-rs-sensors/src/label.rs new file mode 100644 index 000000000..287e73a86 --- /dev/null +++ b/src/riot-rs-sensors/src/label.rs @@ -0,0 +1,39 @@ +/// Label of a [`Value`](crate::sensor::Value) part of a +/// [`Values`](crate::sensor::Values) tuple. +/// +/// # For sensor driver implementors +/// +/// [`Label::Main`] must be used for sensor drivers returning a single +/// [`Value`](crate::sensor::Value), even if a more specific label exists for the +/// physical quantity. +/// This allows consumers displaying the label to ignore it for sensor drivers returning a single +/// [`Value`](crate::sensor::Value). +/// Other labels are reserved for sensor drivers returning multiple physical quantities. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Label { + /// Used for sensor drivers returning a single [`Value`](crate::sensor::Value). + Main, + /// Humidity. + Humidity, + /// Temperature. + Temperature, + /// X axis. + X, + /// Y axis. + Y, + /// Z axis. + Z, +} + +impl core::fmt::Display for Label { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Main => write!(f, ""), + Self::Humidity => write!(f, "Humidity"), + Self::Temperature => write!(f, "Temperature"), + Self::X => write!(f, "X"), + Self::Y => write!(f, "Y"), + Self::Z => write!(f, "Z"), + } + } +} diff --git a/src/riot-rs-sensors/src/lib.rs b/src/riot-rs-sensors/src/lib.rs new file mode 100644 index 000000000..fed3cab49 --- /dev/null +++ b/src/riot-rs-sensors/src/lib.rs @@ -0,0 +1,70 @@ +//! Provides a sensor abstraction layer. +//! +//! # Definitions +//! +//! In the context of this abstraction: +//! +//! - A *sensor device* is a device measuring one or multiple physical quantities and reporting +//! them as one or more digital values. - Sensor devices measuring the same physical quantity are +//! said to be part of the same *sensor category*. +//! A sensor device may be part of multiple sensor categories. +//! - A *measurement* is the physical operation of measuring one or several physical quantities. +//! - A *reading* is the digital result returned by a sensor device after carrying out a +//! measurement. +//! Values of different physical quantities can therefore be part of the same reading. +//! - A *sensor driver* refers to a sensor device as exposed by the sensor abstraction layer. +//! - A *sensor driver instance* is an instance of a sensor driver. +//! +//! # Accessing sensor driver instances +//! +//! Registered sensor driver instances can be accessed using +//! [`REGISTRY::sensors()`](registry::Registry::sensors). +//! Sensor drivers implement the [`Sensor`] trait, which allows to trigger measurements and obtain +//! the resulting readings. +//! +//! # Obtaining a sensor reading +//! +//! After triggering a measurement with [`Sensor::trigger_measurement()`], a reading can be +//! obtained using [`Sensor::wait_for_reading()`]. +//! It is additionally necessary to use [`Sensor::reading_axes()`] to make sense of the obtained +//! reading: +//! +//! - [`Sensor::wait_for_reading()`] returns a [`Values`](sensor::Values), a data "tuple" +//! containing values returned by the sensor driver. +//! - [`Sensor::reading_axes()`] returns a [`ReadingAxes`](sensor::ReadingAxes) which +//! indicates which physical quantity each [`Value`](value::Value) from that tuple corresponds +//! to, using a [`Label`]. +//! For instance, this allows to disambiguate the values provided by a temperature & humidity +//! sensor. +//! Each [`ReadingAxis`](sensor::ReadingAxis) also provides information about the +//! measurement accuracy, through +//! [`ReadingAxis::accuracy_fn()`](sensor::ReadingAxis::accuracy_fn). +//! +//! To avoid handling floats, [`Value`](value::Value)s returned by [`Sensor::wait_for_reading()`] +//! are integers, and a fixed scaling value is provided in [`ReadingAxis`](sensor::ReadingAxis), +//! for each [`Value`](value::Value) returned. +//! See [`Value`](value::Value) for more details. +//! +//! # For implementors +//! +//! Sensor drivers must implement the [`Sensor`] trait. +//! +#![no_std] +// Required by linkme +#![feature(used_with_arg)] +#![deny(clippy::pedantic)] +#![deny(missing_docs)] + +mod category; +mod label; +mod measurement_unit; +pub mod registry; +pub mod sensor; +mod value; + +pub use category::Category; +pub use label::Label; +pub use measurement_unit::MeasurementUnit; +pub use registry::{REGISTRY, SENSOR_REFS}; +pub use sensor::Sensor; +pub use value::Reading; diff --git a/src/riot-rs-sensors/src/measurement_unit.rs b/src/riot-rs-sensors/src/measurement_unit.rs new file mode 100644 index 000000000..e0aa0b3f3 --- /dev/null +++ b/src/riot-rs-sensors/src/measurement_unit.rs @@ -0,0 +1,31 @@ +/// Represents a unit of measurement. +// Built upon https://doc.riot-os.org/phydat_8h_source.html +// and https://bthome.io/format/#sensor-data +// and https://www.rfc-editor.org/rfc/rfc8798.html +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[non_exhaustive] +pub enum MeasurementUnit { + /// [Acceleration *g*](https://en.wikipedia.org/wiki/G-force#Unit_and_measurement). + AccelG, + /// Value one represents an active state (e.g., a push button being pressed). + ActiveOne, + /// Value zero represents an active state (e.g., a push button being pressed). + ActiveZero, + /// Logic boolean. + Bool, + /// Degree Celsius. + Celsius, + /// Percent. + Percent, +} + +impl core::fmt::Display for MeasurementUnit { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::AccelG => write!(f, "g"), + Self::ActiveOne | Self::ActiveZero | Self::Bool => write!(f, ""), + Self::Celsius => write!(f, "°C"), // The Unicode Standard v15 recommends using U+00B0 + U+0043. + Self::Percent => write!(f, "%"), // TODO: should we have a different unit for %RH? + } + } +} diff --git a/src/riot-rs-sensors/src/registry.rs b/src/riot-rs-sensors/src/registry.rs new file mode 100644 index 000000000..027bbe2f8 --- /dev/null +++ b/src/riot-rs-sensors/src/registry.rs @@ -0,0 +1,41 @@ +//! Provides a sensor driver instance registry, allowing to register sensor driver instances and +//! access them in a centralized location. + +use crate::Sensor; + +/// Stores references to registered sensor driver instances. +/// +/// To register a sensor driver instance, insert a `&'static` into this [distributed +/// slice](linkme). +/// The sensor driver will therefore need to be statically allocated, to be able to obtain a +/// `&'static`. +// Exclude this from the users' documentation, to force users to use `Registry::sensors()` instead, +// for easier forward compatibility with possibly non-static references. +#[doc(hidden)] +#[linkme::distributed_slice] +pub static SENSOR_REFS: [&'static dyn Sensor] = [..]; + +/// The global registry instance. +pub static REGISTRY: Registry = Registry::new(); + +/// The sensor driver instance registry. +/// +/// This is exposed as [`REGISTRY`]. +pub struct Registry { + _private: (), +} + +impl Registry { + // The constructor is private to make the registry a singleton. + const fn new() -> Self { + Self { _private: () } + } + + /// Returns an iterator over registered sensor driver instances. + pub fn sensors(&self) -> impl Iterator { + // Returning an iterator instead of the distributed slice directly would allow us to chain + // another source of sensor driver instances in the future, if we decided to support + // dynamically-allocated sensor driver instances. + SENSOR_REFS.iter().copied() + } +} diff --git a/src/riot-rs-sensors/src/sensor.rs b/src/riot-rs-sensors/src/sensor.rs new file mode 100644 index 000000000..4bed9d120 --- /dev/null +++ b/src/riot-rs-sensors/src/sensor.rs @@ -0,0 +1,303 @@ +//! Provides a [`Sensor`] trait abstracting over implementation details of a sensor driver. +use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::ReceiveFuture}; + +use crate::{Category, Label, MeasurementUnit}; + +pub use crate::{ + value::{Accuracy, Value}, + Reading, +}; + +/// This trait must be implemented by sensor drivers. +/// +/// See [the module level documentation](crate) for more. +pub trait Sensor: Send + Sync { + /// Triggers a measurement. + /// Clears the previous reading. + /// + /// To obtain readings from every sensor drivers this method can be called in a loop over all + /// sensors returned by [`Registry::sensors()`](crate::registry::Registry::sensors), before + /// obtaining the readings with [`Self::wait_for_reading()`], so that the measurements happen + /// concurrently. + /// + /// # For implementors + /// + /// This method should return quickly. + /// + /// # Errors + /// + /// Returns [`MeasurementError::NonEnabled`] if the sensor driver is not enabled. + fn trigger_measurement(&self) -> Result<(), MeasurementError>; + + /// Waits for the reading and returns it asynchronously. + /// Depending on the sensor device and the sensor driver, this may use a sensor interrupt or + /// data polling. + /// + /// Interpretation of the reading requires data from [`Sensor::reading_axes()`] as well. + /// See [the module level documentation](crate) for more. + /// + /// # Errors + /// + /// - Quickly returns [`ReadingError::NonEnabled`] if the sensor driver is not enabled. + /// - Returns [`ReadingError::SensorAccess`] if the sensor device cannot be accessed. + fn wait_for_reading(&'static self) -> ReadingWaiter; + + /// Provides information about the reading returned by [`Sensor::wait_for_reading()`]. + #[must_use] + fn reading_axes(&self) -> ReadingAxes; + + /// Sets the sensor driver mode and returns the previous state. + /// Allows to put the sensor device to sleep if supported. + /// + /// # Errors + /// + /// Returns [`ModeSettingError::Uninitialized`] if the sensor driver is not initialized. + fn set_mode(&self, mode: Mode) -> Result; + + /// Returns the current sensor driver state. + #[must_use] + fn state(&self) -> State; + + /// Returns the categories the sensor device is part of. + #[must_use] + fn categories(&self) -> &'static [Category]; + + /// String label of the sensor driver *instance*. + /// For instance, in the case of a temperature sensor, this allows to specify whether this + /// specific sensor device is placed indoor or outdoor. + #[must_use] + fn label(&self) -> Option<&'static str>; + + /// Returns a human-readable name of the *sensor driver*. + /// For instance, "push button" and "3-axis accelerometer" are appropriate display names. + /// + /// # Note + /// + /// Different sensor drivers for the same sensor device may have different display names. + #[must_use] + fn display_name(&self) -> Option<&'static str>; + + /// Returns the sensor device part number. + /// Returns `None` when the sensor device does not have a part number. + /// For instance, "DS18B20" is a valid part number. + #[must_use] + fn part_number(&self) -> Option<&'static str>; + + /// Returns the sensor driver version number. + #[must_use] + fn version(&self) -> u8; +} + +/// Future returned by [`Sensor::wait_for_reading()`]. +#[must_use = "futures do nothing unless you `.await` or poll them"] +#[pin_project::pin_project(project = ReadingWaiterProj)] +pub enum ReadingWaiter { + #[doc(hidden)] + Waiter { + #[pin] + waiter: ReceiveFuture<'static, CriticalSectionRawMutex, ReadingResult, 1>, + }, + #[doc(hidden)] + Err(ReadingError), + #[doc(hidden)] + Resolved, +} + +impl Future for ReadingWaiter { + type Output = ReadingResult; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.as_mut().project(); + match this { + ReadingWaiterProj::Waiter { waiter } => waiter.poll(cx), + ReadingWaiterProj::Err(err) => { + // Replace the error with a dummy error value, crafted from thin air, and mark the + // future as resolved, so that we do not take this dummy value into account later. + // This avoids requiring `Clone` on `ReadingError`. + let err = core::mem::replace(err, ReadingError::NonEnabled); + *self = ReadingWaiter::Resolved; + + Poll::Ready(Err(err)) + } + ReadingWaiterProj::Resolved => unreachable!(), + } + } +} + +/// Mode of a sensor driver. +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum Mode { + /// The sensor driver is disabled. + Disabled, + /// The sensor driver is enabled. + Enabled, + /// The sensor driver is sleeping. + /// The sensor device may be in a low-power mode. + Sleeping, +} + +/// Possible errors when attempting to set the mode of a sensor driver. +pub enum ModeSettingError { + /// The sensor driver is uninitialized. + /// It has not been initialized yet, or initialization could not succeed. + Uninitialized, +} + +/// State of a sensor driver. +#[derive(Copy, Clone, Default, PartialEq, Eq)] +#[repr(u8)] +pub enum State { + /// The sensor driver is uninitialized. + /// It has not been initialized yet, or initialization could not succeed. + #[default] + Uninitialized = 0, + /// The sensor driver is disabled. + Disabled = 1, + /// The sensor driver is enabled. + Enabled = 2, + /// The sensor driver is sleeping. + Sleeping = 3, +} + +impl From for State { + fn from(mode: Mode) -> Self { + match mode { + Mode::Disabled => Self::Disabled, + Mode::Enabled => Self::Enabled, + Mode::Sleeping => Self::Sleeping, + } + } +} + +impl TryFrom for State { + type Error = TryFromIntError; + + fn try_from(int: u8) -> Result { + match int { + 0 => Ok(State::Uninitialized), + 1 => Ok(State::Disabled), + 2 => Ok(State::Enabled), + 3 => Ok(State::Sleeping), + _ => Err(TryFromIntError), + } + } +} + +/// The error type returned when a checked integral type conversion fails. +#[derive(Debug)] +pub struct TryFromIntError; + +riot_rs_macros::define_count_adjusted_enums!(); + +/// Provides metadata about a [`Value`]. +#[derive(Debug, Copy, Clone)] +pub struct ReadingAxis { + label: Label, + scaling: i8, + unit: MeasurementUnit, + accuracy: AccuracyFn, +} + +impl ReadingAxis { + /// Creates a new [`ReadingAxis`]. + /// + /// This constructor is intended for sensor driver implementors only. + #[must_use] + pub fn new(label: Label, scaling: i8, unit: MeasurementUnit, accuracy: AccuracyFn) -> Self { + Self { + label, + scaling, + unit, + accuracy, + } + } + + /// Returns the [`Label`] for this axis. + #[must_use] + pub fn label(&self) -> Label { + self.label + } + + /// Returns the [scaling](Value) for this axis. + #[must_use] + pub fn scaling(&self) -> i8 { + self.scaling + } + + /// Returns the unit of measurement for this axis. + #[must_use] + pub fn unit(&self) -> MeasurementUnit { + self.unit + } + + /// Returns a function allowing to obtain the accuracy error of a recently obtained + /// [`Value`]. + /// + /// # Note + /// + /// As the accuracy may depend on the sensor driver configuration, that accuracy function + /// should only be used for one [`Value`] instance, and it is necessary to obtain an + /// up-to-date function through an up-to-date [`ReadingAxis`]. + #[must_use] + pub fn accuracy_fn(&self) -> AccuracyFn { + self.accuracy + } +} + +/// Function allowing to obtain the accuracy error of a [`Value`], returned by +/// [`ReadingAxis::accuracy_fn()`]. +pub type AccuracyFn = fn(Value) -> Accuracy; + +/// Represents errors happening when *triggering* a sensor measurement. +#[derive(Debug)] +pub enum MeasurementError { + /// The sensor driver is not enabled (e.g., it may be disabled or sleeping). + NonEnabled, +} + +impl core::fmt::Display for MeasurementError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::NonEnabled => write!(f, "sensor driver is not enabled"), + } + } +} + +impl core::error::Error for MeasurementError {} + +/// Represents errors happening when accessing a sensor reading. +#[derive(Debug)] +pub enum ReadingError { + /// The sensor driver is not enabled (e.g., it may be disabled or sleeping). + NonEnabled, + /// Cannot access the sensor device (e.g., because of a bus error). + SensorAccess, +} + +impl core::fmt::Display for ReadingError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::NonEnabled => write!(f, "sensor driver is not enabled"), + Self::SensorAccess => write!(f, "sensor device could not be accessed"), + } + } +} + +impl core::error::Error for ReadingError {} + +/// A specialized [`Result`] type for [`Reading`] operations. +pub type ReadingResult = Result; + +#[cfg(test)] +mod tests { + use super::*; + + // Assert that the Sensor trait is object-safe + static _SENSOR_REFS: &[&dyn Sensor] = &[]; +} diff --git a/src/riot-rs-sensors/src/value.rs b/src/riot-rs-sensors/src/value.rs new file mode 100644 index 000000000..ebbd62464 --- /dev/null +++ b/src/riot-rs-sensors/src/value.rs @@ -0,0 +1,106 @@ +#[expect(clippy::doc_markdown)] +/// Represents a value obtained from a sensor device. +/// +/// # Scaling +/// +/// The [scaling value](crate::sensor::ReadingAxis::scaling()) obtained from the sensor driver with +/// [`Sensor::reading_axes()`](crate::Sensor::reading_axes) must be taken into account using the +/// following formula: +/// +/// Value::value()·10scaling +/// +/// For instance, in the case of a temperature sensor, if [`Value::value()`] returns `2225` and the +/// scaling value is `-2`, this means that the temperature measured and returned by the sensor +/// device is `22.25` (the [measurement error](Accuracy) must additionally be taken into +/// account). +/// This is required to avoid handling floats. +/// +/// # Unit of measurement +/// +/// The unit of measurement can be obtained using +/// [`ReadingAxis::unit()`](crate::sensor::ReadingAxis::unit). +// NOTE(derive): we do not implement `Eq` or `PartialOrd` on purpose: `Eq` would prevent us from +// possibly adding floats in the future and `PartialOrd` does not make sense because interpreting +// the value requires the `ReadingAxis` associated with this `Value`. +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Value { + value: i32, +} + +impl Value { + /// Creates a new value. + #[must_use] + pub const fn new(value: i32) -> Self { + Self { value } + } + + /// Returns the value. + #[must_use] + pub fn value(&self) -> i32 { + self.value + } +} + +/// Specifies the accuracy of a measurement. +/// +/// The [`Accuracy`] should be obtained quickly after obtaining the [`Value`], as the +/// accuracy can be affected by a change in the internal state of the sensor driver. +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum Accuracy { + /// Unknown accuracy. + Unknown, + /// No measurement error (e.g., boolean values from a push button). + NoError, + /// Measurement error symmetrical around the [`bias`](Accuracy::SymmetricalError::bias). + /// + /// The unit of measurement is provided by the [`ReadingAxis`](crate::sensor::ReadingAxis) + /// associated to the [`Value`]. + /// The `scaling` value is used for both `deviation` and `bias`. + /// The accuracy error is thus given by the following formulas: + /// + /// +(bias+deviation)·10scaling/-(bias-deviation)·10scaling + /// + /// # Examples + /// + /// The DS18B20 temperature sensor accuracy error is +0.05/-0.45 + /// at 20 °C (see Figure 1 of its datasheet). + /// [`Accuracy`] would thus be the following: + /// + /// ``` + /// # use riot_rs_sensors::sensor::Accuracy; + /// Accuracy { + /// deviation: 25, + /// bias: -20, + /// scaling: -2, + /// } + /// # ; + /// ``` + SymmetricalError { + /// Deviation around the bias value. + deviation: i16, + /// Bias (mean accuracy error). + bias: i16, + /// Scaling of [`deviation`](Accuracy::SymmetricalError::deviation) and + /// [`bias`](Accuracy::SymmetricalError::bias). + scaling: i8, + }, +} + +/// Implemented on [`Values`](crate::sensor::Values), returned by +/// [`Sensor::wait_for_reading()`](crate::Sensor::wait_for_reading). +pub trait Reading: core::fmt::Debug { + /// Returns the first value returned by [`Reading::values()`]. + fn value(&self) -> Value; + + /// Returns an iterator over [`Value`]s of a sensor reading. + /// + /// The order of [`Value`]s is not significant, but is fixed. + /// + /// # For implementors + /// + /// The default implementation must be overridden on types containing multiple + /// [`Value`]s. + fn values(&self) -> impl ExactSizeIterator { + [self.value()].into_iter() + } +} diff --git a/src/riot-rs/Cargo.toml b/src/riot-rs/Cargo.toml index da6ffc527..621ad042f 100644 --- a/src/riot-rs/Cargo.toml +++ b/src/riot-rs/Cargo.toml @@ -18,6 +18,7 @@ riot-rs-embassy = { path = "../riot-rs-embassy" } riot-rs-macros = { path = "../riot-rs-macros" } riot-rs-random = { workspace = true, optional = true } riot-rs-rt = { path = "../riot-rs-rt" } +riot-rs-sensors = { workspace = true, optional = true } riot-rs-threads = { path = "../riot-rs-threads", optional = true } riot-rs-utils = { workspace = true } static_cell = { workspace = true } @@ -43,6 +44,9 @@ random = ["riot-rs-random"] csprng = ["riot-rs-random/csprng"] ## Enables seeding the random number generator from hardware. hwrng = ["riot-rs-embassy/hwrng"] +## Enables support for sensors. +## *Currently experimental.* +sensors = ["dep:riot-rs-sensors"] #! ## Serial communication ## Enables I2C support. diff --git a/src/riot-rs/src/lib.rs b/src/riot-rs/src/lib.rs index e88a610cf..e21fcc42a 100644 --- a/src/riot-rs/src/lib.rs +++ b/src/riot-rs/src/lib.rs @@ -23,6 +23,9 @@ pub use riot_rs_debug as debug; pub use riot_rs_random as random; #[doc(inline)] pub use riot_rs_rt as rt; +#[cfg(feature = "sensors")] +#[doc(inline)] +pub use riot_rs_sensors as sensors; #[cfg(feature = "threading")] #[doc(inline)] pub use riot_rs_threads as thread; From 28cf4c8ce2bff15eee674e7337eec4a14d9cd395 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:04:19 +0200 Subject: [PATCH 02/21] feat(sensors): add support for defmt --- Cargo.lock | 1 + src/riot-rs-sensors/Cargo.toml | 3 +++ src/riot-rs-sensors/src/category.rs | 1 + src/riot-rs-sensors/src/label.rs | 1 + src/riot-rs-sensors/src/measurement_unit.rs | 1 + src/riot-rs-sensors/src/sensor.rs | 6 ++++++ src/riot-rs-sensors/src/value.rs | 2 ++ 7 files changed, 15 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 691187c90..9978a4d40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3847,6 +3847,7 @@ dependencies = [ name = "riot-rs-sensors" version = "0.1.0" dependencies = [ + "defmt", "embassy-sync 0.6.0", "linkme", "pin-project", diff --git a/src/riot-rs-sensors/Cargo.toml b/src/riot-rs-sensors/Cargo.toml index af60845c2..41f266f5d 100644 --- a/src/riot-rs-sensors/Cargo.toml +++ b/src/riot-rs-sensors/Cargo.toml @@ -10,12 +10,15 @@ repository.workspace = true workspace = true [dependencies] +defmt = { workspace = true, optional = true } embassy-sync = { workspace = true } linkme = { workspace = true } pin-project = { workspace = true } riot-rs-macros = { workspace = true } [features] +defmt = ["dep:defmt"] + # These features could be codegened max-reading-value-min-count-2 = ["riot-rs-macros/max-reading-value-min-count-2"] max-reading-value-min-count-3 = ["riot-rs-macros/max-reading-value-min-count-3"] diff --git a/src/riot-rs-sensors/src/category.rs b/src/riot-rs-sensors/src/category.rs index ef58c1c99..259db19e5 100644 --- a/src/riot-rs-sensors/src/category.rs +++ b/src/riot-rs-sensors/src/category.rs @@ -3,6 +3,7 @@ /// A sensor can be part of multiple categories. // Built upon https://doc.riot-os.org/group__drivers__saul.html#ga8f2dfec7e99562dbe5d785467bb71bbb #[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Category { /// Accelerometer. Accelerometer, diff --git a/src/riot-rs-sensors/src/label.rs b/src/riot-rs-sensors/src/label.rs index 287e73a86..91c5f2ee3 100644 --- a/src/riot-rs-sensors/src/label.rs +++ b/src/riot-rs-sensors/src/label.rs @@ -10,6 +10,7 @@ /// [`Value`](crate::sensor::Value). /// Other labels are reserved for sensor drivers returning multiple physical quantities. #[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Label { /// Used for sensor drivers returning a single [`Value`](crate::sensor::Value). Main, diff --git a/src/riot-rs-sensors/src/measurement_unit.rs b/src/riot-rs-sensors/src/measurement_unit.rs index e0aa0b3f3..ce6cf2a22 100644 --- a/src/riot-rs-sensors/src/measurement_unit.rs +++ b/src/riot-rs-sensors/src/measurement_unit.rs @@ -3,6 +3,7 @@ // and https://bthome.io/format/#sensor-data // and https://www.rfc-editor.org/rfc/rfc8798.html #[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] pub enum MeasurementUnit { /// [Acceleration *g*](https://en.wikipedia.org/wiki/G-force#Unit_and_measurement). diff --git a/src/riot-rs-sensors/src/sensor.rs b/src/riot-rs-sensors/src/sensor.rs index 4bed9d120..2a4aeb6bb 100644 --- a/src/riot-rs-sensors/src/sensor.rs +++ b/src/riot-rs-sensors/src/sensor.rs @@ -132,6 +132,7 @@ impl Future for ReadingWaiter { /// Mode of a sensor driver. #[derive(Copy, Clone, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Mode { /// The sensor driver is disabled. Disabled, @@ -151,6 +152,7 @@ pub enum ModeSettingError { /// State of a sensor driver. #[derive(Copy, Clone, Default, PartialEq, Eq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum State { /// The sensor driver is uninitialized. @@ -191,12 +193,14 @@ impl TryFrom for State { /// The error type returned when a checked integral type conversion fails. #[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct TryFromIntError; riot_rs_macros::define_count_adjusted_enums!(); /// Provides metadata about a [`Value`]. #[derive(Debug, Copy, Clone)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct ReadingAxis { label: Label, scaling: i8, @@ -256,6 +260,7 @@ pub type AccuracyFn = fn(Value) -> Accuracy; /// Represents errors happening when *triggering* a sensor measurement. #[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum MeasurementError { /// The sensor driver is not enabled (e.g., it may be disabled or sleeping). NonEnabled, @@ -273,6 +278,7 @@ impl core::error::Error for MeasurementError {} /// Represents errors happening when accessing a sensor reading. #[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ReadingError { /// The sensor driver is not enabled (e.g., it may be disabled or sleeping). NonEnabled, diff --git a/src/riot-rs-sensors/src/value.rs b/src/riot-rs-sensors/src/value.rs index ebbd62464..e80e07546 100644 --- a/src/riot-rs-sensors/src/value.rs +++ b/src/riot-rs-sensors/src/value.rs @@ -23,6 +23,7 @@ // possibly adding floats in the future and `PartialOrd` does not make sense because interpreting // the value requires the `ReadingAxis` associated with this `Value`. #[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Value { value: i32, } @@ -46,6 +47,7 @@ impl Value { /// The [`Accuracy`] should be obtained quickly after obtaining the [`Value`], as the /// accuracy can be affected by a change in the internal state of the sensor driver. #[derive(Debug, Copy, Clone, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Accuracy { /// Unknown accuracy. Unknown, From 2e8f68d0e3e93d40dc3136a90af8d0ff0ffad1b6 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:04:57 +0200 Subject: [PATCH 03/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/category.rs | 1 + src/riot-rs-sensors/src/label.rs | 1 + src/riot-rs-sensors/src/registry.rs | 1 + src/riot-rs-sensors/src/sensor.rs | 8 +++++--- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/riot-rs-sensors/src/category.rs b/src/riot-rs-sensors/src/category.rs index 259db19e5..be8e90ae3 100644 --- a/src/riot-rs-sensors/src/category.rs +++ b/src/riot-rs-sensors/src/category.rs @@ -4,6 +4,7 @@ // Built upon https://doc.riot-os.org/group__drivers__saul.html#ga8f2dfec7e99562dbe5d785467bb71bbb #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] pub enum Category { /// Accelerometer. Accelerometer, diff --git a/src/riot-rs-sensors/src/label.rs b/src/riot-rs-sensors/src/label.rs index 91c5f2ee3..e467f450c 100644 --- a/src/riot-rs-sensors/src/label.rs +++ b/src/riot-rs-sensors/src/label.rs @@ -11,6 +11,7 @@ /// Other labels are reserved for sensor drivers returning multiple physical quantities. #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] +#[non_exhaustive] pub enum Label { /// Used for sensor drivers returning a single [`Value`](crate::sensor::Value). Main, diff --git a/src/riot-rs-sensors/src/registry.rs b/src/riot-rs-sensors/src/registry.rs index 027bbe2f8..86732ed0e 100644 --- a/src/riot-rs-sensors/src/registry.rs +++ b/src/riot-rs-sensors/src/registry.rs @@ -22,6 +22,7 @@ pub static REGISTRY: Registry = Registry::new(); /// /// This is exposed as [`REGISTRY`]. pub struct Registry { + // Prevents instantiation from outside this module. _private: (), } diff --git a/src/riot-rs-sensors/src/sensor.rs b/src/riot-rs-sensors/src/sensor.rs index 2a4aeb6bb..c1c354e9f 100644 --- a/src/riot-rs-sensors/src/sensor.rs +++ b/src/riot-rs-sensors/src/sensor.rs @@ -131,7 +131,7 @@ impl Future for ReadingWaiter { } /// Mode of a sensor driver. -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Mode { /// The sensor driver is disabled. @@ -151,7 +151,7 @@ pub enum ModeSettingError { } /// State of a sensor driver. -#[derive(Copy, Clone, Default, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[repr(u8)] pub enum State { @@ -199,8 +199,10 @@ pub struct TryFromIntError; riot_rs_macros::define_count_adjusted_enums!(); /// Provides metadata about a [`Value`]. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] +// NOTE(derive): we do not implement `Eq` on purpose: its would prevent us from possibly adding +// floats in the future. pub struct ReadingAxis { label: Label, scaling: i8, From 2f9c9233363a98d80678b74ad8a4a2c6b7557be9 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:09:59 +0200 Subject: [PATCH 04/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/category.rs | 5 +++++ src/riot-rs-sensors/src/label.rs | 3 +++ src/riot-rs-sensors/src/measurement_unit.rs | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/src/riot-rs-sensors/src/category.rs b/src/riot-rs-sensors/src/category.rs index be8e90ae3..d006ddc25 100644 --- a/src/riot-rs-sensors/src/category.rs +++ b/src/riot-rs-sensors/src/category.rs @@ -1,6 +1,11 @@ /// Categories a sensor can be part of. /// /// A sensor can be part of multiple categories. +/// +/// # For sensor driver implementors +/// +/// Missing variants can be added when required. +/// Please open an issue to discuss it. // Built upon https://doc.riot-os.org/group__drivers__saul.html#ga8f2dfec7e99562dbe5d785467bb71bbb #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] diff --git a/src/riot-rs-sensors/src/label.rs b/src/riot-rs-sensors/src/label.rs index e467f450c..f09c340d2 100644 --- a/src/riot-rs-sensors/src/label.rs +++ b/src/riot-rs-sensors/src/label.rs @@ -3,6 +3,9 @@ /// /// # For sensor driver implementors /// +/// Missing variants can be added when required. +/// Please open an issue to discuss it. +/// /// [`Label::Main`] must be used for sensor drivers returning a single /// [`Value`](crate::sensor::Value), even if a more specific label exists for the /// physical quantity. diff --git a/src/riot-rs-sensors/src/measurement_unit.rs b/src/riot-rs-sensors/src/measurement_unit.rs index ce6cf2a22..8ad5ab32e 100644 --- a/src/riot-rs-sensors/src/measurement_unit.rs +++ b/src/riot-rs-sensors/src/measurement_unit.rs @@ -1,4 +1,9 @@ /// Represents a unit of measurement. +/// +/// # For sensor driver implementors +/// +/// Missing variants can be added when required. +/// Please open an issue to discuss it. // Built upon https://doc.riot-os.org/phydat_8h_source.html // and https://bthome.io/format/#sensor-data // and https://www.rfc-editor.org/rfc/rfc8798.html From 8a600472c1e75da48e14fb2cf4671ebab9f6b0c0 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Fri, 18 Oct 2024 12:11:46 +0200 Subject: [PATCH 05/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/value.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/riot-rs-sensors/src/value.rs b/src/riot-rs-sensors/src/value.rs index e80e07546..c1790fce5 100644 --- a/src/riot-rs-sensors/src/value.rs +++ b/src/riot-rs-sensors/src/value.rs @@ -9,7 +9,7 @@ /// /// Value::value()·10scaling /// -/// For instance, in the case of a temperature sensor, if [`Value::value()`] returns `2225` and the +/// For instance, in the case of a temperature sensor, if [`Value::get()`] returns `2225` and the /// scaling value is `-2`, this means that the temperature measured and returned by the sensor /// device is `22.25` (the [measurement error](Accuracy) must additionally be taken into /// account). @@ -37,7 +37,7 @@ impl Value { /// Returns the value. #[must_use] - pub fn value(&self) -> i32 { + pub fn get(&self) -> i32 { self.value } } From 1070cea0dbac1790f5052abebb325eb76c3c4170 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:02:00 +0200 Subject: [PATCH 06/21] fixup! feat: introduce a sensor abstraction --- ..._adjusted_enums.rs => define_count_adjusted_sensor_enums.rs} | 2 +- src/riot-rs-macros/src/lib.rs | 2 +- src/riot-rs-sensors/src/sensor.rs | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) rename src/riot-rs-macros/src/{define_count_adjusted_enums.rs => define_count_adjusted_sensor_enums.rs} (98%) diff --git a/src/riot-rs-macros/src/define_count_adjusted_enums.rs b/src/riot-rs-macros/src/define_count_adjusted_sensor_enums.rs similarity index 98% rename from src/riot-rs-macros/src/define_count_adjusted_enums.rs rename to src/riot-rs-macros/src/define_count_adjusted_sensor_enums.rs index 028bd492d..628b6adf6 100644 --- a/src/riot-rs-macros/src/define_count_adjusted_enums.rs +++ b/src/riot-rs-macros/src/define_count_adjusted_sensor_enums.rs @@ -1,5 +1,5 @@ #[proc_macro] -pub fn define_count_adjusted_enums(_item: TokenStream) -> TokenStream { +pub fn define_count_adjusted_sensor_enums(_item: TokenStream) -> TokenStream { use quote::quote; #[allow(clippy::wildcard_imports)] diff --git a/src/riot-rs-macros/src/lib.rs b/src/riot-rs-macros/src/lib.rs index aee9ec7c5..5c63aa236 100644 --- a/src/riot-rs-macros/src/lib.rs +++ b/src/riot-rs-macros/src/lib.rs @@ -5,7 +5,7 @@ mod utils; use proc_macro::TokenStream; include!("config.rs"); -include!("define_count_adjusted_enums.rs"); +include!("define_count_adjusted_sensor_enums.rs"); include!("spawner.rs"); include!("task.rs"); include!("thread.rs"); diff --git a/src/riot-rs-sensors/src/sensor.rs b/src/riot-rs-sensors/src/sensor.rs index c1c354e9f..98056f17c 100644 --- a/src/riot-rs-sensors/src/sensor.rs +++ b/src/riot-rs-sensors/src/sensor.rs @@ -14,6 +14,8 @@ pub use crate::{ Reading, }; +riot_rs_macros::define_count_adjusted_sensor_enums!(); + /// This trait must be implemented by sensor drivers. /// /// See [the module level documentation](crate) for more. From e39d3000e224d3624c8ae75934299c309c6a7d87 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:02:08 +0200 Subject: [PATCH 07/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/sensor.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/riot-rs-sensors/src/sensor.rs b/src/riot-rs-sensors/src/sensor.rs index 98056f17c..68669fe22 100644 --- a/src/riot-rs-sensors/src/sensor.rs +++ b/src/riot-rs-sensors/src/sensor.rs @@ -146,12 +146,24 @@ pub enum Mode { } /// Possible errors when attempting to set the mode of a sensor driver. +#[derive(Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ModeSettingError { /// The sensor driver is uninitialized. /// It has not been initialized yet, or initialization could not succeed. Uninitialized, } +impl core::fmt::Display for ModeSettingError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Uninitialized => write!(f, "sensor driver is not initialized"), + } + } +} + +impl core::error::Error for ModeSettingError {} + /// State of a sensor driver. #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -198,7 +210,13 @@ impl TryFrom for State { #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct TryFromIntError; -riot_rs_macros::define_count_adjusted_enums!(); +impl core::fmt::Display for TryFromIntError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "out of range integral type conversion attempted") + } +} + +impl core::error::Error for TryFromIntError {} /// Provides metadata about a [`Value`]. #[derive(Debug, Copy, Clone, PartialEq)] From a41e0f0534eba4b9a6cbe3e18758f172d641d345 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:05:16 +0200 Subject: [PATCH 08/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/sensor.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/riot-rs-sensors/src/sensor.rs b/src/riot-rs-sensors/src/sensor.rs index 68669fe22..a653a38e7 100644 --- a/src/riot-rs-sensors/src/sensor.rs +++ b/src/riot-rs-sensors/src/sensor.rs @@ -34,8 +34,8 @@ pub trait Sensor: Send + Sync { /// /// # Errors /// - /// Returns [`MeasurementError::NonEnabled`] if the sensor driver is not enabled. - fn trigger_measurement(&self) -> Result<(), MeasurementError>; + /// Returns [`TriggerMeasurementError::NonEnabled`] if the sensor driver is not enabled. + fn trigger_measurement(&self) -> Result<(), TriggerMeasurementError>; /// Waits for the reading and returns it asynchronously. /// Depending on the sensor device and the sensor driver, this may use a sensor interrupt or @@ -59,8 +59,8 @@ pub trait Sensor: Send + Sync { /// /// # Errors /// - /// Returns [`ModeSettingError::Uninitialized`] if the sensor driver is not initialized. - fn set_mode(&self, mode: Mode) -> Result; + /// Returns [`SetModeError::Uninitialized`] if the sensor driver is not initialized. + fn set_mode(&self, mode: Mode) -> Result; /// Returns the current sensor driver state. #[must_use] @@ -148,13 +148,13 @@ pub enum Mode { /// Possible errors when attempting to set the mode of a sensor driver. #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum ModeSettingError { +pub enum SetModeError { /// The sensor driver is uninitialized. /// It has not been initialized yet, or initialization could not succeed. Uninitialized, } -impl core::fmt::Display for ModeSettingError { +impl core::fmt::Display for SetModeError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::Uninitialized => write!(f, "sensor driver is not initialized"), @@ -162,7 +162,7 @@ impl core::fmt::Display for ModeSettingError { } } -impl core::error::Error for ModeSettingError {} +impl core::error::Error for SetModeError {} /// State of a sensor driver. #[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] @@ -283,12 +283,12 @@ pub type AccuracyFn = fn(Value) -> Accuracy; /// Represents errors happening when *triggering* a sensor measurement. #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -pub enum MeasurementError { +pub enum TriggerMeasurementError { /// The sensor driver is not enabled (e.g., it may be disabled or sleeping). NonEnabled, } -impl core::fmt::Display for MeasurementError { +impl core::fmt::Display for TriggerMeasurementError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::NonEnabled => write!(f, "sensor driver is not enabled"), @@ -296,7 +296,7 @@ impl core::fmt::Display for MeasurementError { } } -impl core::error::Error for MeasurementError {} +impl core::error::Error for TriggerMeasurementError {} /// Represents errors happening when accessing a sensor reading. #[derive(Debug)] From 0b9ff53c9c5ab153233df449ed084af7071a6293 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:12:36 +0200 Subject: [PATCH 09/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/sensor.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/riot-rs-sensors/src/sensor.rs b/src/riot-rs-sensors/src/sensor.rs index a653a38e7..dad788f4d 100644 --- a/src/riot-rs-sensors/src/sensor.rs +++ b/src/riot-rs-sensors/src/sensor.rs @@ -25,8 +25,8 @@ pub trait Sensor: Send + Sync { /// /// To obtain readings from every sensor drivers this method can be called in a loop over all /// sensors returned by [`Registry::sensors()`](crate::registry::Registry::sensors), before - /// obtaining the readings with [`Self::wait_for_reading()`], so that the measurements happen - /// concurrently. + /// obtaining the readings with [`Self::wait_for_reading()`] in a second loop, so that the + /// measurements happen concurrently. /// /// # For implementors /// From d400d4d2937a0854017093074c890397932c5da8 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Fri, 18 Oct 2024 14:26:21 +0200 Subject: [PATCH 10/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/sensor.rs | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/riot-rs-sensors/src/sensor.rs b/src/riot-rs-sensors/src/sensor.rs index dad788f4d..931dfa670 100644 --- a/src/riot-rs-sensors/src/sensor.rs +++ b/src/riot-rs-sensors/src/sensor.rs @@ -40,13 +40,19 @@ pub trait Sensor: Send + Sync { /// Waits for the reading and returns it asynchronously. /// Depending on the sensor device and the sensor driver, this may use a sensor interrupt or /// data polling. - /// /// Interpretation of the reading requires data from [`Sensor::reading_axes()`] as well. /// See [the module level documentation](crate) for more. /// + /// # Note + /// + /// It is necessary to trigger a measurement by calling [`Sensor::trigger_measurement()`] + /// beforehand, even if the sensor device carries out periodic measurements on its own. + /// /// # Errors /// /// - Quickly returns [`ReadingError::NonEnabled`] if the sensor driver is not enabled. + /// - Quickly returns [`ReadingError::NotMeasuring`] if no measurement has been triggered + /// beforehand using [`Sensor::trigger_measurement()`]. /// - Returns [`ReadingError::SensorAccess`] if the sensor device cannot be accessed. fn wait_for_reading(&'static self) -> ReadingWaiter; @@ -177,8 +183,10 @@ pub enum State { Disabled = 1, /// The sensor driver is enabled. Enabled = 2, + /// The sensor driver is enabled and a measurement has been triggered. + Measuring = 3, /// The sensor driver is sleeping. - Sleeping = 3, + Sleeping = 4, } impl From for State { @@ -196,10 +204,11 @@ impl TryFrom for State { fn try_from(int: u8) -> Result { match int { - 0 => Ok(State::Uninitialized), - 1 => Ok(State::Disabled), - 2 => Ok(State::Enabled), - 3 => Ok(State::Sleeping), + 0 => Ok(Self::Uninitialized), + 1 => Ok(Self::Disabled), + 2 => Ok(Self::Enabled), + 3 => Ok(Self::Measuring), + 4 => Ok(Self::Sleeping), _ => Err(TryFromIntError), } } @@ -306,6 +315,10 @@ pub enum ReadingError { NonEnabled, /// Cannot access the sensor device (e.g., because of a bus error). SensorAccess, + /// No measurement has been triggered before waiting for a reading. + /// It is necessary to call [`Sensor::trigger_measurement()`] before calling + /// [`Sensor::wait_for_reading()`]. + NotMeasuring, } impl core::fmt::Display for ReadingError { @@ -313,6 +326,7 @@ impl core::fmt::Display for ReadingError { match self { Self::NonEnabled => write!(f, "sensor driver is not enabled"), Self::SensorAccess => write!(f, "sensor device could not be accessed"), + Self::NotMeasuring => write!(f, "no measurement has been triggered"), } } } From 848c07b764d3acab6ec8079403a95af87b16ce36 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:13:45 +0200 Subject: [PATCH 11/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/category.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/riot-rs-sensors/src/category.rs b/src/riot-rs-sensors/src/category.rs index d006ddc25..cbb781df7 100644 --- a/src/riot-rs-sensors/src/category.rs +++ b/src/riot-rs-sensors/src/category.rs @@ -1,6 +1,6 @@ -/// Categories a sensor can be part of. +/// Categories a sensor driver can be part of. /// -/// A sensor can be part of multiple categories. +/// A sensor driver can be part of multiple categories. /// /// # For sensor driver implementors /// From a23c4dc9adba518c63855fad196a1081b36f29ba Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:21:02 +0200 Subject: [PATCH 12/21] fixup! feat: introduce a sensor abstraction --- .../src/define_count_adjusted_sensor_enums.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/riot-rs-macros/src/define_count_adjusted_sensor_enums.rs b/src/riot-rs-macros/src/define_count_adjusted_sensor_enums.rs index 628b6adf6..2c933f68e 100644 --- a/src/riot-rs-macros/src/define_count_adjusted_sensor_enums.rs +++ b/src/riot-rs-macros/src/define_count_adjusted_sensor_enums.rs @@ -1,3 +1,9 @@ +/// Generates sensor-related enums whose number of variants needs to be adjusted based on Cargo +/// features, to accommodate the sensor driver returning the largest number of values. +/// +/// One single type must be defined so that it can be used in the Future returned by sensor +/// drivers, which must be the same for every sensor driver so it can be part of the `Sensor` +/// trait. #[proc_macro] pub fn define_count_adjusted_sensor_enums(_item: TokenStream) -> TokenStream { use quote::quote; From 50d027273ab4b426118ac03aff97e10077adb92e Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Fri, 18 Oct 2024 15:26:27 +0200 Subject: [PATCH 13/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/value.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/riot-rs-sensors/src/value.rs b/src/riot-rs-sensors/src/value.rs index c1790fce5..4b426fe00 100644 --- a/src/riot-rs-sensors/src/value.rs +++ b/src/riot-rs-sensors/src/value.rs @@ -70,7 +70,7 @@ pub enum Accuracy { /// /// ``` /// # use riot_rs_sensors::sensor::Accuracy; - /// Accuracy { + /// Accuracy::SymmetricalError { /// deviation: 25, /// bias: -20, /// scaling: -2, From 40e4f4944a49a75285d96227bf029f67cb033c7c Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:18:19 +0200 Subject: [PATCH 14/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/value.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/riot-rs-sensors/src/value.rs b/src/riot-rs-sensors/src/value.rs index 4b426fe00..c0f603c63 100644 --- a/src/riot-rs-sensors/src/value.rs +++ b/src/riot-rs-sensors/src/value.rs @@ -30,6 +30,8 @@ pub struct Value { impl Value { /// Creates a new value. + /// + /// This constructor is intended for sensor driver implementors only. #[must_use] pub const fn new(value: i32) -> Self { Self { value } From 74455ec7af14d87fb9ef50125e92b6288294e490 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:28:14 +0200 Subject: [PATCH 15/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/lib.rs | 2 +- src/riot-rs-sensors/src/sensor.rs | 20 ++++++++------------ src/riot-rs-sensors/src/value.rs | 5 ++--- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/riot-rs-sensors/src/lib.rs b/src/riot-rs-sensors/src/lib.rs index fed3cab49..f076c1766 100644 --- a/src/riot-rs-sensors/src/lib.rs +++ b/src/riot-rs-sensors/src/lib.rs @@ -38,7 +38,7 @@ //! sensor. //! Each [`ReadingAxis`](sensor::ReadingAxis) also provides information about the //! measurement accuracy, through -//! [`ReadingAxis::accuracy_fn()`](sensor::ReadingAxis::accuracy_fn). +//! [`ReadingAxis::accuracy()`](sensor::ReadingAxis::accuracy). //! //! To avoid handling floats, [`Value`](value::Value)s returned by [`Sensor::wait_for_reading()`] //! are integers, and a fixed scaling value is provided in [`ReadingAxis`](sensor::ReadingAxis), diff --git a/src/riot-rs-sensors/src/sensor.rs b/src/riot-rs-sensors/src/sensor.rs index 931dfa670..8b1f8faed 100644 --- a/src/riot-rs-sensors/src/sensor.rs +++ b/src/riot-rs-sensors/src/sensor.rs @@ -236,7 +236,7 @@ pub struct ReadingAxis { label: Label, scaling: i8, unit: MeasurementUnit, - accuracy: AccuracyFn, + accuracy: Accuracy, } impl ReadingAxis { @@ -244,7 +244,7 @@ impl ReadingAxis { /// /// This constructor is intended for sensor driver implementors only. #[must_use] - pub fn new(label: Label, scaling: i8, unit: MeasurementUnit, accuracy: AccuracyFn) -> Self { + pub fn new(label: Label, scaling: i8, unit: MeasurementUnit, accuracy: Accuracy) -> Self { Self { label, scaling, @@ -271,24 +271,20 @@ impl ReadingAxis { self.unit } - /// Returns a function allowing to obtain the accuracy error of a recently obtained - /// [`Value`]. + /// Returns the accuracy of the most recent reading obtained with + /// [`Sensor::wait_for_reading()`]. + /// Returns [`Accuracy::NoReading`] when no reading has been obtained yet. /// /// # Note /// - /// As the accuracy may depend on the sensor driver configuration, that accuracy function - /// should only be used for one [`Value`] instance, and it is necessary to obtain an - /// up-to-date function through an up-to-date [`ReadingAxis`]. + /// As the accuracy depends on the reading and also on other internal conditions, the accuracy + /// must be obtained anew for each reading. #[must_use] - pub fn accuracy_fn(&self) -> AccuracyFn { + pub fn accuracy(&self) -> Accuracy { self.accuracy } } -/// Function allowing to obtain the accuracy error of a [`Value`], returned by -/// [`ReadingAxis::accuracy_fn()`]. -pub type AccuracyFn = fn(Value) -> Accuracy; - /// Represents errors happening when *triggering* a sensor measurement. #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] diff --git a/src/riot-rs-sensors/src/value.rs b/src/riot-rs-sensors/src/value.rs index c0f603c63..f83646605 100644 --- a/src/riot-rs-sensors/src/value.rs +++ b/src/riot-rs-sensors/src/value.rs @@ -45,9 +45,6 @@ impl Value { } /// Specifies the accuracy of a measurement. -/// -/// The [`Accuracy`] should be obtained quickly after obtaining the [`Value`], as the -/// accuracy can be affected by a change in the internal state of the sensor driver. #[derive(Debug, Copy, Clone, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Accuracy { @@ -55,6 +52,8 @@ pub enum Accuracy { Unknown, /// No measurement error (e.g., boolean values from a push button). NoError, + /// No reading has been obtained yet. + NoReading, /// Measurement error symmetrical around the [`bias`](Accuracy::SymmetricalError::bias). /// /// The unit of measurement is provided by the [`ReadingAxis`](crate::sensor::ReadingAxis) From 1fe05fa02864c3531d1be098dea1238dc446a10e Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:53:37 +0200 Subject: [PATCH 16/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/lib.rs | 3 --- src/riot-rs-sensors/src/sensor.rs | 17 +------------- src/riot-rs-sensors/src/value.rs | 39 ++++++++++++++++++++++++------- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/riot-rs-sensors/src/lib.rs b/src/riot-rs-sensors/src/lib.rs index f076c1766..cd1530f21 100644 --- a/src/riot-rs-sensors/src/lib.rs +++ b/src/riot-rs-sensors/src/lib.rs @@ -36,9 +36,6 @@ //! to, using a [`Label`]. //! For instance, this allows to disambiguate the values provided by a temperature & humidity //! sensor. -//! Each [`ReadingAxis`](sensor::ReadingAxis) also provides information about the -//! measurement accuracy, through -//! [`ReadingAxis::accuracy()`](sensor::ReadingAxis::accuracy). //! //! To avoid handling floats, [`Value`](value::Value)s returned by [`Sensor::wait_for_reading()`] //! are integers, and a fixed scaling value is provided in [`ReadingAxis`](sensor::ReadingAxis), diff --git a/src/riot-rs-sensors/src/sensor.rs b/src/riot-rs-sensors/src/sensor.rs index 8b1f8faed..f344d076d 100644 --- a/src/riot-rs-sensors/src/sensor.rs +++ b/src/riot-rs-sensors/src/sensor.rs @@ -236,7 +236,6 @@ pub struct ReadingAxis { label: Label, scaling: i8, unit: MeasurementUnit, - accuracy: Accuracy, } impl ReadingAxis { @@ -244,12 +243,11 @@ impl ReadingAxis { /// /// This constructor is intended for sensor driver implementors only. #[must_use] - pub fn new(label: Label, scaling: i8, unit: MeasurementUnit, accuracy: Accuracy) -> Self { + pub fn new(label: Label, scaling: i8, unit: MeasurementUnit) -> Self { Self { label, scaling, unit, - accuracy, } } @@ -270,19 +268,6 @@ impl ReadingAxis { pub fn unit(&self) -> MeasurementUnit { self.unit } - - /// Returns the accuracy of the most recent reading obtained with - /// [`Sensor::wait_for_reading()`]. - /// Returns [`Accuracy::NoReading`] when no reading has been obtained yet. - /// - /// # Note - /// - /// As the accuracy depends on the reading and also on other internal conditions, the accuracy - /// must be obtained anew for each reading. - #[must_use] - pub fn accuracy(&self) -> Accuracy { - self.accuracy - } } /// Represents errors happening when *triggering* a sensor measurement. diff --git a/src/riot-rs-sensors/src/value.rs b/src/riot-rs-sensors/src/value.rs index f83646605..cc0ca2cb1 100644 --- a/src/riot-rs-sensors/src/value.rs +++ b/src/riot-rs-sensors/src/value.rs @@ -1,5 +1,5 @@ #[expect(clippy::doc_markdown)] -/// Represents a value obtained from a sensor device. +/// Represents a value obtained from a sensor device, along with its accuracy. /// /// # Scaling /// @@ -9,7 +9,7 @@ /// /// Value::value()·10scaling /// -/// For instance, in the case of a temperature sensor, if [`Value::get()`] returns `2225` and the +/// For instance, in the case of a temperature sensor, if [`Self::value()`] returns `2225` and the /// scaling value is `-2`, this means that the temperature measured and returned by the sensor /// device is `22.25` (the [measurement error](Accuracy) must additionally be taken into /// account). @@ -19,6 +19,10 @@ /// /// The unit of measurement can be obtained using /// [`ReadingAxis::unit()`](crate::sensor::ReadingAxis::unit). +/// +/// # Accuracy +/// +/// The accuracy can be obtained with [`Self::accuracy()`]. // NOTE(derive): we do not implement `Eq` or `PartialOrd` on purpose: `Eq` would prevent us from // possibly adding floats in the future and `PartialOrd` does not make sense because interpreting // the value requires the `ReadingAxis` associated with this `Value`. @@ -26,6 +30,7 @@ #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct Value { value: i32, + accuracy: Accuracy, } impl Value { @@ -33,15 +38,21 @@ impl Value { /// /// This constructor is intended for sensor driver implementors only. #[must_use] - pub const fn new(value: i32) -> Self { - Self { value } + pub const fn new(value: i32, accuracy: Accuracy) -> Self { + Self { value, accuracy } } /// Returns the value. #[must_use] - pub fn get(&self) -> i32 { + pub fn value(&self) -> i32 { self.value } + + /// Returns the measurement accuracy. + #[must_use] + pub fn accuracy(&self) -> Accuracy { + self.accuracy + } } /// Specifies the accuracy of a measurement. @@ -52,8 +63,6 @@ pub enum Accuracy { Unknown, /// No measurement error (e.g., boolean values from a push button). NoError, - /// No reading has been obtained yet. - NoReading, /// Measurement error symmetrical around the [`bias`](Accuracy::SymmetricalError::bias). /// /// The unit of measurement is provided by the [`ReadingAxis`](crate::sensor::ReadingAxis) @@ -80,9 +89,9 @@ pub enum Accuracy { /// ``` SymmetricalError { /// Deviation around the bias value. - deviation: i16, + deviation: i8, /// Bias (mean accuracy error). - bias: i16, + bias: i8, /// Scaling of [`deviation`](Accuracy::SymmetricalError::deviation) and /// [`bias`](Accuracy::SymmetricalError::bias). scaling: i8, @@ -107,3 +116,15 @@ pub trait Reading: core::fmt::Debug { [self.value()].into_iter() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn assert_type_sizes() { + assert!(size_of::() <= size_of::()); + // Make sure the type is small enough. + assert!(size_of::() <= 2 * size_of::()); + } +} From 4792e00cc144ad50f140c60a15b48a253c8a881d Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:41:06 +0200 Subject: [PATCH 17/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-rt/linkme.x | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/riot-rs-rt/linkme.x b/src/riot-rs-rt/linkme.x index 9a22004c0..099d975e0 100644 --- a/src/riot-rs-rt/linkme.x +++ b/src/riot-rs-rt/linkme.x @@ -5,6 +5,8 @@ SECTIONS { linkm2_EMBASSY_TASKS : { *(linkm2_EMBASSY_TASKS) } > FLASH linkme_USB_BUILDER_HOOKS : { *(linkme_USB_BUILDER_HOOKS) } > FLASH linkm2_USB_BUILDER_HOOKS : { *(linkm2_USB_BUILDER_HOOKS) } > FLASH + linkme_SENSOR_REFS : { *(linkme_SENSOR_REFS) } > FLASH + linkm2_SENSOR_REFS : { *(linkm2_SENSOR_REFS) } > FLASH linkme_THREAD_FNS : { *(linkme_THREAD_FNS) } > FLASH linkm2_THREAD_FNS : { *(linkm2_THREAD_FNS) } > FLASH } From df634e41ae5d21ba3b58b66d9a3971304c9e45ef Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:42:13 +0200 Subject: [PATCH 18/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/measurement_unit.rs | 104 +++++++++++++++++++- 1 file changed, 99 insertions(+), 5 deletions(-) diff --git a/src/riot-rs-sensors/src/measurement_unit.rs b/src/riot-rs-sensors/src/measurement_unit.rs index 8ad5ab32e..a30943486 100644 --- a/src/riot-rs-sensors/src/measurement_unit.rs +++ b/src/riot-rs-sensors/src/measurement_unit.rs @@ -6,7 +6,7 @@ /// Please open an issue to discuss it. // Built upon https://doc.riot-os.org/phydat_8h_source.html // and https://bthome.io/format/#sensor-data -// and https://www.rfc-editor.org/rfc/rfc8798.html +// and https://www.iana.org/assignments/senml/senml.xhtml #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] #[non_exhaustive] @@ -17,21 +17,115 @@ pub enum MeasurementUnit { ActiveOne, /// Value zero represents an active state (e.g., a push button being pressed). ActiveZero, + /// Ampere (A). + Ampere, + /// Becquerel (Bq). + Becquerel, /// Logic boolean. Bool, - /// Degree Celsius. + /// Candela (cd). + Candela, + /// Degrees Celsius (°C). Celsius, - /// Percent. + /// Coulomb (C). + Coulomb, + /// Decibel (dB). + Decibel, + /// Farad (F). + Farad, + // FIXME: Kilogram as well? + /// Gram (g). + Gram, + /// Gray (Gy). + Gray, + /// Henry (H). + Henry, + /// Hertz (Hz). + Hertz, + /// Joule (J). + Joule, + /// Katal (kat). + Katal, + /// Kelvin (K). + Kelvin, + /// Lumen (lm). + Lumen, + /// Lux (lx). + Lux, + /// Meter (m) + Meter, + /// Mole (mol). + Mole, + /// Newton (N). + Newton, + /// Ohm (Ω). + Ohm, + /// Pascal (Pa). + Pascal, + /// Percent (%). Percent, + /// %RH. + PercentageRelativeHumidity, + /// Radian (rad). + Radian, + /// Second (s). + Second, + /// Siemens (S). + Siemens, + /// Sievert (Sv). + Sievert, + /// Steradian (sr). + Steradian, + /// Tesla (T). + Tesla, + /// Volt (V). + Volt, + /// Watt (W). + Watt, + /// Weber (Wb). + Weber, } impl core::fmt::Display for MeasurementUnit { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + #[expect(clippy::match_same_arms)] match self { Self::AccelG => write!(f, "g"), - Self::ActiveOne | Self::ActiveZero | Self::Bool => write!(f, ""), + Self::ActiveOne => write!(f, ""), + Self::ActiveZero => write!(f, ""), + Self::Ampere => write!(f, "A"), + Self::Becquerel => write!(f, "Bq"), + Self::Bool => write!(f, ""), + Self::Candela => write!(f, "cd"), Self::Celsius => write!(f, "°C"), // The Unicode Standard v15 recommends using U+00B0 + U+0043. - Self::Percent => write!(f, "%"), // TODO: should we have a different unit for %RH? + Self::Coulomb => write!(f, "C"), + Self::Decibel => write!(f, "dB"), + Self::Farad => write!(f, "F"), + Self::Gram => write!(f, "g"), + Self::Gray => write!(f, "Gy"), + Self::Henry => write!(f, "H"), + Self::Hertz => write!(f, "Hz"), + Self::Joule => write!(f, "J"), + Self::Katal => write!(f, "kat"), + Self::Kelvin => write!(f, "K"), + Self::Lumen => write!(f, "lm"), + Self::Lux => write!(f, "lx"), + Self::Meter => write!(f, "m"), + Self::Mole => write!(f, "mol"), + Self::Newton => write!(f, "N"), + Self::Ohm => write!(f, "Ω"), + Self::Pascal => write!(f, "Pa"), + Self::Percent => write!(f, "%"), + Self::PercentageRelativeHumidity => write!(f, "%RH"), + Self::Radian => write!(f, "rad"), + Self::Second => write!(f, "s"), + Self::Siemens => write!(f, "S"), + Self::Sievert => write!(f, "Sv"), + Self::Steradian => write!(f, "sr"), + Self::Tesla => write!(f, "T"), + Self::Volt => write!(f, "V"), + Self::Watt => write!(f, "W"), + Self::Weber => write!(f, "Wb"), } } } From da6aea59e55ef51ab045e471e4852440203bae6a Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:03:38 +0200 Subject: [PATCH 19/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/category.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/riot-rs-sensors/src/category.rs b/src/riot-rs-sensors/src/category.rs index cbb781df7..fa6a5998b 100644 --- a/src/riot-rs-sensors/src/category.rs +++ b/src/riot-rs-sensors/src/category.rs @@ -13,12 +13,32 @@ pub enum Category { /// Accelerometer. Accelerometer, + /// Ammeter (ampere meter). + Ammeter, + /// CO₂ gas sensor. + Co2Gas, + /// Color sensor. + Color, + /// Gyroscope. + Gyroscope, /// Humidity sensor. Humidity, /// Humidity and temperature sensor. HumidityTemperature, + /// Light sensor. + Light, + /// Magnetometer. + Magnetometer, + /// pH sensor. + Ph, + /// Pressure sensor. + Pressure, /// Push button. PushButton, /// Temperature sensor. Temperature, + /// TVOC sensor. + Tvoc, + /// Voltage sensor. + Voltage, } From 6f2eff7955327437d5521ef1a46579ebb4e23e88 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Wed, 23 Oct 2024 09:59:13 +0200 Subject: [PATCH 20/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/category.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/riot-rs-sensors/src/category.rs b/src/riot-rs-sensors/src/category.rs index fa6a5998b..309ca41d1 100644 --- a/src/riot-rs-sensors/src/category.rs +++ b/src/riot-rs-sensors/src/category.rs @@ -4,6 +4,12 @@ /// /// # For sensor driver implementors /// +/// Many mechanical sensor devices (e.g., accelerometers) include a temperature sensor as +/// temperature may slightly affect the measurement results. +/// If temperature readings are not exposed by the sensor driver, the sensor driver must not be +/// considered part of a category that includes temperature ([`Category::Temperature`] or +/// [`Category::AccelerometerTemperature`] in the case of an accelerometer). +/// /// Missing variants can be added when required. /// Please open an issue to discuss it. // Built upon https://doc.riot-os.org/group__drivers__saul.html#ga8f2dfec7e99562dbe5d785467bb71bbb @@ -13,6 +19,10 @@ pub enum Category { /// Accelerometer. Accelerometer, + /// Accelerometer & temperature sensor. + AccelerometerTemperature, + /// Accelerometer & magnetometer & temperature sensor. + AccelerometerMagnetometerTemperature, /// Ammeter (ampere meter). Ammeter, /// CO₂ gas sensor. @@ -23,7 +33,7 @@ pub enum Category { Gyroscope, /// Humidity sensor. Humidity, - /// Humidity and temperature sensor. + /// Humidity & temperature sensor. HumidityTemperature, /// Light sensor. Light, From c471c39df13d68bdad2900d6d75ad9a7fd20ebe3 Mon Sep 17 00:00:00 2001 From: ROMemories <152802150+ROMemories@users.noreply.github.com> Date: Wed, 23 Oct 2024 10:26:40 +0200 Subject: [PATCH 21/21] fixup! feat: introduce a sensor abstraction --- src/riot-rs-sensors/src/measurement_unit.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/riot-rs-sensors/src/measurement_unit.rs b/src/riot-rs-sensors/src/measurement_unit.rs index a30943486..201c29c76 100644 --- a/src/riot-rs-sensors/src/measurement_unit.rs +++ b/src/riot-rs-sensors/src/measurement_unit.rs @@ -13,15 +13,11 @@ pub enum MeasurementUnit { /// [Acceleration *g*](https://en.wikipedia.org/wiki/G-force#Unit_and_measurement). AccelG, - /// Value one represents an active state (e.g., a push button being pressed). - ActiveOne, - /// Value zero represents an active state (e.g., a push button being pressed). - ActiveZero, /// Ampere (A). Ampere, /// Becquerel (Bq). Becquerel, - /// Logic boolean. + /// Logic boolean: `0` means `false` and `1` means `true`. Bool, /// Candela (cd). Candela, @@ -91,8 +87,6 @@ impl core::fmt::Display for MeasurementUnit { #[expect(clippy::match_same_arms)] match self { Self::AccelG => write!(f, "g"), - Self::ActiveOne => write!(f, ""), - Self::ActiveZero => write!(f, ""), Self::Ampere => write!(f, "A"), Self::Becquerel => write!(f, "Bq"), Self::Bool => write!(f, ""),