sen66_interface/data/
measurement.rs

1use crate::{error::DataError, util::check_deserialization};
2
3/// One measurement taken from the SEN66. Use
4/// [`read_measured_values`](crate::asynch::Sen66::read_measured_values) to retrieve it.
5#[derive(Debug, PartialEq)]
6pub struct Measurement {
7    /// Mass concentration for PM1.0 in ug/m³.
8    pub pm1_0: f32,
9    /// Mass concentration for PM2.5 in ug/m³.
10    pub pm2_5: f32,
11    /// Mass concentration for PM4.0 in ug/m³.
12    pub pm4_0: f32,
13    /// Mass concentration for PM10.0 in ug/m³.
14    pub pm10_0: f32,
15    /// Relative Humidity in %.
16    pub relative_humidity: f32,
17    /// Temperature in °C.
18    pub temperature: f32,
19    /// VOC Index.
20    pub voc_index: f32,
21    /// NOx Index.
22    pub nox_index: f32,
23    /// CO2 concentration in ppm.
24    pub co2: u16,
25}
26
27impl TryFrom<&[u8]> for Measurement {
28    type Error = DataError;
29
30    /// Parse the measurement from the received data.
31    ///
32    /// # Errors
33    ///
34    /// - [`CrcFailed`](crate::error::DataError::CrcFailed): If the received data CRC indicates
35    ///   corruption.
36    /// - [`ReceivedBufferWrongSize`](crate::error::DataError::ReceivedBufferWrongSize): If the
37    ///   received data buffer is not the expected size.
38    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
39        check_deserialization(data, 27)?;
40        Ok(Self {
41            pm1_0: u16::from_be_bytes([data[0], data[1]]) as f32 / 10.,
42            pm2_5: u16::from_be_bytes([data[3], data[4]]) as f32 / 10.,
43            pm4_0: u16::from_be_bytes([data[6], data[7]]) as f32 / 10.,
44            pm10_0: u16::from_be_bytes([data[9], data[10]]) as f32 / 10.,
45            relative_humidity: i16::from_be_bytes([data[12], data[13]]) as f32 / 100.,
46            temperature: i16::from_be_bytes([data[15], data[16]]) as f32 / 200.,
47            voc_index: i16::from_be_bytes([data[18], data[19]]) as f32 / 10.,
48            nox_index: i16::from_be_bytes([data[21], data[22]]) as f32 / 10.,
49            co2: u16::from_be_bytes([data[24], data[25]]),
50        })
51    }
52}
53
54#[cfg(feature = "defmt")]
55impl defmt::Format for Measurement {
56    fn format(&self, f: defmt::Formatter) {
57        defmt::write!(
58            f,
59            "PM1.0:     {} ug/m³
60PM2.5:     {} ug/m³
61PM4.0:     {} ug/m³
62PM10.0:    {} ug/m³
63RH:        {} %
64Temp:      {} °C
65VOC Index: {} / 1
66NOx Index: {} / 100
67CO2:       {} ppm",
68            self.pm1_0,
69            self.pm2_5,
70            self.pm4_0,
71            self.pm10_0,
72            self.relative_humidity,
73            self.temperature,
74            self.voc_index,
75            self.nox_index,
76            self.co2
77        )
78    }
79}
80
81/// One raw measurement taken from the SEN66. Use
82/// [`read_measured_raw_values`](crate::asynch::Sen66::read_measured_raw_values) to retrieve it.
83#[derive(Debug, PartialEq)]
84pub struct RawMeasurement {
85    /// Relative Humidity in %.
86    pub relative_humidity: f32,
87    /// Temperature in °C.
88    pub temperature: f32,
89    /// VOC ticks without scale facot
90    pub voc: u16,
91    /// NOx ticks without scale facot
92    pub nox: u16,
93    /// Uninterpolated CO2 concentration in ppm, updated every 5 seconds.
94    pub co2: u16,
95}
96
97impl TryFrom<&[u8]> for RawMeasurement {
98    type Error = DataError;
99
100    /// Parse the raw measurement 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, 15)?;
110        Ok(Self {
111            relative_humidity: i16::from_be_bytes([data[0], data[1]]) as f32 / 100.,
112            temperature: i16::from_be_bytes([data[3], data[4]]) as f32 / 200.,
113            voc: u16::from_be_bytes([data[6], data[7]]),
114            nox: u16::from_be_bytes([data[9], data[10]]),
115            co2: u16::from_be_bytes([data[12], data[13]]),
116        })
117    }
118}
119
120#[cfg(feature = "defmt")]
121impl defmt::Format for RawMeasurement {
122    fn format(&self, f: defmt::Formatter) {
123        defmt::write!(
124            f,
125            "RH:     {} %
126Temp:   {} °C
127VOC:    {} ticks
128NOx:    {} ticks
129CO2:    {} ppm",
130            self.relative_humidity,
131            self.temperature,
132            self.voc,
133            self.nox,
134            self.co2
135        )
136    }
137}
138
139/// One concentration measurement taken from the SEN66. Use
140/// [`read_number_concentrations`](crate::asynch::Sen66::read_number_concentrations) to retrieve it.
141#[derive(Debug, PartialEq)]
142pub struct Concentrations {
143    /// PM0.5 concentration in particles/cm³
144    pub pm0_5: f32,
145    /// PM1.0 concentration in particles/cm³
146    pub pm1_0: f32,
147    /// PM2.5 concentration in particles/cm³
148    pub pm2_5: f32,
149    /// PM4.0 concentration in particles/cm³
150    pub pm4_0: f32,
151    /// PM10.0 concentration in particles/cm³
152    pub pm10_0: f32,
153}
154
155impl TryFrom<&[u8]> for Concentrations {
156    type Error = DataError;
157
158    /// Parse the concentration  measurement from the received data.
159    ///
160    /// # Errors
161    ///
162    /// - [`CrcFailed`](crate::error::DataError::CrcFailed): If the received data CRC indicates
163    ///   corruption.
164    /// - [`ReceivedBufferWrongSize`](crate::error::DataError::ReceivedBufferWrongSize): If the
165    ///   received data buffer is not the expected size.
166    fn try_from(data: &[u8]) -> Result<Self, Self::Error> {
167        check_deserialization(data, 15)?;
168        Ok(Self {
169            pm0_5: u16::from_be_bytes([data[0], data[1]]) as f32 / 10.,
170            pm1_0: u16::from_be_bytes([data[3], data[4]]) as f32 / 10.,
171            pm2_5: u16::from_be_bytes([data[6], data[7]]) as f32 / 10.,
172            pm4_0: u16::from_be_bytes([data[9], data[10]]) as f32 / 10.,
173            pm10_0: u16::from_be_bytes([data[12], data[13]]) as f32 / 10.,
174        })
175    }
176}
177
178#[cfg(feature = "defmt")]
179impl defmt::Format for Concentrations {
180    fn format(&self, f: defmt::Formatter) {
181        defmt::write!(
182            f,
183            "PM0.5:  {} p/cm³
184PM1.0:  {} p/cm³
185PM2.5:  {} p/cm³
186PM4.0:  {} p/cm³
187PM10.0: {} p/cm³",
188            self.pm0_5,
189            self.pm1_0,
190            self.pm2_5,
191            self.pm4_0,
192            self.pm10_0
193        )
194    }
195}