scd30_interface/data/
temperature_offset.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use byteorder::{BigEndian, ByteOrder};

use crate::{error::DataError, util::check_deserialization};

const MIN_TEMPERATURE_OFFSET: f32 = 0.0;
const MAX_TEMPERATURE_OFFSET: f32 = 0.1 * u16::MAX as f32;
const TEMPERATURE_OFFSET_VAL: &str = "Temperature offset";
const TEMPERATURE_UNIT: &str = "°C";

/// A runtime checked representation of the forced recalibration value. Accepted value range:
/// [0.0...6553.5] °C.
#[derive(Debug, PartialEq)]
pub struct TemperatureOffset(u16);

impl TemperatureOffset {
    /// Returns a big endian byte representation of the temperature offset.
    pub const fn to_be_bytes(&self) -> [u8; 2] {
        self.0.to_be_bytes()
    }
}

#[cfg(feature = "defmt")]
impl defmt::Format for TemperatureOffset {
    fn format(&self, f: defmt::Formatter) {
        defmt::write!(f, "{}°C", self.0 as f32 / 100.0)
    }
}

impl TryFrom<f32> for TemperatureOffset {
    type Error = DataError;

    /// Converts a f32 value to a [TemperatureOffset]. The value must be between 0.0 and
    /// 6553.5 in °C.
    ///
    /// # Errors
    ///
    /// - [ValueOutOfRange](crate::error::DataError::ValueOutOfRange) if `offset` is lower than 0.0 or higher than
    ///   6553.5 °C.
    fn try_from(offset: f32) -> Result<Self, Self::Error> {
        if !(MIN_TEMPERATURE_OFFSET..=MAX_TEMPERATURE_OFFSET).contains(&offset) {
            Err(DataError::ValueOutOfRange {
                parameter: TEMPERATURE_OFFSET_VAL,
                min: MIN_TEMPERATURE_OFFSET as u16,
                max: (MAX_TEMPERATURE_OFFSET * 100.0) as u16,
                unit: TEMPERATURE_UNIT,
            })
        } else {
            Ok(Self((offset * 100.0) as u16))
        }
    }
}

impl TryFrom<f64> for TemperatureOffset {
    type Error = DataError;

    /// Converts a f64 value to a [TemperatureOffset]. The value must be between 0.0 and
    /// 6553.5 in °C.
    ///
    /// # Errors
    ///
    /// - [ValueOutOfRange](crate::error::DataError::ValueOutOfRange) if `offset` is lower than 0.0 or higher than
    ///   6553.5 °C.
    fn try_from(offset: f64) -> Result<Self, Self::Error> {
        if MIN_TEMPERATURE_OFFSET as f64 > offset || offset > MAX_TEMPERATURE_OFFSET as f64 {
            Err(DataError::ValueOutOfRange {
                parameter: TEMPERATURE_OFFSET_VAL,
                min: MIN_TEMPERATURE_OFFSET as u16,
                max: (MAX_TEMPERATURE_OFFSET * 100.0) as u16,
                unit: TEMPERATURE_UNIT,
            })
        } else {
            Ok(Self((offset * 100.0) as u16))
        }
    }
}

impl TryFrom<&[u8]> for TemperatureOffset {
    type Error = DataError;

    /// Converts buffered data to a [TemperatureOffset] value.
    ///
    /// # Errors
    ///
    /// - [ReceivedBufferWrongSize](crate::error::DataError::ReceivedBufferWrongSize) if the `data` buffer is not big enough for the data
    ///   that should have been received.
    /// - [CrcFailed](crate::error::DataError::CrcFailed) if the CRC of the received data does not match.
    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
        check_deserialization(data, 3)?;
        Ok(Self(BigEndian::read_u16(&data[..2])))
    }
}

#[cfg(test)]
mod tests {
    use core::u16;

    use super::*;

    #[test]
    fn deserialize_sample_works() {
        let data = [0x01, 0xF4, 0x33];
        let offset = TemperatureOffset::try_from(&data[..]).unwrap();
        assert_eq!(offset, TemperatureOffset(500));
    }

    #[test]
    fn serialize_sample_works() {
        let offset = TemperatureOffset(500);
        assert_eq!(offset.to_be_bytes(), [0x01, 0xF4]);
    }

    #[test]
    fn create_allowed_value_from_f32_works() {
        let values = [(0.0f32, 0), (0.1, 10), (10.0, 1000), (6553.5, u16::MAX)];
        for (value, result) in values {
            assert_eq!(
                TemperatureOffset::try_from(value).unwrap(),
                TemperatureOffset(result)
            );
        }
    }
    #[test]
    fn create_allowed_value_from_f64_works() {
        let values = [(0.0, 0), (0.1, 10), (10.0, 1000), (6553.5, u16::MAX)];
        for (value, result) in values {
            assert_eq!(
                TemperatureOffset::try_from(value).unwrap(),
                TemperatureOffset(result)
            );
        }
    }

    #[test]
    fn create_from_f32_non_null_out_of_spec_value_errors() {
        let values = [-0.1f32, 6554.0];
        for value in values {
            assert_eq!(
                TemperatureOffset::try_from(value).unwrap_err(),
                DataError::ValueOutOfRange {
                    parameter: TEMPERATURE_OFFSET_VAL,
                    min: 0,
                    max: u16::MAX,
                    unit: TEMPERATURE_UNIT
                }
            );
        }
    }

    #[test]
    fn create_from_f64_non_null_out_of_spec_value_errors() {
        let values = [-0.1, 6554.0];
        for value in values {
            assert_eq!(
                TemperatureOffset::try_from(value).unwrap_err(),
                DataError::ValueOutOfRange {
                    parameter: TEMPERATURE_OFFSET_VAL,
                    min: 0,
                    max: u16::MAX,
                    unit: TEMPERATURE_UNIT
                }
            );
        }
    }
}