pmsx003/
lib.rs

1#![no_std]
2
3use embedded_io::{Read, Write, ErrorType, ReadExactError};
4
5const CMD_FRAME_SIZE: usize = 7;
6const OUTPUT_FRAME_SIZE: usize = 32;
7const RESPONSE_FRAME_SIZE: usize = 8;
8const CHECKSUM_SIZE: usize = 2;
9
10type Response = [u8; RESPONSE_FRAME_SIZE];
11
12pub const MN1: u8 = 0x42;
13pub const MN2: u8 = 0x4D;
14const PASSIVE_MODE_RESPONSE: Response = [MN1, MN1, 0x00, 0x04, 0xE1, 0x00, 0x01, 0x74];
15const ACTIVE_MODE_RESPONSE: Response = [MN1, MN2, 0x00, 0x04, 0xE1, 0x01, 0x01, 0x75];
16const SLEEP_RESPONSE: Response = [MN1, MN2, 0x00, 0x04, 0xE4, 0x00, 0x01, 0x77];
17
18#[derive(Debug)]
19pub enum Error<E> {
20    Read(ReadExactError<E>),
21    Write(E),
22    ChecksumError,
23    IncorrectResponse,
24    NoResponse,
25}
26
27/// Sensor interface
28pub struct PmsX003Sensor<UART> {
29    uart: UART,
30}
31
32impl<UART> PmsX003Sensor<UART>
33where
34    UART: Read + Write + ErrorType,
35{
36    /// Creates a new sensor instance
37    /// * `uart` - UART implementing embedded-io Read + Write traits
38    pub fn new(uart: UART) -> Self {
39        Self { uart }
40    }
41
42    fn read_from_device<T: AsMut<[u8]>>(&mut self, mut buffer: T) -> Result<T, Error<UART::Error>> {
43        let buf = buffer.as_mut();
44        
45        // Find the magic numbers (0x42, 0x4D) at the start of a frame
46        let mut temp_buf = [0u8; 1];
47        loop {
48            // Read first magic number
49            loop {
50                match self.uart.read_exact(&mut temp_buf) {
51                    Ok(()) => {
52                        if temp_buf[0] == MN1 {
53                            break;
54                        }
55                    }
56                    Err(e) => return Err(Error::Read(e)),
57                }
58            }
59            
60            // Read second magic number
61            match self.uart.read_exact(&mut temp_buf) {
62                Ok(()) => {
63                    if temp_buf[0] == MN2 {
64                        // Found both magic numbers, set them in buffer and read the rest
65                        buf[0] = MN1;
66                        buf[1] = MN2;
67                        match self.uart.read_exact(&mut buf[2..]) {
68                            Ok(()) => break,
69                            Err(e) => return Err(Error::Read(e)),
70                        }
71                    }
72                    // If second byte wasn't MN2, continue looking for MN1
73                }
74                Err(e) => return Err(Error::Read(e)),
75            }
76        }
77        
78        Ok(buffer)
79    }
80
81    /// Reads sensor status. Blocks until status is available.
82    pub fn read(&mut self) -> Result<OutputFrame, Error<UART::Error>> {
83        OutputFrame::from_buffer(&self.read_from_device([0_u8; OUTPUT_FRAME_SIZE])?)
84    }
85
86    /// Sleep mode. May fail because of incorrect response because of race condition between response and air quality status
87    pub fn sleep(&mut self) -> Result<(), Error<UART::Error>> {
88        self.send_cmd(&create_command(0xe4, 0))?;
89        self.receive_response(SLEEP_RESPONSE)
90    }
91
92    pub fn wake(&mut self) -> Result<(), Error<UART::Error>> {
93        self.send_cmd(&create_command(0xe4, 1))
94    }
95
96    /// Passive mode - sensor reports air quality on request
97    pub fn passive(&mut self) -> Result<(), Error<UART::Error>> {
98        self.send_cmd(&create_command(0xe1, 0))?;
99        self.receive_response(PASSIVE_MODE_RESPONSE)
100    }
101
102    /// Active mode - sensor reports air quality continuously
103    pub fn active(&mut self) -> Result<(), Error<UART::Error>> {
104        self.send_cmd(&create_command(0xe1, 1))?;
105        self.receive_response(ACTIVE_MODE_RESPONSE)
106    }
107
108    /// Requests status in passive mode
109    pub fn request(&mut self) -> Result<(), Error<UART::Error>> {
110        self.send_cmd(&create_command(0xe2, 0))
111    }
112
113    fn send_cmd(&mut self, cmd: &[u8]) -> Result<(), Error<UART::Error>> {
114        match self.uart.write_all(cmd) {
115            Ok(()) => Ok(()),
116            Err(_) => Err(Error::NoResponse), // Simplify for now
117        }
118    }
119
120    fn receive_response(&mut self, expected_response: Response) -> Result<(), Error<UART::Error>> {
121        if self.read_from_device([0u8; RESPONSE_FRAME_SIZE])? != expected_response {
122            Err(Error::IncorrectResponse)
123        } else {
124            Ok(())
125        }
126    }
127}
128
129fn create_command(cmd: u8, data: u16) -> [u8; CMD_FRAME_SIZE] {
130    let mut buffer = [0_u8; CMD_FRAME_SIZE];
131    let mut offset = 0usize;
132
133    // Write magic numbers and command
134    buffer[offset] = MN1;
135    offset += 1;
136    buffer[offset] = MN2;
137    offset += 1;
138    buffer[offset] = cmd;
139    offset += 1;
140
141    // Write data as big-endian u16
142    let data_bytes = data.to_be_bytes();
143    buffer[offset..offset + 2].copy_from_slice(&data_bytes);
144    offset += 2;
145
146    // Calculate checksum
147    let checksum = buffer
148        .iter()
149        .take(CMD_FRAME_SIZE - CHECKSUM_SIZE)
150        .map(|b| *b as u16)
151        .sum::<u16>();
152
153    // Write checksum as big-endian u16
154    let checksum_bytes = checksum.to_be_bytes();
155    buffer[offset..offset + 2].copy_from_slice(&checksum_bytes);
156
157    buffer
158}
159
160/// Contains data reported by the sensor
161#[derive(Default, Debug)]
162pub struct OutputFrame {
163    pub start1: u8,
164    pub start2: u8,
165    pub frame_length: u16,
166    pub pm1_0: u16,
167    pub pm2_5: u16,
168    pub pm10: u16,
169    pub pm1_0_atm: u16,
170    pub pm2_5_atm: u16,
171    pub pm10_atm: u16,
172    pub beyond_0_3: u16,
173    pub beyond_0_5: u16,
174    pub beyond_1_0: u16,
175    pub beyond_2_5: u16,
176    pub beyond_5_0: u16,
177    pub beyond_10_0: u16,
178    pub reserved: u16,
179    pub check: u16,
180}
181
182impl OutputFrame {
183    pub fn from_buffer<E>(buffer: &[u8; OUTPUT_FRAME_SIZE]) -> Result<Self, Error<E>> {
184        let sum: usize = buffer
185            .iter()
186            .take(OUTPUT_FRAME_SIZE - CHECKSUM_SIZE)
187            .map(|e| *e as usize)
188            .sum();
189
190        let mut frame = OutputFrame::default();
191        let mut offset = 0usize;
192
193        // Read u8 values
194        frame.start1 = buffer[offset];
195        offset += 1;
196        frame.start2 = buffer[offset];
197        offset += 1;
198
199        // Read u16 values as big-endian
200        frame.frame_length = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
201        offset += 2;
202        frame.pm1_0 = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
203        offset += 2;
204        frame.pm2_5 = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
205        offset += 2;
206        frame.pm10 = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
207        offset += 2;
208        frame.pm1_0_atm = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
209        offset += 2;
210        frame.pm2_5_atm = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
211        offset += 2;
212        frame.pm10_atm = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
213        offset += 2;
214        frame.beyond_0_3 = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
215        offset += 2;
216        frame.beyond_0_5 = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
217        offset += 2;
218        frame.beyond_1_0 = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
219        offset += 2;
220        frame.beyond_2_5 = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
221        offset += 2;
222        frame.beyond_5_0 = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
223        offset += 2;
224        frame.beyond_10_0 = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
225        offset += 2;
226        frame.reserved = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
227        offset += 2;
228        frame.check = u16::from_be_bytes([buffer[offset], buffer[offset + 1]]);
229
230        if sum != frame.check as usize {
231            return Err(Error::ChecksumError);
232        }
233
234        Ok(frame)
235    }
236}
237
238