sen66_interface/data/
state.rs

1use crate::{
2    error::{DataError, DeviceError},
3    util::{check_deserialization, is_set},
4};
5
6/// Represents the state of the sensor.
7#[derive(Debug, PartialEq)]
8pub enum SensorState {
9    /// Sensor is in idle state. Either after power-on, a reset or when calling
10    /// [`stop_measurement`](crate::asynch::Sen66::stop_measurement).
11    Idle,
12    /// Sensor is in measuring state. Entered by calling
13    /// [`start_measurement`](crate::asynch::Sen66::start_measurement).
14    Measuring,
15}
16
17#[cfg(feature = "defmt")]
18impl defmt::Format for SensorState {
19    fn format(&self, f: defmt::Formatter) {
20        defmt::write!(f, "{}", self)
21    }
22}
23
24/// Sensor status register.
25#[derive(Debug, PartialEq)]
26pub struct DeviceStatusRegister(u32);
27
28impl DeviceStatusRegister {
29    /// Returns whether a fan speed warning is present, as the speed is off more than 10% for
30    /// multiple measurement intervals. Disappears if the issue disappears.
31    pub fn fan_speed_warning(&self) -> bool {
32        is_set(self.0, 21)
33    }
34
35    /// Returns whether the PM sensor exhibits an error.
36    /// <div class="warning">Persists even if the error disappears. Requires reseting the devices
37    /// status, the device or performing a power cycle.</div>
38    pub fn pm_sensor_error(&self) -> bool {
39        is_set(self.0, 11)
40    }
41
42    /// Returns whether the CO2 sensor exhibits an error.
43    /// <div class="warning">Persists even if the error disappears. Requires reseting the devices
44    /// status, the device or performing a power cycle.</div>
45    pub fn co2_sensor_error(&self) -> bool {
46        is_set(self.0, 9)
47    }
48
49    /// Returns whether the Gas sensor exhibits an error.
50    /// <div class="warning">Persists even if the error disappears. Requires reseting the devices
51    /// status, the device or performing a power cycle.</div>
52    pub fn gas_sensor_error(&self) -> bool {
53        is_set(self.0, 7)
54    }
55
56    /// Returns whether the RH/T sensor exhibits an error.
57    /// <div class="warning">Persists even if the error disappears. Requires reseting the devices
58    /// status, the device or performing a power cycle.</div>
59    pub fn rht_sensor_error(&self) -> bool {
60        is_set(self.0, 6)
61    }
62
63    /// Returns whether the fan exhibits an error: It is turned on, but 0RPM are reported over
64    /// multiple measurement intervals.
65    /// <div class="warning">Persists even if the error disappears. Requires reseting the devices
66    /// status, the device or performing a power cycle.</div>
67    pub fn fan_error(&self) -> bool {
68        is_set(self.0, 4)
69    }
70
71    /// Checks whether any error has occured
72    ///
73    /// # Errors
74    ///
75    /// - [`DeviceError`](crate::error::DeviceError): Returned when any error is present, flags
76    ///   indicate which errors are present.
77    pub fn has_error(&self) -> Result<(), DeviceError> {
78        let pm = self.pm_sensor_error();
79        let co2 = self.co2_sensor_error();
80        let gas = self.gas_sensor_error();
81        let rht = self.rht_sensor_error();
82        let fan = self.fan_error();
83        if [pm, co2, gas, rht, fan].iter().any(|&err| err) {
84            Err(DeviceError {
85                pm,
86                co2,
87                gas,
88                rht,
89                fan,
90            })
91        } else {
92            Ok(())
93        }
94    }
95}
96
97impl TryFrom<&[u8]> for DeviceStatusRegister {
98    type Error = DataError;
99
100    /// Parse the device status register from the received data.
101    ///
102    /// # Errors
103    ///
104    /// - [`CrcFailed`](crate::error::DataError::CrcFailed): If the received data CRC indicates
105    ///   corruption.
106    /// - [`ReceivedBufferWrongSize`](crate::error::DataError::ReceivedBufferWrongSize): If the
107    ///   received data buffer is not the expected size.
108    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
109        check_deserialization(data, 6)?;
110        Ok(DeviceStatusRegister(u32::from_be_bytes([
111            data[0], data[1], data[3], data[4],
112        ])))
113    }
114}
115
116/// Indicates whether automatic self calibration (ASC) is enabled.
117#[derive(Debug, PartialEq)]
118pub enum AscState {
119    /// ASC is enabled.
120    Enabled,
121    /// ASC is disabled.
122    Disabled,
123}
124
125impl TryFrom<&[u8]> for AscState {
126    type Error = DataError;
127
128    /// Parse the ASC state from the received data.
129    ///
130    /// # Errors
131    ///
132    /// - [`CrcFailed`](crate::error::DataError::CrcFailed): If the received data CRC indicates
133    ///   corruption.
134    /// - [`ReceivedBufferWrongSize`](crate::error::DataError::ReceivedBufferWrongSize): If the
135    ///   received data buffer is not the expected size.
136    /// - [UnexpectedValueReceived](crate::error::DataError::UnexpectedValueReceived) if the
137    ///   received value is not `0` or `1`.
138    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
139        check_deserialization(data, 3)?;
140        match data[1] {
141            0x00 => Ok(Self::Disabled),
142            0x01 => Ok(Self::Enabled),
143            val => Err(DataError::UnexpectedValueReceived {
144                parameter: "ASC State",
145                expected: "0 or 1",
146                actual: val as u16,
147            }),
148        }
149    }
150}
151
152impl From<AscState> for u16 {
153    fn from(value: AscState) -> Self {
154        match value {
155            AscState::Enabled => 0x0001,
156            AscState::Disabled => 0x0000,
157        }
158    }
159}
160
161/// Stores the VOC algorithm state, which can be used to skip the learning phase after a power
162/// cycle.
163#[derive(Debug, PartialEq)]
164pub struct VocAlgorithmState([u8; 8]);
165
166impl TryFrom<&[u8]> for VocAlgorithmState {
167    type Error = DataError;
168
169    /// Parse the VOC algorithm state from the received data.
170    ///
171    /// # Errors
172    ///
173    /// - [`CrcFailed`](crate::error::DataError::CrcFailed): If the received data CRC indicates
174    ///   corruption.
175    /// - [`ReceivedBufferWrongSize`](crate::error::DataError::ReceivedBufferWrongSize): If the
176    ///   received data buffer is not the expected size.
177    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
178        check_deserialization(data, 12)?;
179        Ok(VocAlgorithmState([
180            data[0], data[1], data[3], data[4], data[6], data[7], data[9], data[10],
181        ]))
182    }
183}
184
185impl From<VocAlgorithmState> for [u16; 4] {
186    fn from(value: VocAlgorithmState) -> Self {
187        [
188            u16::from_be_bytes([value.0[0], value.0[1]]),
189            u16::from_be_bytes([value.0[2], value.0[3]]),
190            u16::from_be_bytes([value.0[4], value.0[5]]),
191            u16::from_be_bytes([value.0[6], value.0[7]]),
192        ]
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199
200    #[test]
201    fn no_flags_set_nothing_reported() {
202        let state = DeviceStatusRegister(0b0000_0000_0000_0000_0000_0000_0000_0000);
203        assert!(!state.fan_speed_warning());
204        assert!(state.has_error().is_ok());
205    }
206
207    #[test]
208    fn set_fan_speed_warning_reported() {
209        let state = DeviceStatusRegister(0b0000_0000_0010_0000_0000_0000_0000_0000);
210        assert!(state.fan_speed_warning());
211    }
212
213    #[test]
214    fn set_fan_speed_error_reported() {
215        let state = DeviceStatusRegister(0b0000_0000_0000_0000_0000_0000_0001_0000);
216        assert!(state.fan_error());
217    }
218
219    #[test]
220    fn set_rht_error_reported() {
221        let state = DeviceStatusRegister(0b0000_0000_0000_0000_0000_0000_0100_0000);
222        assert!(state.rht_sensor_error());
223    }
224
225    #[test]
226    fn set_gas_error_reported() {
227        let state = DeviceStatusRegister(0b0000_0000_0000_0000_0000_0000_1000_0000);
228        assert!(state.gas_sensor_error());
229    }
230
231    #[test]
232    fn set_co2_error_reported() {
233        let state = DeviceStatusRegister(0b0000_0000_0000_0000_0000_0010_0000_0000);
234        assert!(state.co2_sensor_error());
235    }
236
237    #[test]
238    fn set_pm_error_reported() {
239        let state = DeviceStatusRegister(0b0000_0000_0000_0000_0000_1000_0000_0000);
240        assert!(state.pm_sensor_error());
241    }
242
243    #[test]
244    fn set_warning_flag_does_not_emit_error() {
245        let state = DeviceStatusRegister(0b0000_0000_0010_0000_0000_0000_0000_0000);
246        assert!(state.has_error().is_ok());
247    }
248
249    #[test]
250    fn set_error_flag_does_emit_device_error() {
251        let state = DeviceStatusRegister(0b0000_0000_0000_0000_0000_1000_0000_0000);
252        assert_eq!(
253            state.has_error().unwrap_err(),
254            DeviceError {
255                pm: true,
256                co2: false,
257                gas: false,
258                rht: false,
259                fan: false
260            }
261        );
262    }
263
264    #[test]
265    fn deserialize_device_status_register_with_all_flags_set_yields_u32_with_flag_bits_one() {
266        let data = [0x00, 0x20, 0x07, 0x0E, 0xD0, 0xE8];
267        assert_eq!(
268            DeviceStatusRegister::try_from(&data[..]).unwrap(),
269            DeviceStatusRegister(0b0000_0000_0010_0000_0000_1110_1101_0000)
270        );
271    }
272
273    #[test]
274    fn deserialize_asc_status_enabled_yields_enabled() {
275        let data = [0x00, 0x01, 0xB0];
276        assert_eq!(AscState::try_from(&data[..]).unwrap(), AscState::Enabled);
277    }
278
279    #[test]
280    fn deserialize_asc_status_disabled_yields_enabled() {
281        let data = [0x00, 0x00, 0x81];
282        assert_eq!(AscState::try_from(&data[..]).unwrap(), AscState::Disabled);
283    }
284
285    #[test]
286    fn deserialize_asc_status_unknown_emit_error() {
287        let data = [0x00, 0x03, 0xd2];
288        assert!(AscState::try_from(&data[..]).is_err());
289    }
290
291    #[test]
292    fn serialize_asc_status_enabled_yields_one() {
293        assert_eq!(u16::from(AscState::Enabled), 0x0001);
294    }
295
296    #[test]
297    fn serialize_asc_status_disabled_yields_zero() {
298        assert_eq!(u16::from(AscState::Disabled), 0x0000);
299    }
300
301    #[test]
302    fn deserialize_voc_algorithm_state_yields_same_state() {
303        let data = [
304            0x01, 0x02, 0x17, 0x03, 0x04, 0x68, 0x05, 0x06, 0x50, 0x07, 0x08, 0x96,
305        ];
306        assert_eq!(
307            VocAlgorithmState::try_from(&data[..]).unwrap(),
308            VocAlgorithmState([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08])
309        );
310    }
311
312    #[test]
313    fn serialize_voc_algorithm_state_yields_same_state() {
314        assert_eq!(
315            <[u16; 4]>::from(VocAlgorithmState([
316                0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08
317            ])),
318            [0x0102, 0x0304, 0x0506, 0x0708]
319        );
320    }
321}