sps30/
lib.rs

1//! A platform agnostic driver to interface the Sensirion SPS30 (UART Particulate Matter Sensor)
2//!
3//! This driver was built using [`embedded-hal`] traits.
4//!  
5//!
6//! # References
7//!
8//! - [SPS30 data sheet][1]
9//!
10//! [1]: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/0_Datasheets/Particulate_Matter/Sensirion_PM_Sensors_SPS30_Datasheet.pdf
11
12#![deny(unsafe_code)]
13#![deny(missing_docs)]
14#![no_std]
15
16use arrayvec::ArrayVec;
17use core::convert::From;
18use ieee754::*;
19use nb::Error as nbError;
20use sensirion_hdlc::{decode, encode, HDLCError, SpecialChars};
21
22/// Max characters to read for a frame detection
23const MAX_BUFFER: usize = 600;
24
25/// Errors for this crate
26#[derive(Debug)]
27pub enum Error<E, F> {
28    /// Serial bus read error
29    SerialR(nb::Error<F>),
30    /// Serial bus write error
31    SerialW(E),
32    /// SHDLC decode error
33    SHDLC(HDLCError),
34    /// No valid frame read.
35    ///
36    /// Input function read more than 600 characters without seeing two 0x7e
37    InvalidFrame,
38    /// Result is empty
39    EmptyResult,
40    /// Checksum failed, after shdlc decode
41    ChecksumFailed,
42    /// Response is for another CommandType
43    InvalidRespose,
44    /// Device returned an Error (State field of MISO Frame is not 0)
45    StatusError,
46}
47
48impl<E, F> From<nbError<F>> for Error<E, F> {
49    fn from(f: nbError<F>) -> Self {
50        Error::SerialR(f)
51    }
52}
53
54/// Types of information device holds
55#[repr(u8)]
56pub enum DeviceInfo {
57    /// Product Name
58    ProductName = 1,
59    /// Article Code
60    ArticleCode = 2,
61    /// Serial Number
62    SerialNumber = 3,
63}
64
65/// Available commands
66#[repr(u8)]
67pub enum CommandType {
68    /// Start measurement
69    StartMeasurement = 0,
70    /// Stop measurement
71    StopMeasurement = 1,
72    ///  Read measurement
73    ReadMeasuredData = 3,
74    /// Read/Write Auto Cleaning Interval
75    ReadWriteAutoCleaningInterval = 0x80,
76    /// Start Fan Cleaning
77    StartFanCleaning = 0x56,
78    /// Device Information
79    DeviceInformation = 0xD0,
80    /// Reset
81    Reset = 0xD3,
82}
83
84/// Checksum implemented as per section 4.1 from spec
85fn compute_cksum(data: &[u8]) -> u8 {
86    let mut cksum: u8 = 0;
87    for &byte in data.iter() {
88        let val: u16 = cksum as u16 + byte as u16;
89        let lsb = val % 256;
90        cksum = lsb as u8;
91    }
92
93    255 - cksum
94}
95
96/// Sps30 driver
97#[derive(Debug, Default)]
98pub struct Sps30<SERIAL> {
99    /// The concrete Serial device implementation.
100    serial: SERIAL,
101}
102
103impl<SERIAL, E, F> Sps30<SERIAL>
104where
105    SERIAL: embedded_hal::blocking::serial::Write<u8, Error = E>
106        + embedded_hal::serial::Read<u8, Error = F>,
107{
108    /// Create new instance of the Sps30 device
109    pub fn new(serial: SERIAL) -> Self {
110        Sps30 { serial }
111    }
112
113    /// Send data through serial interface
114    fn send_uart_data(&mut self, data: &[u8]) -> Result<(), Error<E, F>> {
115        let s_chars = SpecialChars::default();
116        let output = encode(&data, s_chars).unwrap();
117        //extern crate std;
118        //std::println!("Write {:x?}", output);
119        self.serial.bwrite_all(&output).map_err(Error::SerialW)
120    }
121
122    /// Read from serial until two 0x7e are seen
123    ///
124    /// No more than MAX_BUFFER=600 u8 will be read
125    /// After a MISO Frame is received, result is SHDLC decoded
126    /// Checksum for decoded frame is verified
127    fn read_uart_data(&mut self) -> Result<ArrayVec<[u8; 1024]>, Error<E, F>> {
128        let mut output = ArrayVec::<[u8; 1024]>::new();
129
130        let mut seen = 0;
131        while seen != 2 {
132            let byte = self.serial.read();
133            match byte {
134                Ok(value) => {
135                    if value == 0x7e {
136                        seen += 1;
137                    }
138                    output.push(value);
139                }
140                Err(e) => {
141                    return Err(Error::from(e));
142                }
143            }
144            if output.len() > MAX_BUFFER {
145                return Err(Error::InvalidFrame);
146            }
147        }
148
149        match decode(&output, SpecialChars::default()) {
150            Ok(v) => {
151                if v[v.len() - 1] == compute_cksum(&v[..v.len() - 1]) {
152                    return Ok(v);
153                }
154
155                Err(Error::ChecksumFailed)
156            }
157            Err(e) => Err(Error::SHDLC(e)),
158        }
159    }
160
161    /// Perform checks on MISO Frame
162    ///  * lenght >=5
163    ///  * CMD must match sent MOSI Frame CMD
164    ///  * State should be 0 (No Error)
165    ///  * L(ength) must be valid
166    fn check_miso_frame<'a>(
167        &self,
168        data: &'a [u8],
169        cmd_type: CommandType,
170    ) -> Result<&'a [u8], Error<E, F>> {
171        if data.len() < 5 {
172            return Err(Error::InvalidRespose);
173        }
174
175        if data[1] != cmd_type as u8 {
176            return Err(Error::InvalidRespose);
177        }
178        if data[2] != 0 {
179            return Err(Error::StatusError);
180        }
181
182        if data[3] as usize != data.len() - 5 {
183            return Err(Error::InvalidRespose);
184        }
185
186        //extern crate std;
187        //std::println!("Read: {:x?}", &data);
188        Ok(data)
189    }
190
191    /// Start measuring
192    pub fn start_measurement(&mut self) -> Result<(), Error<E, F>> {
193        let mut output = ArrayVec::<[u8; 1024]>::new();
194        let cmd = [0x00, 0x00, 0x02, 0x01, 0x03];
195        for item in &cmd {
196            output.push(*item);
197        }
198        output.push(compute_cksum(&output));
199        self.send_uart_data(&output)?;
200
201        match self.read_uart_data() {
202            Ok(response) => self
203                .check_miso_frame(&response, CommandType::StartMeasurement)
204                .map(|_| ()),
205            Err(e) => Err(e),
206        }
207    }
208
209    /// Stop measuring
210    pub fn stop_measurement(&mut self) -> Result<(), Error<E, F>> {
211        let mut output = ArrayVec::<[u8; 1024]>::new();
212        let cmd = [0x00, 0x01, 0x00];
213        for item in &cmd {
214            output.push(*item);
215        }
216        output.push(compute_cksum(&output));
217        self.send_uart_data(&output)?;
218
219        match self.read_uart_data() {
220            Ok(response) => self
221                .check_miso_frame(&response, CommandType::StopMeasurement)
222                .map(|_| ()),
223            Err(e) => Err(e),
224        }
225    }
226
227    /// Read measuring
228    pub fn read_measurement(&mut self) -> Result<[f32; 10], Error<E, F>> {
229        let mut output = ArrayVec::<[u8; 1024]>::new();
230        let cmd = [0x00, 0x03, 0x00];
231        for item in &cmd {
232            output.push(*item);
233        }
234        output.push(compute_cksum(&cmd));
235        self.send_uart_data(&output)?;
236
237        let data = self.read_uart_data();
238
239        let mut res: [f32; 10] = [0.0; 10];
240        match data {
241            Ok(v) => match v.len() {
242                45 => {
243                    self.check_miso_frame(&v, CommandType::ReadMeasuredData)?;
244                    for i in 0..res.len() {
245                        let mut bits: u32 = 0;
246                        for &byte in v[4 + 4 * i..4 + 4 * (i + 1)].iter() {
247                            bits = (bits << 8) + byte as u32;
248                        }
249                        res[i] = Ieee754::from_bits(bits);
250                    }
251                    Ok(res)
252                }
253                5 => Err(Error::EmptyResult),
254                _ => Err(Error::InvalidFrame),
255            },
256            Err(e) => Err(e),
257        }
258    }
259
260    /// Read cleaning interval
261    pub fn read_cleaning_interval(&mut self) -> Result<u32, Error<E, F>> {
262        let mut output = ArrayVec::<[u8; 1024]>::new();
263        let cmd = [0x00, 0x80, 0x01, 0x00];
264        for item in &cmd {
265            output.push(*item);
266        }
267        output.push(compute_cksum(&output));
268        self.send_uart_data(&output)?;
269
270        match self.read_uart_data() {
271            Ok(response) => {
272                match self.check_miso_frame(&response, CommandType::ReadWriteAutoCleaningInterval) {
273                    Ok(v) => {
274                        if v[3] != 4 {
275                            return Err(Error::InvalidRespose);
276                        }
277
278                        let mut ret: u32 = 0;
279                        for &byte in v[4..8].iter() {
280                            ret = ret * 256 + byte as u32;
281                        }
282                        Ok(ret)
283                    }
284                    Err(e) => Err(e),
285                }
286            }
287            Err(e) => Err(e),
288        }
289    }
290
291    /// Write cleaning interval
292    pub fn write_cleaning_interval(&mut self, val: u32) -> Result<(), Error<E, F>> {
293        let mut output = ArrayVec::<[u8; 1024]>::new();
294        let cmd = [0x00, 0x80, 0x05, 0x00];
295        for item in &cmd {
296            output.push(*item);
297        }
298        for item in &val.to_be_bytes() {
299            output.push(*item);
300        }
301        output.push(compute_cksum(&output));
302        self.send_uart_data(&output)?;
303
304        match self.read_uart_data() {
305            Ok(response) => {
306                match self.check_miso_frame(&response, CommandType::ReadWriteAutoCleaningInterval) {
307                    Ok(v) => {
308                        if v[3] != 0 {
309                            return Err(Error::InvalidRespose);
310                        }
311                        Ok(())
312                    }
313                    Err(e) => Err(e),
314                }
315            }
316            Err(e) => Err(e),
317        }
318    }
319
320    /// Start fan cleaning
321    pub fn start_fan_cleaning(&mut self) -> Result<(), Error<E, F>> {
322        let mut output = ArrayVec::<[u8; 1024]>::new();
323        let cmd = [0x00, 0x56, 0x00];
324        for item in &cmd {
325            output.push(*item);
326        }
327        output.push(compute_cksum(&output));
328        self.send_uart_data(&output)?;
329
330        match self.read_uart_data() {
331            Ok(response) => self
332                .check_miso_frame(&response, CommandType::StartFanCleaning)
333                .map(|_| ()),
334            Err(e) => Err(e),
335        }
336    }
337
338    /// Get info
339    ///
340    /// Return a [u8;32] with info
341    pub fn device_info(&mut self, info: DeviceInfo) -> Result<[u8; 32], Error<E, F>> {
342        let mut output = ArrayVec::<[u8; 1024]>::new();
343        let cmd = [0x00, 0xD0, 0x01];
344        for item in &cmd {
345            output.push(*item);
346        }
347        output.push(info as u8);
348        output.push(compute_cksum(&output));
349        self.send_uart_data(&output)?;
350
351        match self.read_uart_data() {
352            Ok(response) => {
353                match self.check_miso_frame(&response, CommandType::DeviceInformation) {
354                    Ok(val) => {
355                        let mut ret: [u8; 32] = [0; 32];
356                        if val[3] < 33 {
357                            for i in 0..val[3] {
358                                ret[i as usize] = val[3 + i as usize];
359                            }
360                            return Ok(ret);
361                        }
362                        Err(Error::EmptyResult)
363                    }
364                    Err(e) => Err(e),
365                }
366            }
367            Err(e) => Err(e),
368        }
369    }
370
371    /// Reset device
372    ///
373    /// After calling this function, caller must sleep before issuing more commands
374    pub fn reset(&mut self) -> Result<(), Error<E, F>> {
375        let mut output = ArrayVec::<[u8; 1024]>::new();
376        let cmd = [0x00, 0xD3, 0x00];
377        for item in &cmd {
378            output.push(*item);
379        }
380        output.push(compute_cksum(&output));
381        self.send_uart_data(&output)?;
382
383        match self.read_uart_data() {
384            Ok(response) => self
385                .check_miso_frame(&response, CommandType::Reset)
386                .map(|_| ()),
387            Err(e) => Err(e),
388        }
389    }
390}
391
392#[cfg(test)]
393mod tests {
394    #[test]
395    fn it_works() {
396        assert_eq!(2 + 2, 4);
397    }
398}