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:
+///
+///
+///
+/// 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:
+ ///
+ ///
+ ///
+ /// # 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 @@
///
///
///
-/// 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 @@
///
///
///
-/// 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, ""),