scd30_interface/data/
temperature_offset.rs

1use byteorder::{BigEndian, ByteOrder};
2
3use crate::{error::DataError, util::check_deserialization};
4
5const MIN_TEMPERATURE_OFFSET: f32 = 0.0;
6const MAX_TEMPERATURE_OFFSET: f32 = 0.1 * u16::MAX as f32;
7const TEMPERATURE_OFFSET_VAL: &str = "Temperature offset";
8const TEMPERATURE_UNIT: &str = "°C";
9
10/// A runtime checked representation of the forced recalibration value. Accepted value range:
11/// [0.0...6553.5] °C.
12#[derive(Debug, PartialEq)]
13pub struct TemperatureOffset(u16);
14
15impl TemperatureOffset {
16    /// Returns a big endian byte representation of the temperature offset.
17    pub const fn to_be_bytes(&self) -> [u8; 2] {
18        self.0.to_be_bytes()
19    }
20}
21
22#[cfg(feature = "defmt")]
23impl defmt::Format for TemperatureOffset {
24    fn format(&self, f: defmt::Formatter) {
25        defmt::write!(f, "{}°C", self.0 as f32 / 100.0)
26    }
27}
28
29impl TryFrom<f32> for TemperatureOffset {
30    type Error = DataError;
31
32    /// Converts a f32 value to a [TemperatureOffset]. The value must be between 0.0 and
33    /// 6553.5 in °C.
34    ///
35    /// # Errors
36    ///
37    /// - [ValueOutOfRange](crate::error::DataError::ValueOutOfRange) if `offset` is lower than 0.0 or higher than
38    ///   6553.5 °C.
39    fn try_from(offset: f32) -> Result<Self, Self::Error> {
40        if !(MIN_TEMPERATURE_OFFSET..=MAX_TEMPERATURE_OFFSET).contains(&offset) {
41            Err(DataError::ValueOutOfRange {
42                parameter: TEMPERATURE_OFFSET_VAL,
43                min: MIN_TEMPERATURE_OFFSET as u16,
44                max: (MAX_TEMPERATURE_OFFSET * 100.0) as u16,
45                unit: TEMPERATURE_UNIT,
46            })
47        } else {
48            Ok(Self((offset * 100.0) as u16))
49        }
50    }
51}
52
53impl TryFrom<f64> for TemperatureOffset {
54    type Error = DataError;
55
56    /// Converts a f64 value to a [TemperatureOffset]. The value must be between 0.0 and
57    /// 6553.5 in °C.
58    ///
59    /// # Errors
60    ///
61    /// - [ValueOutOfRange](crate::error::DataError::ValueOutOfRange) if `offset` is lower than 0.0 or higher than
62    ///   6553.5 °C.
63    fn try_from(offset: f64) -> Result<Self, Self::Error> {
64        if MIN_TEMPERATURE_OFFSET as f64 > offset || offset > MAX_TEMPERATURE_OFFSET as f64 {
65            Err(DataError::ValueOutOfRange {
66                parameter: TEMPERATURE_OFFSET_VAL,
67                min: MIN_TEMPERATURE_OFFSET as u16,
68                max: (MAX_TEMPERATURE_OFFSET * 100.0) as u16,
69                unit: TEMPERATURE_UNIT,
70            })
71        } else {
72            Ok(Self((offset * 100.0) as u16))
73        }
74    }
75}
76
77impl TryFrom<&[u8]> for TemperatureOffset {
78    type Error = DataError;
79
80    /// Converts buffered data to a [TemperatureOffset] value.
81    ///
82    /// # Errors
83    ///
84    /// - [ReceivedBufferWrongSize](crate::error::DataError::ReceivedBufferWrongSize) if the `data` buffer is not big enough for the data
85    ///   that should have been received.
86    /// - [CrcFailed](crate::error::DataError::CrcFailed) if the CRC of the received data does not match.
87    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
88        check_deserialization(data, 3)?;
89        Ok(Self(BigEndian::read_u16(&data[..2])))
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use core::u16;
96
97    use super::*;
98
99    #[test]
100    fn deserialize_sample_works() {
101        let data = [0x01, 0xF4, 0x33];
102        let offset = TemperatureOffset::try_from(&data[..]).unwrap();
103        assert_eq!(offset, TemperatureOffset(500));
104    }
105
106    #[test]
107    fn serialize_sample_works() {
108        let offset = TemperatureOffset(500);
109        assert_eq!(offset.to_be_bytes(), [0x01, 0xF4]);
110    }
111
112    #[test]
113    fn create_allowed_value_from_f32_works() {
114        let values = [(0.0f32, 0), (0.1, 10), (10.0, 1000), (6553.5, u16::MAX)];
115        for (value, result) in values {
116            assert_eq!(
117                TemperatureOffset::try_from(value).unwrap(),
118                TemperatureOffset(result)
119            );
120        }
121    }
122    #[test]
123    fn create_allowed_value_from_f64_works() {
124        let values = [(0.0, 0), (0.1, 10), (10.0, 1000), (6553.5, u16::MAX)];
125        for (value, result) in values {
126            assert_eq!(
127                TemperatureOffset::try_from(value).unwrap(),
128                TemperatureOffset(result)
129            );
130        }
131    }
132
133    #[test]
134    fn create_from_f32_non_null_out_of_spec_value_errors() {
135        let values = [-0.1f32, 6554.0];
136        for value in values {
137            assert_eq!(
138                TemperatureOffset::try_from(value).unwrap_err(),
139                DataError::ValueOutOfRange {
140                    parameter: TEMPERATURE_OFFSET_VAL,
141                    min: 0,
142                    max: u16::MAX,
143                    unit: TEMPERATURE_UNIT
144                }
145            );
146        }
147    }
148
149    #[test]
150    fn create_from_f64_non_null_out_of_spec_value_errors() {
151        let values = [-0.1, 6554.0];
152        for value in values {
153            assert_eq!(
154                TemperatureOffset::try_from(value).unwrap_err(),
155                DataError::ValueOutOfRange {
156                    parameter: TEMPERATURE_OFFSET_VAL,
157                    min: 0,
158                    max: u16::MAX,
159                    unit: TEMPERATURE_UNIT
160                }
161            );
162        }
163    }
164}