ariel_os_sensors/
sample.rs

1use crate::sensor::ReadingChannel;
2
3/// Errors returned when trying to interpret a sample.
4#[derive(Debug, Copy, Clone, PartialEq, Eq)]
5#[cfg_attr(feature = "defmt", derive(defmt::Format))]
6#[non_exhaustive]
7pub enum SampleError {
8    /// The channel is not available at the moment (e.g., part of the sensor is not ready yet).
9    TemporarilyUnavailable,
10    /// The channel is disabled by configuration.
11    ChannelDisabled,
12}
13
14impl core::fmt::Display for SampleError {
15    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
16        match self {
17            Self::TemporarilyUnavailable => write!(f, "sample is temporarily unavailable"),
18            Self::ChannelDisabled => write!(f, "channel is disabled"),
19        }
20    }
21}
22
23impl core::error::Error for SampleError {}
24
25#[expect(clippy::doc_markdown)]
26/// Represents a value obtained from a sensor device, along with its metadata.
27///
28/// # Scaling
29///
30/// The [scaling value](crate::sensor::ReadingChannel::scaling()) obtained from the sensor driver with
31/// [`Sensor::reading_channels()`](crate::Sensor::reading_channels) must be taken into account using the
32/// following formula:
33///
34/// <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><mrow><mi mathvariant="monospace">Sample::value()</mi></mrow><mo>·</mo><msup><mn>10</mn><mrow><mi mathvariant="monospace">scaling</mi></mrow></msup></math>
35///
36/// For instance, in the case of a temperature sensor, if [`Self::value()`] returns `2225` and the
37/// scaling value is `-2`, this means that the temperature measured and returned by the sensor
38/// device is `22.25` (the [measurement error](SampleMetadata) must additionally be taken into
39/// account).
40/// This is required to avoid handling floats.
41///
42/// # Unit of measurement
43///
44/// The unit of measurement can be obtained using
45/// [`ReadingChannel::unit()`](crate::sensor::ReadingChannel::unit).
46///
47/// # Accuracy
48///
49/// The accuracy can be obtained through [`Self::metadata()`].
50// NOTE(derive): we do not implement `Eq` or `PartialOrd` on purpose: `Eq` would prevent us from
51// possibly adding floats in the future and `PartialOrd` does not make sense because interpreting
52// the sample requires the `ReadingChannel` associated with this `Sample`.
53#[derive(Debug, Copy, Clone, PartialEq)]
54#[cfg_attr(feature = "defmt", derive(defmt::Format))]
55pub struct Sample {
56    value: i32,
57    metadata: SampleMetadata,
58}
59
60impl Sample {
61    /// Creates a new sample.
62    ///
63    /// This constructor is intended for sensor driver implementors only.
64    #[must_use]
65    pub const fn new(value: i32, metadata: SampleMetadata) -> Self {
66        Self { value, metadata }
67    }
68
69    /// Returns the sample value.
70    ///
71    /// # Errors
72    ///
73    /// [`SampleError`] is returned in case of error.
74    pub fn value(&self) -> Result<i32, SampleError> {
75        match self.metadata {
76            SampleMetadata::ChannelTemporarilyUnavailable => {
77                Err(SampleError::TemporarilyUnavailable)
78            }
79            SampleMetadata::ChannelDisabled => Err(SampleError::ChannelDisabled),
80            SampleMetadata::NoMeasurementError
81            | SampleMetadata::UnknownAccuracy
82            | SampleMetadata::SymmetricalError { .. } => Ok(self.value),
83        }
84    }
85
86    /// Returns the measurement metadata, including accuracy if available.
87    #[must_use]
88    pub fn metadata(&self) -> SampleMetadata {
89        self.metadata
90    }
91}
92
93/// Metadata associated with a [`Sample`].
94///
95/// Includes the measurement accuracy if available.
96#[derive(Debug, Copy, Clone, PartialEq)]
97#[cfg_attr(feature = "defmt", derive(defmt::Format))]
98pub enum SampleMetadata {
99    /// Unknown accuracy.
100    UnknownAccuracy,
101    /// No observational/measurement error (e.g., boolean values from a push button).
102    NoMeasurementError,
103    /// Measurement error symmetrical around the [`bias`](SampleMetadata::SymmetricalError::bias).
104    ///
105    /// The unit of measurement is provided by the [`ReadingChannel`](crate::sensor::ReadingChannel)
106    /// associated with the [`Sample`].
107    /// The `scaling` value is used for both `deviation` and `bias`.
108    /// The accuracy error is thus given by the following formulas:
109    ///
110    /// <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><mo>+</mo><mo>(</mo><mrow><mi mathvariant="monospace">bias</mi></mrow><mo>+</mo><mrow><mi mathvariant="monospace">deviation</mi></mrow><mo>)</mo><mo>·</mo><msup><mn>10</mn><mrow><mi mathvariant="monospace">scaling</mi></mrow></msup>/<mo>-</mo><mo>(</mo><mrow><mi mathvariant="monospace">bias</mi></mrow><mo>-</mo><mrow><mi mathvariant="monospace">deviation</mi></mrow><mo>)</mo><mo>·</mo><msup><mn>10</mn><mrow><mi mathvariant="monospace">scaling</mi></mrow></msup></math>
111    ///
112    /// # Examples
113    ///
114    /// The DS18B20 temperature sensor accuracy error is <mo>+</mo><mn>0.05</mn>/<mo>-</mo><mn>0.45</mn>
115    /// at 20 °C (see Figure 1 of its datasheet).
116    /// [`SampleMetadata`] would thus be the following:
117    ///
118    /// ```
119    /// # use ariel_os_sensors::sensor::SampleMetadata;
120    /// SampleMetadata::SymmetricalError {
121    ///     deviation: 25,
122    ///     bias: -20,
123    ///     scaling: -2,
124    /// }
125    /// # ;
126    /// ```
127    SymmetricalError {
128        /// Deviation around the bias value.
129        deviation: u8,
130        /// Bias (mean accuracy error).
131        bias: i8,
132        /// Scaling of [`deviation`](SampleMetadata::SymmetricalError::deviation) and
133        /// [`bias`](SampleMetadata::SymmetricalError::bias).
134        scaling: i8,
135    },
136    /// Indicates that the channel is temporarily unavailable.
137    /// This may happen when parts of a sensor are not ready yet, but data is already available for other channels.
138    ChannelTemporarilyUnavailable,
139    /// The channel is disabled by configuration.
140    ChannelDisabled,
141}
142
143/// Implemented on [`Samples`](crate::sensor::Samples), returned by
144/// [`Sensor::wait_for_reading()`](crate::Sensor::wait_for_reading).
145pub trait Reading: core::fmt::Debug {
146    /// Returns the first value returned by [`Reading::samples()`].
147    fn sample(&self) -> (ReadingChannel, Sample);
148
149    /// Returns an iterator over [`Sample`]s of a sensor reading.
150    ///
151    /// The order of [`Sample`]s is not significant, but is fixed.
152    ///
153    /// # For implementors
154    ///
155    /// The default implementation must be overridden on types containing multiple
156    /// [`Sample`]s.
157    fn samples(
158        &self,
159    ) -> impl ExactSizeIterator<Item = (ReadingChannel, Sample)> + core::iter::FusedIterator {
160        [self.sample()].into_iter()
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn assert_type_sizes() {
170        assert!(size_of::<SampleMetadata>() <= size_of::<u32>());
171        // Make sure the type is small enough.
172        assert!(size_of::<Sample>() <= 2 * size_of::<u32>());
173    }
174}