sdp8xx/
lib.rs

1//! A platform agnostic Rust driver for the Sensirion `SDP8xx` differential pressure sensor, based
2//! on the [`embedded-hal`](https://github.com/japaric/embedded-hal) traits.
3//! Heavily inspired by the [`sgp30 driver by Danilo Bergen`](https://github.com/dbrgn/sgp30-rs)
4//!
5//! ## The Device
6//!
7//! The Sensirion `SDP8xx` is a differential pressure sensor. It has an I2C interface.
8//!
9//! - [Datasheet](https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/8_Differential_Pressure/Datasheets/Sensirion_Differential_Pressure_Sensors_SDP8xx_Digital_Datasheet.pdf)
10//! - [Product Page](https://www.sensirion.com/en/flow-sensors/differential-pressure-sensors/sdp800-proven-and-improved/)
11//!
12//! ## Usage
13//!
14//! ### Instantiating
15//!
16//! Import this crate and an `embedded_hal` implementation, then instantiate
17//! the device:
18//!
19//! ```no_run
20//! use linux_embedded_hal as hal;
21//!
22//! use hal::{Delay, I2cdev};
23//! use sdp8xx::Sdp8xx;
24//!
25//! # fn main() {
26//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
27//! let address = 0x25;
28//! let mut sdp = Sdp8xx::new(dev, address, Delay);
29//! # }
30//! ```
31//!
32//! See [sdp8xx-rpi-test](https://github.com/barafael/sdp8xx-rpi-test) for an example on the raspberry pi.
33//!
34//! ### Fetching Device Information
35//!
36//! You can fetch the product id of your sensor:
37//!
38//! ```no_run
39//! use linux_embedded_hal as hal;
40//! use hal::{Delay, I2cdev};
41//! use sdp8xx::ProductIdentifier;
42//! use sdp8xx::Sdp8xx;
43//!
44//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
45//! let mut sdp = Sdp8xx::new(dev, 0x25, Delay);
46//! if let Ok(product_id) = sdp.read_product_id() {
47//!     println!("{:?}", product_id);
48//! } else {
49//!     eprintln!("Error during reading product ID.");
50//! }
51//! ```
52//!
53//! ### Fetching Some Data
54//!
55//! You can fetch the differential pressure:
56//!
57//! ```no_run
58//! use linux_embedded_hal as hal;
59//! use hal::{Delay, I2cdev};
60//! use sdp8xx::Sdp8xx;
61//!
62//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
63//! let mut sdp = Sdp8xx::new(dev, 0x25, Delay);
64//!
65//! let data = match sdp.trigger_differential_pressure_sample() {
66//!     Ok(d) => dbg!(d),
67//!     Err(_) => panic!(),
68//! };
69//! ```
70
71#![warn(unsafe_code)]
72#![warn(missing_docs)]
73#![cfg_attr(not(test), no_std)]
74
75#[cfg(test)]
76mod test;
77
78use crate::command::Command;
79pub use crate::sample::*;
80use core::convert::TryFrom;
81use core::marker::PhantomData;
82use embedded_hal::{delay::DelayNs, i2c::I2c};
83pub use product_info::ProductIdentifier;
84use states::{
85    ContinuousSamplingState, IdleState, SleepState, SleepToIdle, ToDifferentialPressureSampling,
86    ToIdle, ToMassflowSampling, ToSleep,
87};
88
89pub mod command;
90pub mod product_info;
91pub mod sample;
92pub mod states;
93
94/// All possible errors in this crate
95#[derive(Debug)]
96pub enum SdpError<I>
97where
98    I: I2c,
99{
100    /// Error from the underlying sensirion I2C device
101    Device(sensirion_i2c::i2c::Error<I>),
102    /// Wrong Buffer Size
103    InvalidBufferSize,
104    /// Invalid sensor variant
105    InvalidVariant,
106    /// Wake up sent while sensor was not in sleep state
107    WakeUpWhileNotSleeping,
108    /// Cannot wake up sensor
109    CannotWakeUp,
110    /// Sampling error
111    SampleError,
112    /// Buffer too small for requested operation
113    BufferTooSmall,
114}
115
116impl<I> From<sensirion_i2c::i2c::Error<I>> for SdpError<I>
117where
118    I: I2c,
119{
120    fn from(error: sensirion_i2c::i2c::Error<I>) -> Self {
121        SdpError::Device(error)
122    }
123}
124
125/// State of the `SDP8xx`
126#[derive(Debug)]
127pub struct Sdp8xx<I2C, D, State> {
128    /// The concrete I2C device implementation.
129    i2c: I2C,
130    /// The I2C device address.
131    address: u8,
132    /// The concrete Delay implementation.
133    delay: D,
134    /// The state of the sensor
135    state: PhantomData<State>,
136}
137
138impl<I, D> Sdp8xx<I, D, IdleState>
139where
140    I: I2c,
141    D: DelayNs,
142{
143    /// Create a new instance of the `SDP8xx` driver.
144    pub fn new(i2c: I, address: u8, delay: D) -> Self {
145        Self {
146            i2c,
147            address,
148            delay,
149            state: PhantomData::<IdleState>,
150        }
151    }
152
153    /// Destroy driver instance, return I2C bus instance.
154    pub fn release(self) -> I {
155        self.i2c
156    }
157
158    /// Write an I2C command to the sensor.
159    fn send_command(&mut self, command: Command) -> Result<(), SdpError<I>> {
160        sensirion_i2c::i2c::write_command_u16(&mut self.i2c, self.address, command.into())
161            .map_err(|e| SdpError::Device(sensirion_i2c::i2c::Error::I2cWrite(e)))
162    }
163
164    /// Return the product id of the `SDP8xx`
165    pub fn read_product_id(&mut self) -> Result<ProductIdentifier, SdpError<I>> {
166        let mut buf = [0; 18];
167        // Request product id
168        self.send_command(Command::ReadProductId0)?;
169        self.send_command(Command::ReadProductId1)?;
170
171        self.i2c
172            .read(self.address, &mut buf)
173            .map_err(|e| SdpError::Device(sensirion_i2c::i2c::Error::I2cRead(e)))?;
174
175        Ok(ProductIdentifier::from(buf))
176    }
177
178    /// Trigger a differential pressure read without clock stretching.
179    /// This function blocks for at least 60 milliseconds to await a result.
180    pub fn trigger_differential_pressure_sample(
181        &mut self,
182    ) -> Result<Sample<DifferentialPressure>, SdpError<I>> {
183        self.send_command(Command::TriggerDifferentialPressureRead)?;
184        self.delay.delay_ms(60);
185        let mut buffer = [0u8; 9];
186        sensirion_i2c::i2c::read_words_with_crc(&mut self.i2c, self.address, &mut buffer)?;
187        Sample::<DifferentialPressure>::try_from(buffer).map_err(|_| SdpError::SampleError)
188    }
189
190    /// Trigger a mass flow read without clock stretching.
191    /// This function blocks for at least 60 milliseconds to await a result.
192    pub fn trigger_mass_flow_sample(&mut self) -> Result<Sample<MassFlow>, SdpError<I>> {
193        self.send_command(Command::TriggerMassFlowRead)?;
194        self.delay.delay_ms(60);
195        let mut buffer = [0u8; 9];
196        sensirion_i2c::i2c::read_words_with_crc(&mut self.i2c, self.address, &mut buffer)?;
197        Sample::<MassFlow>::try_from(buffer).map_err(|_| SdpError::SampleError)
198    }
199
200    /// Trigger a differential pressure read with clock stretching.
201    /// This function blocks until the data becomes available (if clock stretching is supported).
202    pub fn trigger_differential_pressure_sample_sync(
203        &mut self,
204    ) -> Result<Sample<DifferentialPressure>, SdpError<I>> {
205        self.send_command(Command::TriggerDifferentialPressureReadSync)?;
206        let mut buffer = [0u8; 9];
207        sensirion_i2c::i2c::read_words_with_crc(&mut self.i2c, self.address, &mut buffer)?;
208        Sample::<DifferentialPressure>::try_from(buffer).map_err(|_| SdpError::SampleError)
209    }
210
211    /// Trigger a mass flow read with clock stretching.
212    /// This function blocks until the data becomes available (if clock stretching is supported).
213    pub fn trigger_mass_flow_sample_sync(&mut self) -> Result<Sample<MassFlow>, SdpError<I>> {
214        self.send_command(Command::TriggerMassFlowReadSync)?;
215        let mut buffer = [0u8; 9];
216        sensirion_i2c::i2c::read_words_with_crc(&mut self.i2c, self.address, &mut buffer)?;
217        Sample::<MassFlow>::try_from(buffer).map_err(|_| SdpError::SampleError)
218    }
219
220    /// Start sampling in continuous mode
221    pub fn start_sampling_differential_pressure(
222        mut self,
223        averaging: bool,
224    ) -> ToDifferentialPressureSampling<I, D> {
225        let command = if averaging {
226            Command::SampleDifferentialPressureAveraging
227        } else {
228            Command::SampleDifferentialPressureRaw
229        };
230        self.send_command(command)?;
231        Ok(Sdp8xx {
232            i2c: self.i2c,
233            address: self.address,
234            delay: self.delay,
235            state: PhantomData::<ContinuousSamplingState<DifferentialPressure>>,
236        })
237    }
238
239    /// Start sampling mass flow mode
240    pub fn start_sampling_mass_flow(mut self, averaging: bool) -> ToMassflowSampling<I, D> {
241        let command = if averaging {
242            Command::SampleMassFlowAveraging
243        } else {
244            Command::SampleMassFlowRaw
245        };
246        self.send_command(command)?;
247        Ok(Sdp8xx {
248            i2c: self.i2c,
249            address: self.address,
250            delay: self.delay,
251            state: PhantomData::<ContinuousSamplingState<MassFlow>>,
252        })
253    }
254
255    /// Enter the `SDP8xx` sleep state
256    pub fn go_to_sleep(mut self) -> ToSleep<I, D> {
257        self.send_command(Command::EnterSleepMode)?;
258        Ok(Sdp8xx {
259            i2c: self.i2c,
260            address: self.address,
261            delay: self.delay,
262            state: PhantomData::<SleepState>,
263        })
264    }
265}
266
267impl<I, D> Sdp8xx<I, D, SleepState>
268where
269    I: I2c,
270    D: DelayNs,
271{
272    /// Wake the sensor up from the sleep state
273    /// This function blocks for at least 2 milliseconds
274    pub fn wake_up(mut self) -> SleepToIdle<I, D> {
275        // TODO polling with timeout.
276        // Send wake up signal (not acked)
277        let _ = self.i2c.write(self.address, &[]);
278        self.delay.delay_ms(3);
279        if self.i2c.write(self.address, &[]).is_ok() {
280            return Err(SdpError::WakeUpWhileNotSleeping);
281        }
282        self.delay.delay_ms(3);
283        match self.i2c.write(self.address, &[]) {
284            Ok(_) => {}
285            Err(_) => return Err(SdpError::CannotWakeUp),
286        }
287        Ok(Sdp8xx {
288            i2c: self.i2c,
289            address: self.address,
290            delay: self.delay,
291            state: PhantomData::<IdleState>,
292        })
293    }
294
295    /// Wake the sensor up from the sleep state by polling the I2C bus
296    /// This function blocks for at least 2 milliseconds
297    pub fn wake_up_poll(mut self) -> SleepToIdle<I, D> {
298        // Send wake up signal (not acked)
299        if self.i2c.write(self.address, &[]).is_ok() {
300            return Err(SdpError::WakeUpWhileNotSleeping);
301        }
302        loop {
303            // TODO timeout
304            if self.i2c.write(self.address, &[]).is_ok() {
305                break;
306            }
307        }
308        Ok(Sdp8xx {
309            i2c: self.i2c,
310            address: self.address,
311            delay: self.delay,
312            state: PhantomData::<IdleState>,
313        })
314    }
315}
316
317impl<I, D, T> Sdp8xx<I, D, ContinuousSamplingState<T>>
318where
319    I: I2c,
320    D: DelayNs,
321{
322    /// Read a sample in continuous mode
323    pub fn read_continuous_sample(&mut self) -> Result<Sample<T>, SdpError<I>> {
324        let mut buffer = [0u8; 9];
325        // TODO rate limiting no faster than 0.5ms
326        sensirion_i2c::i2c::read_words_with_crc(&mut self.i2c, self.address, &mut buffer)?;
327        Sample::try_from(buffer).map_err(|_| SdpError::SampleError)
328    }
329
330    /// Stop sampling continuous mode
331    pub fn stop_sampling(mut self) -> ToIdle<I, D> {
332        let bytes: [u8; 2] = Command::StopContinuousMeasurement.into();
333        self.i2c
334            .write(self.address, &bytes)
335            .map_err(|e| SdpError::Device(sensirion_i2c::i2c::Error::I2cWrite(e)))?;
336        Ok(Sdp8xx {
337            i2c: self.i2c,
338            address: self.address,
339            delay: self.delay,
340            state: PhantomData::<IdleState>,
341        })
342    }
343}