embedded_devices/devices/sensirion/sen60/
mod.rs

1//! The SEN60 is a particulate matter (PM) sensor from Sensition's SEN6x sensor module family.
2//!
3//! The SEN6x sensor module family is an air quality platform that combines critical parameters
4//! such as particulate matter, relative humidity, temperature, VOC, NOx and either CO2 or
5//! formaldehyde, all in one compact package.
6//!
7//! ## Usage (sync)
8//!
9//! ```rust
10//! # #[cfg(feature = "sync")] mod test {
11//! # fn test<I, D>(mut i2c: I, delay: D) -> Result<(), embedded_devices::devices::sensirion::sen60::TransportError<I::Error>>
12//! # where
13//! #   I: embedded_hal::i2c::I2c + embedded_hal::i2c::ErrorType,
14//! #   D: embedded_hal::delay::DelayNs
15//! # {
16//! use embedded_devices::devices::sensirion::sen60::{SEN60Sync, address::Address};
17//! use embedded_devices::sensor::ContinuousSensorSync;
18//! use uom::si::mass_concentration::microgram_per_cubic_meter;
19//!
20//! // Create and initialize the device
21//! let mut sen60 = SEN60Sync::new_i2c(delay, i2c, Address::Default);
22//! sen60.init()?;
23//! sen60.start_measuring()?;
24//!
25//! // [...] wait ~1h for PM results to stabilize
26//! // Read measurements
27//! let measurement = sen60.next_measurement()?;
28//! let pm1 = measurement.pm1_concentration.unwrap().get::<microgram_per_cubic_meter>();
29//! let pm2_5 = measurement.pm2_5_concentration.unwrap().get::<microgram_per_cubic_meter>();
30//! let pm4 = measurement.pm4_concentration.unwrap().get::<microgram_per_cubic_meter>();
31//! let pm10 = measurement.pm10_concentration.unwrap().get::<microgram_per_cubic_meter>();
32//! println!("Current measurement: {:?} µg/m³ PM1, {:?} µg/m³ PM2.5, {:?} µg/m³ PM4, {:?} µg/m³ PM10", pm1, pm2_5, pm4, pm10);
33//! # Ok(())
34//! # }
35//! # }
36//! ```
37//!
38//! ## Usage (async)
39//!
40//! ```rust
41//! # #[cfg(feature = "async")] mod test {
42//! # async fn test<I, D>(mut i2c: I, delay: D) -> Result<(), embedded_devices::devices::sensirion::sen60::TransportError<I::Error>>
43//! # where
44//! #   I: embedded_hal_async::i2c::I2c + embedded_hal_async::i2c::ErrorType,
45//! #   D: embedded_hal_async::delay::DelayNs
46//! # {
47//! use embedded_devices::devices::sensirion::sen60::{SEN60Async, address::Address};
48//! use embedded_devices::sensor::ContinuousSensorAsync;
49//! use uom::si::mass_concentration::microgram_per_cubic_meter;
50//!
51//! // Create and initialize the device
52//! let mut sen60 = SEN60Async::new_i2c(delay, i2c, Address::Default);
53//! sen60.init().await?;
54//! sen60.start_measuring().await?;
55//!
56//! // [...] wait ~1h for PM results to stabilize
57//! // Read measurements
58//! let measurement = sen60.next_measurement().await?;
59//! let pm1 = measurement.pm1_concentration.unwrap().get::<microgram_per_cubic_meter>();
60//! let pm2_5 = measurement.pm2_5_concentration.unwrap().get::<microgram_per_cubic_meter>();
61//! let pm4 = measurement.pm4_concentration.unwrap().get::<microgram_per_cubic_meter>();
62//! let pm10 = measurement.pm10_concentration.unwrap().get::<microgram_per_cubic_meter>();
63//! println!("Current measurement: {:?} µg/m³ PM1, {:?} µg/m³ PM2.5, {:?} µg/m³ PM4, {:?} µg/m³ PM10", pm1, pm2_5, pm4, pm10);
64//! # Ok(())
65//! # }
66//! # }
67//! ```
68
69use self::commands::{
70    DeviceReset, GetDataReady, ReadMeasuredValuesMassConcentrationOnly, StartContinuousMeasurement, StopMeasurement,
71};
72use commands::DataReadyStatus;
73use embedded_devices_derive::{forward_command_fns, sensor};
74use uom::si::f64::MassConcentration;
75
76use super::commands::Crc8Error;
77
78pub mod address;
79pub mod commands;
80
81/// Any CRC or Bus related error
82pub type TransportError<E> = embedded_interfaces::TransportError<Crc8Error, E>;
83
84/// Measurement data
85#[derive(Debug, embedded_devices_derive::Measurement)]
86pub struct Measurement {
87    /// PM1 concentration
88    #[measurement(Pm1Concentration)]
89    pub pm1_concentration: Option<MassConcentration>,
90    /// PM2.5 concentration
91    #[measurement(Pm2_5Concentration)]
92    pub pm2_5_concentration: Option<MassConcentration>,
93    /// PM4 concentration
94    #[measurement(Pm4Concentration)]
95    pub pm4_concentration: Option<MassConcentration>,
96    /// PM10 concentration
97    #[measurement(Pm10Concentration)]
98    pub pm10_concentration: Option<MassConcentration>,
99}
100
101/// The SEN60 is a particulate matter (PM) sensor from Sensition's SEN6x sensor module family.
102///
103/// For a full description and usage examples, refer to the [module documentation](self).
104#[maybe_async_cfg::maybe(
105    idents(
106        hal(sync = "embedded_hal", async = "embedded_hal_async"),
107        CommandInterface,
108        I2cDevice
109    ),
110    sync(feature = "sync"),
111    async(feature = "async")
112)]
113pub struct SEN60<D: hal::delay::DelayNs, I: embedded_interfaces::commands::CommandInterface> {
114    /// The delay provider
115    delay: D,
116    /// The interface to communicate with the device
117    interface: I,
118}
119
120pub trait SEN60Command {}
121
122#[maybe_async_cfg::maybe(
123    idents(hal(sync = "embedded_hal", async = "embedded_hal_async"), I2cDevice),
124    sync(feature = "sync"),
125    async(feature = "async")
126)]
127impl<D, I> SEN60<D, embedded_interfaces::i2c::I2cDevice<I, hal::i2c::SevenBitAddress>>
128where
129    I: hal::i2c::I2c<hal::i2c::SevenBitAddress> + hal::i2c::ErrorType,
130    D: hal::delay::DelayNs,
131{
132    /// Initializes a new device with the given address on the specified bus.
133    /// This consumes the I2C bus `I`.
134    ///
135    /// Before using this device, you should call the [`Self::init`] method which
136    /// initializes the device and ensures that it is working correctly.
137    #[inline]
138    pub fn new_i2c(delay: D, interface: I, address: self::address::Address) -> Self {
139        Self {
140            delay,
141            interface: embedded_interfaces::i2c::I2cDevice::new(interface, address.into()),
142        }
143    }
144}
145
146#[forward_command_fns]
147#[sensor(Pm1Concentration, Pm2_5Concentration, Pm4Concentration, Pm10Concentration)]
148#[maybe_async_cfg::maybe(
149    idents(
150        hal(sync = "embedded_hal", async = "embedded_hal_async"),
151        CommandInterface,
152        ResettableDevice
153    ),
154    sync(feature = "sync"),
155    async(feature = "async")
156)]
157impl<D: hal::delay::DelayNs, I: embedded_interfaces::commands::CommandInterface> SEN60<D, I> {
158    /// Initializes the sensor by stopping any ongoing measurement, and resetting the device.
159    pub async fn init(&mut self) -> Result<(), TransportError<I::BusError>> {
160        use crate::device::ResettableDevice;
161
162        // Datasheet specifies 100ms before I2C communication may be started
163        self.delay.delay_ms(100).await;
164        self.reset().await?;
165
166        Ok(())
167    }
168}
169
170#[maybe_async_cfg::maybe(
171    idents(
172        hal(sync = "embedded_hal", async = "embedded_hal_async"),
173        CommandInterface,
174        ResettableDevice
175    ),
176    sync(feature = "sync"),
177    async(feature = "async")
178)]
179impl<D: hal::delay::DelayNs, I: embedded_interfaces::commands::CommandInterface> crate::device::ResettableDevice
180    for SEN60<D, I>
181{
182    type Error = TransportError<I::BusError>;
183
184    /// Resets the sensor by stopping any ongoing measurement, and resetting the device.
185    async fn reset(&mut self) -> Result<(), Self::Error> {
186        // Try to stop measurement if it is ongoing, otherwise ignore
187        let _ = self.execute::<StopMeasurement>(()).await;
188        // Reset
189        self.execute::<DeviceReset>(()).await?;
190        Ok(())
191    }
192}
193
194#[maybe_async_cfg::maybe(
195    idents(
196        hal(sync = "embedded_hal", async = "embedded_hal_async"),
197        CommandInterface,
198        ContinuousSensor
199    ),
200    sync(feature = "sync"),
201    async(feature = "async")
202)]
203impl<D: hal::delay::DelayNs, I: embedded_interfaces::commands::CommandInterface> crate::sensor::ContinuousSensor
204    for SEN60<D, I>
205{
206    type Error = TransportError<I::BusError>;
207    type Measurement = Measurement;
208
209    /// Starts continuous measurement.
210    async fn start_measuring(&mut self) -> Result<(), Self::Error> {
211        self.execute::<StartContinuousMeasurement>(()).await?;
212        Ok(())
213    }
214
215    /// Stops continuous measurement.
216    async fn stop_measuring(&mut self) -> Result<(), Self::Error> {
217        self.execute::<StopMeasurement>(()).await?;
218        Ok(())
219    }
220
221    /// Expected amount of time between measurements in microseconds.
222    async fn measurement_interval_us(&mut self) -> Result<u32, Self::Error> {
223        Ok(1_000_000)
224    }
225
226    /// Returns the most recent measurement.
227    async fn current_measurement(&mut self) -> Result<Option<Self::Measurement>, Self::Error> {
228        let measurement = self.execute::<ReadMeasuredValuesMassConcentrationOnly>(()).await?;
229        Ok(Some(Measurement {
230            pm1_concentration: (measurement.read_raw_mass_concentration_pm1() != u16::MAX)
231                .then(|| measurement.read_mass_concentration_pm1()),
232            pm2_5_concentration: (measurement.read_raw_mass_concentration_pm2_5() != u16::MAX)
233                .then(|| measurement.read_mass_concentration_pm2_5()),
234            pm4_concentration: (measurement.read_raw_mass_concentration_pm4() != u16::MAX)
235                .then(|| measurement.read_mass_concentration_pm4()),
236            pm10_concentration: (measurement.read_raw_mass_concentration_pm10() != u16::MAX)
237                .then(|| measurement.read_mass_concentration_pm10()),
238        }))
239    }
240
241    /// Check if new measurements are available.
242    async fn is_measurement_ready(&mut self) -> Result<bool, Self::Error> {
243        Ok(self.execute::<GetDataReady>(()).await?.read_data_ready() == DataReadyStatus::Ready)
244    }
245
246    /// Wait indefinitely until new measurements are available and return them. Checks whether data
247    /// is ready in intervals of 100ms.
248    async fn next_measurement(&mut self) -> Result<Self::Measurement, Self::Error> {
249        loop {
250            if self.is_measurement_ready().await? {
251                return self.current_measurement().await?.ok_or_else(|| {
252                    TransportError::Unexpected("measurement was not ready even though we expected it to be")
253                });
254            }
255            self.delay.delay_ms(100).await;
256        }
257    }
258}