scd4x_rs/async/
sensor.rs

1// Copyright Claudio Mattera 2024.
2//
3// Distributed under the MIT License or the Apache 2.0 License at your option.
4// See the accompanying files License-MIT.txt and License-Apache-2.0.txt, or
5// online at
6// https://opensource.org/licenses/MIT
7// https://opensource.org/licenses/Apache-2.0
8
9//! Data types and functions for SCD4x sensor interface
10
11use core::marker::PhantomData;
12
13use log::debug;
14
15use embedded_hal_async::delay::DelayNs;
16use embedded_hal_async::i2c::I2c;
17
18use crate::constants::DEFAULT_ADDRESS;
19use crate::sample::Sample;
20use crate::Altitude;
21use crate::Co2;
22use crate::Error;
23use crate::Idle;
24use crate::Measuring;
25use crate::Pressure;
26use crate::State;
27use crate::Temperature;
28
29use super::commands;
30use super::Command;
31
32/// Interface to SCD4x sensor over I²C
33pub struct Scd4x<I2c, Delay, State> {
34    /// I²C device
35    i2c: I2c,
36
37    /// I²C address
38    address: u8,
39
40    /// Delay function
41    delay: Delay,
42
43    /// State for type-state pattern
44    _state: PhantomData<State>,
45}
46
47impl<I2C, D> Scd4x<I2C, D, Idle>
48where
49    I2C: I2c,
50    D: DelayNs,
51{
52    /// Create a new sensor using an I²C interface and a delay function using
53    /// the sensor's default address [`DEFAULT_ADDRESS`])
54    pub fn new(i2c: I2C, delay: D) -> Self {
55        Self::new_with_address(i2c, DEFAULT_ADDRESS, delay)
56    }
57
58    /// Create a new sensor using an I²C interface and a delay function using
59    /// a custom address
60    pub fn new_with_address(i2c: I2C, address: u8, delay: D) -> Self {
61        Self {
62            i2c,
63            address,
64            delay,
65            _state: PhantomData,
66        }
67    }
68
69    /// Start periodic measurement
70    ///
71    /// # Errors
72    ///
73    /// Return an error if it cannot communicate with the sensor.
74    pub async fn start_periodic_measurement(mut self) -> Result<Scd4x<I2C, D, Measuring>, Error> {
75        debug!("Send command 'start_periodic_measurement'");
76
77        commands::StartPeriodicMeasurement
78            .execute(self.address, &mut self.i2c, &mut self.delay, ())
79            .await?;
80
81        Ok(Scd4x {
82            i2c: self.i2c,
83            address: self.address,
84            delay: self.delay,
85            _state: PhantomData,
86        })
87    }
88
89    /// Set temperature offset
90    ///
91    /// # Errors
92    ///
93    /// Return an error if it cannot communicate with the sensor.
94    pub async fn set_temperature_offset(
95        &mut self,
96        temperature_offset: Temperature,
97    ) -> Result<(), Error> {
98        debug!("Send command 'set_temperature_offset'");
99
100        commands::SetTemperatureOffset
101            .execute(
102                self.address,
103                &mut self.i2c,
104                &mut self.delay,
105                temperature_offset,
106            )
107            .await
108    }
109
110    /// Get temperature offset
111    ///
112    /// # Errors
113    ///
114    /// Return an error if it cannot communicate with the sensor.
115    pub async fn get_temperature_offset(&mut self) -> Result<Temperature, Error> {
116        debug!("Send command 'get_temperature_offset'");
117
118        commands::GetTemperatureOffset
119            .execute(self.address, &mut self.i2c, &mut self.delay, ())
120            .await
121    }
122
123    /// Set sensor altitude
124    ///
125    /// # Errors
126    ///
127    /// Return an error if it cannot communicate with the sensor.
128    pub async fn set_sensor_altitude(&mut self, sensor_altitude: Altitude) -> Result<(), Error> {
129        debug!("Send command 'set_sensor_altitude'");
130
131        commands::SetSensorAltitude
132            .execute(
133                self.address,
134                &mut self.i2c,
135                &mut self.delay,
136                sensor_altitude,
137            )
138            .await
139    }
140
141    /// Get sensor altitude
142    ///
143    /// # Errors
144    ///
145    /// Return an error if it cannot communicate with the sensor.
146    pub async fn get_sensor_altitude(&mut self) -> Result<Altitude, Error> {
147        debug!("Send command 'get_sensor_altitude'");
148
149        commands::GetSensorAltitude
150            .execute(self.address, &mut self.i2c, &mut self.delay, ())
151            .await
152    }
153
154    /// Perform forced recalibration
155    ///
156    /// # Errors
157    ///
158    /// Return an error if it cannot communicate with the sensor.
159    pub async fn perform_forced_recalibration(&mut self, co2: Co2) -> Result<Option<Co2>, Error> {
160        debug!("Send command 'perform_forced_recalibration'");
161
162        commands::PerformForcedRecalibration
163            .execute(self.address, &mut self.i2c, &mut self.delay, co2)
164            .await
165    }
166
167    /// Set whether automatic self-calibration is enabled
168    ///
169    /// # Errors
170    ///
171    /// Return an error if it cannot communicate with the sensor.
172    pub async fn set_automatic_self_calibration_enabled(
173        &mut self,
174        enabled: bool,
175    ) -> Result<(), Error> {
176        debug!("Send command 'set_automatic_self_calibration_enabled'");
177
178        commands::SetAutomaticSelfCalibrationEnabled
179            .execute(self.address, &mut self.i2c, &mut self.delay, enabled)
180            .await
181    }
182
183    /// Query whether automatic self-calibration is enabled
184    ///
185    /// # Errors
186    ///
187    /// Return an error if it cannot communicate with the sensor.
188    pub async fn get_automatic_self_calibration_enabled(&mut self) -> Result<bool, Error> {
189        debug!("Send command 'get_automatic_self_calibration_enabled'");
190
191        commands::GetAutomaticSelfCalibrationEnabled
192            .execute(self.address, &mut self.i2c, &mut self.delay, ())
193            .await
194    }
195
196    /// Start low-power periodic measurement
197    ///
198    /// # Errors
199    ///
200    /// Return an error if it cannot communicate with the sensor.
201    pub async fn start_low_power_periodic_measurement(
202        mut self,
203    ) -> Result<Scd4x<I2C, D, Measuring>, Error> {
204        debug!("Send command 'start_low_power_periodic_measurement'");
205
206        commands::StartLowPowerPeriodicMeasurement
207            .execute(self.address, &mut self.i2c, &mut self.delay, ())
208            .await?;
209
210        Ok(Scd4x {
211            i2c: self.i2c,
212            address: self.address,
213            delay: self.delay,
214            _state: PhantomData,
215        })
216    }
217
218    /// Persist settings to EEPROM
219    ///
220    /// # Errors
221    ///
222    /// Return an error if it cannot communicate with the sensor.
223    pub async fn persist_settings(&mut self) -> Result<(), Error> {
224        debug!("Send command 'persist_settings'");
225
226        commands::PersistSettings
227            .execute(self.address, &mut self.i2c, &mut self.delay, ())
228            .await
229    }
230
231    /// Obtain the serial number
232    ///
233    /// # Errors
234    ///
235    /// Return an error if it cannot communicate with the sensor.
236    pub async fn get_serial_number(&mut self) -> Result<u64, Error> {
237        debug!("Send command 'get_serial_number'");
238
239        commands::GetSerialNumber
240            .execute(self.address, &mut self.i2c, &mut self.delay, ())
241            .await
242    }
243
244    /// Perform self-test
245    ///
246    /// # Errors
247    ///
248    /// Return an error if it cannot communicate with the sensor.
249    pub async fn perform_self_test(&mut self) -> Result<bool, Error> {
250        debug!("Send command 'perform_self_test'");
251
252        commands::PerformSelfTest
253            .execute(self.address, &mut self.i2c, &mut self.delay, ())
254            .await
255    }
256
257    /// Perform factory reset
258    ///
259    /// # Errors
260    ///
261    /// Return an error if it cannot communicate with the sensor.
262    pub async fn perform_factory_reset(&mut self) -> Result<(), Error> {
263        debug!("Send command 'perform_factory_reset'");
264
265        commands::PerformFactoryReset
266            .execute(self.address, &mut self.i2c, &mut self.delay, ())
267            .await
268    }
269
270    /// Reinitialize the sensor
271    ///
272    /// Send a soft-reset signal, obtain the calibration coefficients, and set
273    /// default sampling configuration.
274    ///
275    /// Note that the default sampling configuration disables measurement of
276    /// temperature, pressure and humidity.
277    ///
278    /// # Errors
279    ///
280    /// Return an error if it cannot communicate with the sensor.
281    pub async fn reinit(&mut self) -> Result<(), Error> {
282        debug!("Send command 'reinit'");
283
284        commands::Reinitialize
285            .execute(self.address, &mut self.i2c, &mut self.delay, ())
286            .await
287    }
288
289    /// Read a single-shot measurement
290    ///
291    /// # Errors
292    ///
293    /// Return an error if it cannot communicate with the sensor.
294    pub async fn measure_single_shot(mut self) -> Result<Scd4x<I2C, D, Measuring>, Error> {
295        debug!("Send command 'measure_single_shot'");
296
297        commands::MeasureSingleShot
298            .execute(self.address, &mut self.i2c, &mut self.delay, ())
299            .await?;
300
301        Ok(Scd4x {
302            i2c: self.i2c,
303            address: self.address,
304            delay: self.delay,
305            _state: PhantomData,
306        })
307    }
308
309    /// Read a single-shot measurement of humidity and temperature
310    ///
311    /// # Errors
312    ///
313    /// Return an error if it cannot communicate with the sensor.
314    pub async fn measure_single_shot_rht_only(mut self) -> Result<Scd4x<I2C, D, Measuring>, Error> {
315        debug!("Send command 'measure_single_shot_rht_only'");
316
317        commands::MeasureSingleShotRhtOnly
318            .execute(self.address, &mut self.i2c, &mut self.delay, ())
319            .await?;
320
321        Ok(Scd4x {
322            i2c: self.i2c,
323            address: self.address,
324            delay: self.delay,
325            _state: PhantomData,
326        })
327    }
328}
329
330impl<I2C, D> Scd4x<I2C, D, Measuring>
331where
332    I2C: I2c,
333    D: DelayNs,
334{
335    /// Create a new sensor in measuring state using an I²C interface and a
336    /// delay function using the sensor's default address [`DEFAULT_ADDRESS`])
337    pub fn new_in_measuring(i2c: I2C, delay: D) -> Self {
338        Self::new_in_measuring_with_address(i2c, DEFAULT_ADDRESS, delay)
339    }
340
341    /// Create a new sensor in measuring state  using an I²C interface and a
342    /// delay function
343    pub fn new_in_measuring_with_address(i2c: I2C, address: u8, delay: D) -> Self {
344        Self {
345            i2c,
346            address,
347            delay,
348            _state: PhantomData,
349        }
350    }
351
352    /// Read a measurement from the sensor
353    ///
354    /// # Errors
355    ///
356    /// Return an error if it cannot communicate with the sensor.
357    pub async fn read_measurement(&mut self) -> Result<Sample, Error> {
358        debug!("Send command 'read_measurement'");
359
360        commands::ReadMeasurement
361            .execute(self.address, &mut self.i2c, &mut self.delay, ())
362            .await
363    }
364
365    /// Query whether data is available to be read
366    ///
367    /// # Errors
368    ///
369    /// Return an error if it cannot communicate with the sensor.
370    pub async fn get_data_ready_status(&mut self) -> Result<bool, Error> {
371        debug!("Send command 'get_data_ready_status'");
372
373        commands::GetDataReadyStatus
374            .execute(self.address, &mut self.i2c, &mut self.delay, ())
375            .await
376    }
377}
378
379impl<I2C, D, S> Scd4x<I2C, D, S>
380where
381    I2C: I2c,
382    D: DelayNs,
383    S: State,
384{
385    /// Release the I²C interface
386    pub fn release(self) -> I2C {
387        self.i2c
388    }
389
390    /// Stop periodic measurement
391    ///
392    /// # Errors
393    ///
394    /// Return an error if it cannot communicate with the sensor.
395    pub async fn stop_periodic_measurement(mut self) -> Result<Scd4x<I2C, D, Idle>, Error> {
396        debug!("Send command 'stop_periodic_measurement'");
397
398        commands::StopPeriodicMeasurement
399            .execute(self.address, &mut self.i2c, &mut self.delay, ())
400            .await?;
401
402        Ok(Scd4x {
403            i2c: self.i2c,
404            address: self.address,
405            delay: self.delay,
406            _state: PhantomData,
407        })
408    }
409
410    /// Set ambient pressure
411    ///
412    /// # Errors
413    ///
414    /// Return an error if it cannot communicate with the sensor.
415    pub async fn set_ambient_pressure(&mut self, ambient_pressure: Pressure) -> Result<(), Error> {
416        debug!("Send command 'set_ambient_pressure'");
417
418        commands::SetAmbientPressure
419            .execute(
420                self.address,
421                &mut self.i2c,
422                &mut self.delay,
423                ambient_pressure,
424            )
425            .await
426    }
427}