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}