scd30_interface/
interface.rs

1use duplicate::duplicate_item;
2
3const ADDRESS: u8 = 0x61;
4const WRITE_FLAG: u8 = 0x00;
5const READ_FLAG: u8 = 0x01;
6
7// `await` replacement needs to be a callable due to the dot notation. This tricks enables that
8// use case.
9#[cfg(not(tarpaulin_include))]
10trait Identity: Sized {
11    fn identity(self) -> Self {
12        core::convert::identity(self)
13    }
14}
15
16impl<T: Sized> Identity for T {}
17
18#[duplicate_item(
19    feature_        module      async   await               i2c_trait                                       test_macro;
20    ["blocking"]    [blocking]  []      [identity()]        [embedded_hal::i2c::I2c<Error = I2cErr>]        [test];
21    ["async"]       [asynch]    [async] [await.identity()]  [embedded_hal_async::i2c::I2c<Error = I2cErr>]  [tokio::test];
22)]
23pub mod module {
24    //! Implementation of the SCD30's interface
25
26    #[cfg(feature=feature_)]
27    mod inner {
28        use crate::{
29            command::Command,
30            data::{
31                AltitudeCompensation, AmbientPressureCompensation, AutomaticSelfCalibration,
32                DataStatus, FirmwareVersion, ForcedRecalibrationValue, Measurement,
33                MeasurementInterval, TemperatureOffset,
34            },
35            error::Scd30Error,
36            interface::{Identity, ADDRESS, READ_FLAG, WRITE_FLAG},
37            util::compute_crc8,
38        };
39
40        /// Interface for the [SCD30 CO2 sensor by Sensirion](https://sensirion.com/products/catalog/SCD30).
41        pub struct Scd30<I2C> {
42            i2c: I2C,
43        }
44
45        impl<I2C: i2c_trait, I2cErr: embedded_hal::i2c::Error> Scd30<I2C> {
46            /// Create a new SCD30 interface.
47            pub fn new(i2c: I2C) -> Self {
48                Self { i2c }
49            }
50
51            /// Start continuous measurements.
52            /// This is stored in non-volatile memory. After power-cycling the device, it will continue
53            /// measuring without being send a measurement command.
54            /// Additionally an AmbientPressure value can be send, to compensate for ambient pressure.
55            /// Default ambient pressure is 1013.25 mBar, can be configured in the range of 700 mBar to
56            /// 1400 mBar.
57            pub async fn trigger_continuous_measurements(
58                &mut self,
59                pressure_compensation: Option<AmbientPressureCompensation>,
60            ) -> Result<(), Scd30Error<I2cErr>> {
61                let data = match pressure_compensation {
62                    None => [0x0, 0x0],
63                    Some(pres) => pres.to_be_bytes(),
64                };
65                self.write(Command::TriggerContinuousMeasurement, Some(&data))
66                    .await
67            }
68
69            /// Stop continuous measurements.
70            pub async fn stop_continuous_measurements(&mut self) -> Result<(), Scd30Error<I2cErr>> {
71                self.write(Command::StopContinuousMeasurement, None).await
72            }
73
74            /// Configures the measurement interval in seconds, ranging from to 2s to 1800s.
75            pub async fn set_measurement_interval(
76                &mut self,
77                interval: MeasurementInterval,
78            ) -> Result<(), Scd30Error<I2cErr>> {
79                self.write(
80                    Command::SetMeasurementInterval,
81                    Some(&interval.to_be_bytes()),
82                )
83                .await
84            }
85
86            /// Reads out the configured continuous measurement interval
87            pub async fn get_measurement_interval(
88                &mut self,
89            ) -> Result<MeasurementInterval, Scd30Error<I2cErr>> {
90                let receive = self.read::<3>(Command::SetMeasurementInterval).await?;
91                Ok(MeasurementInterval::try_from(&receive[..])?)
92            }
93
94            /// Checks whether a measurement is ready for readout.
95            pub async fn is_data_ready(&mut self) -> Result<DataStatus, Scd30Error<I2cErr>> {
96                let receive = self.read::<3>(Command::GetDataReady).await?;
97                Ok(DataStatus::try_from(&receive[..])?)
98            }
99
100            /// Reads out a [Measurement](crate::data::Measurement) from the sensor.
101            pub async fn read_measurement(&mut self) -> Result<Measurement, Scd30Error<I2cErr>> {
102                let receive = self.read::<18>(Command::ReadMeasurement).await?;
103                Ok(Measurement::try_from(&receive[..])?)
104            }
105
106            /// Activates or deactivates automatic self-calibration.
107            pub async fn set_automatic_self_calibration(
108                &mut self,
109                setting: AutomaticSelfCalibration,
110            ) -> Result<(), Scd30Error<I2cErr>> {
111                self.write(
112                    Command::ActivateAutomaticSelfCalibration,
113                    Some(&setting.to_be_bytes()),
114                )
115                .await
116            }
117
118            /// Reads out the current state of the automatic self-calibration.
119            pub async fn get_automatic_self_calibration(
120                &mut self,
121            ) -> Result<AutomaticSelfCalibration, Scd30Error<I2cErr>> {
122                let receive = self
123                    .read::<3>(Command::ActivateAutomaticSelfCalibration)
124                    .await?;
125                Ok(AutomaticSelfCalibration::try_from(&receive[..])?)
126            }
127
128            /// Configures the forced re-calibration (FRC) value to compensate for sensor drift. The value
129            /// can range from 400 ppm to 2000 ppm.
130            pub async fn set_forced_recalibration(
131                &mut self,
132                frc: ForcedRecalibrationValue,
133            ) -> Result<(), Scd30Error<I2cErr>> {
134                self.write(Command::ForcedRecalibrationValue, Some(&frc.to_be_bytes()))
135                    .await
136            }
137
138            /// Reads out the configured value of the forced re-calibration (FRC) value.
139            pub async fn get_forced_recalibration(
140                &mut self,
141            ) -> Result<ForcedRecalibrationValue, Scd30Error<I2cErr>> {
142                let receive = self.read::<3>(Command::ForcedRecalibrationValue).await?;
143                Ok(ForcedRecalibrationValue::try_from(&receive[..])?)
144            }
145
146            /// Configures the temperature offset to compensate for self-heating electric components. The
147            /// value can range from 0.0 °C to 6553.5 °C.
148            pub async fn set_temperature_offset(
149                &mut self,
150                offset: TemperatureOffset,
151            ) -> Result<(), Scd30Error<I2cErr>> {
152                self.write(Command::SetTemperatureOffset, Some(&offset.to_be_bytes()))
153                    .await
154            }
155
156            /// Reads out the configured temperature offset.
157            pub async fn get_temperature_offset(
158                &mut self,
159            ) -> Result<TemperatureOffset, Scd30Error<I2cErr>> {
160                let receive = self.read::<3>(Command::SetTemperatureOffset).await?;
161                Ok(TemperatureOffset::try_from(&receive[..])?)
162            }
163
164            /// Configures the altitude compensation. The value can range from 0 m to 65535 m above sea
165            /// level.
166            pub async fn set_altitude_compensation(
167                &mut self,
168                altitude: AltitudeCompensation,
169            ) -> Result<(), Scd30Error<I2cErr>> {
170                self.write(
171                    Command::SetAltitudeCompensation,
172                    Some(&altitude.to_be_bytes()),
173                )
174                .await
175            }
176
177            /// Reads out the configured altitude compensation.
178            pub async fn get_altitude_compensation(
179                &mut self,
180            ) -> Result<AltitudeCompensation, Scd30Error<I2cErr>> {
181                let receive = self.read::<3>(Command::SetAltitudeCompensation).await?;
182                Ok(AltitudeCompensation::try_from(&receive[..])?)
183            }
184
185            /// Reads out the version of the firmware deployed on the sensor.
186            pub async fn read_firmware_version(
187                &mut self,
188            ) -> Result<FirmwareVersion, Scd30Error<I2cErr>> {
189                let receive = self.read::<3>(Command::ReadFirmwareVersion).await?;
190                Ok(FirmwareVersion::try_from(&receive[..])?)
191            }
192
193            /// Executes a soft reset of the sensor.
194            pub async fn soft_reset(&mut self) -> Result<(), Scd30Error<I2cErr>> {
195                self.write(Command::SoftReset, None).await
196            }
197
198            async fn read<const DATA_SIZE: usize>(
199                &mut self,
200                command: Command,
201            ) -> Result<[u8; DATA_SIZE], Scd30Error<I2cErr>> {
202                self.write(command, None).await?;
203                let mut data = [0; DATA_SIZE];
204                self.i2c.read(ADDRESS | READ_FLAG, &mut data).await?;
205                Ok(data)
206            }
207
208            async fn write(
209                &mut self,
210                command: Command,
211                data: Option<&[u8]>,
212            ) -> Result<(), Scd30Error<I2cErr>> {
213                let mut sent = [command.to_be_bytes()[0], command.to_be_bytes()[1], 0, 0, 0];
214
215                let len = if let Some(data) = data {
216                    if data.len() != 2 {
217                        return Err(Scd30Error::SentDataToBig);
218                    }
219                    sent[2] = data[0];
220                    sent[3] = data[1];
221                    sent[4] = compute_crc8(data);
222                    5
223                } else {
224                    2
225                };
226                Ok(self.i2c.write(ADDRESS | WRITE_FLAG, &sent[..len]).await?)
227            }
228
229            /// Consumes the sensor and returns the contained I2C peripheral.
230            #[cfg(not(tarpaulin_include))]
231            pub fn shutdown(self) -> I2C {
232                self.i2c
233            }
234        }
235
236        #[cfg(test)]
237        mod tests {
238            use super::*;
239            use crate::data::AmbientPressure;
240            use embedded_hal::i2c;
241            use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
242
243            #[test_macro]
244            async fn trigger_continuous_measurements_with_ambient_pressure_compensation() {
245                let expected_transactions = [I2cTransaction::write(
246                    0x61 | 0x00,
247                    vec![0x00, 0x10, 0x03, 0x20, 0x2A],
248                )];
249
250                let i2c = I2cMock::new(&expected_transactions);
251
252                let mut sensor = Scd30::new(i2c);
253
254                sensor
255                    .trigger_continuous_measurements(Some(
256                        AmbientPressureCompensation::CompensationPressure(
257                            AmbientPressure::try_from(800).unwrap(),
258                        ),
259                    ))
260                    .await
261                    .unwrap();
262                sensor.shutdown().done();
263            }
264
265            #[test_macro]
266            async fn trigger_continuous_measurements_spec_example_with_none() {
267                let expected_transactions = [I2cTransaction::write(
268                    0x61 | 0x00,
269                    vec![0x00, 0x10, 0x00, 0x00, 0x81],
270                )];
271
272                let i2c = I2cMock::new(&expected_transactions);
273
274                let mut sensor = Scd30::new(i2c);
275
276                sensor.trigger_continuous_measurements(None).await.unwrap();
277                sensor.shutdown().done();
278            }
279
280            #[test_macro]
281            async fn trigger_continuous_measurements_spec_example() {
282                let expected_transactions = [I2cTransaction::write(
283                    0x61 | 0x00,
284                    vec![0x00, 0x10, 0x00, 0x00, 0x81],
285                )];
286
287                let i2c = I2cMock::new(&expected_transactions);
288
289                let mut sensor = Scd30::new(i2c);
290
291                sensor
292                    .trigger_continuous_measurements(Some(
293                        AmbientPressureCompensation::DefaultPressure,
294                    ))
295                    .await
296                    .unwrap();
297                sensor.shutdown().done();
298            }
299
300            #[test_macro]
301            async fn stop_continuous_measurements_spec_example() {
302                let expected_transactions = [I2cTransaction::write(0x61 | 0x00, vec![0x01, 0x04])];
303
304                let i2c = I2cMock::new(&expected_transactions);
305
306                let mut sensor = Scd30::new(i2c);
307
308                sensor.stop_continuous_measurements().await.unwrap();
309                sensor.shutdown().done();
310            }
311
312            #[test_macro]
313            async fn set_measurement_interval_spec_example() {
314                let expected_transactions = [I2cTransaction::write(
315                    0x61 | 0x00,
316                    vec![0x46, 0x00, 0x00, 0x02, 0xE3],
317                )];
318
319                let i2c = I2cMock::new(&expected_transactions);
320
321                let mut sensor = Scd30::new(i2c);
322
323                sensor
324                    .set_measurement_interval(MeasurementInterval::try_from(2).unwrap())
325                    .await
326                    .unwrap();
327                sensor.shutdown().done();
328            }
329
330            #[test_macro]
331            async fn get_measurement_interval_spec_example() {
332                let expected_transactions = [
333                    I2cTransaction::write(0x61 | 0x00, vec![0x46, 0x00]),
334                    I2cTransaction::read(0x61 | 0x01, vec![0x00, 0x02, 0xE3]),
335                ];
336
337                let i2c = I2cMock::new(&expected_transactions);
338
339                let mut sensor = Scd30::new(i2c);
340
341                let interval = sensor.get_measurement_interval().await.unwrap();
342                assert_eq!(interval, MeasurementInterval::try_from(2).unwrap());
343                sensor.shutdown().done();
344            }
345
346            #[test_macro]
347            async fn get_ready_status_sample_works() {
348                let expected_transactions = [
349                    I2cTransaction::write(0x61 | 0x00, vec![0x02, 0x02]),
350                    I2cTransaction::read(0x61 | 0x01, vec![0x00, 0x01, 0xB0]),
351                ];
352
353                let i2c = I2cMock::new(&expected_transactions);
354
355                let mut sensor = Scd30::new(i2c);
356
357                let ready_status = sensor.is_data_ready().await.unwrap();
358                assert_eq!(ready_status, DataStatus::Ready);
359                sensor.shutdown().done();
360            }
361
362            #[test_macro]
363            async fn read_measurement_spec_example() {
364                let expected_transactions = [
365                    I2cTransaction::write(0x61 | 0x00, vec![0x03, 0x00]),
366                    I2cTransaction::read(
367                        0x61 | 0x01,
368                        vec![
369                            0x43, 0xDB, 0xCB, 0x8C, 0x2E, 0x8F, 0x41, 0xD9, 0x70, 0xE7, 0xFF, 0xF5,
370                            0x42, 0x43, 0xBF, 0x3A, 0x1B, 0x74,
371                        ],
372                    ),
373                ];
374
375                let i2c = I2cMock::new(&expected_transactions);
376
377                let mut sensor = Scd30::new(i2c);
378
379                let measurement = sensor.read_measurement().await.unwrap();
380                assert_eq!(measurement.co2_concentration, 439.09515);
381                assert_eq!(measurement.temperature, 27.23828);
382                assert_eq!(measurement.humidity, 48.806744);
383                sensor.shutdown().done();
384            }
385
386            #[test_macro]
387            async fn set_automatic_self_calibration_spec_example() {
388                let expected_transactions = [I2cTransaction::write(
389                    0x61 | 0x00,
390                    vec![0x53, 0x06, 0x00, 0x00, 0x81],
391                )];
392
393                let i2c = I2cMock::new(&expected_transactions);
394
395                let mut sensor = Scd30::new(i2c);
396
397                sensor
398                    .set_automatic_self_calibration(AutomaticSelfCalibration::Inactive)
399                    .await
400                    .unwrap();
401                sensor.shutdown().done();
402            }
403
404            #[test_macro]
405            async fn get_automatic_self_calibration_spec_example() {
406                let expected_transactions = [
407                    I2cTransaction::write(0x61 | 0x00, vec![0x53, 0x06]),
408                    I2cTransaction::read(0x61 | 0x01, vec![0x00, 0x00, 0x81]),
409                ];
410
411                let i2c = I2cMock::new(&expected_transactions);
412
413                let mut sensor = Scd30::new(i2c);
414
415                let asc = sensor.get_automatic_self_calibration().await.unwrap();
416                assert_eq!(asc, AutomaticSelfCalibration::Inactive);
417                sensor.shutdown().done();
418            }
419
420            #[test_macro]
421            async fn set_forced_recalibration_spec_example() {
422                let expected_transactions = [I2cTransaction::write(
423                    0x61 | 0x00,
424                    vec![0x52, 0x04, 0x01, 0xC2, 0x50],
425                )];
426
427                let i2c = I2cMock::new(&expected_transactions);
428
429                let mut sensor = Scd30::new(i2c);
430
431                sensor
432                    .set_forced_recalibration(ForcedRecalibrationValue::try_from(450).unwrap())
433                    .await
434                    .unwrap();
435                sensor.shutdown().done();
436            }
437
438            #[test_macro]
439            async fn get_forced_recalibration_spec_example() {
440                let expected_transactions = [
441                    I2cTransaction::write(0x61 | 0x00, vec![0x52, 0x04]),
442                    I2cTransaction::read(0x61 | 0x01, vec![0x01, 0xC2, 0x50]),
443                ];
444
445                let i2c = I2cMock::new(&expected_transactions);
446
447                let mut sensor = Scd30::new(i2c);
448
449                let frc = sensor.get_forced_recalibration().await.unwrap();
450                assert_eq!(frc, ForcedRecalibrationValue::try_from(450).unwrap());
451                sensor.shutdown().done();
452            }
453
454            #[test_macro]
455            async fn set_temperature_offset_spec_example() {
456                let expected_transactions = [I2cTransaction::write(
457                    0x61 | 0x00,
458                    vec![0x54, 0x03, 0x01, 0xF4, 0x33],
459                )];
460
461                let i2c = I2cMock::new(&expected_transactions);
462
463                let mut sensor = Scd30::new(i2c);
464
465                sensor
466                    .set_temperature_offset(TemperatureOffset::try_from(5.0).unwrap())
467                    .await
468                    .unwrap();
469                sensor.shutdown().done();
470            }
471
472            #[test_macro]
473            async fn get_temperature_offset_spec_example() {
474                let expected_transactions = [
475                    I2cTransaction::write(0x61 | 0x00, vec![0x54, 0x03]),
476                    I2cTransaction::read(0x61 | 0x01, vec![0x01, 0xF4, 0x33]),
477                ];
478
479                let i2c = I2cMock::new(&expected_transactions);
480
481                let mut sensor = Scd30::new(i2c);
482
483                let offset = sensor.get_temperature_offset().await.unwrap();
484                assert_eq!(offset, TemperatureOffset::try_from(5.0).unwrap());
485                sensor.shutdown().done();
486            }
487
488            #[test_macro]
489            async fn set_altitude_compensation_spec_example() {
490                let expected_transactions = [I2cTransaction::write(
491                    0x61 | 0x00,
492                    vec![0x51, 0x02, 0x03, 0xE8, 0xD4],
493                )];
494
495                let i2c = I2cMock::new(&expected_transactions);
496
497                let mut sensor = Scd30::new(i2c);
498
499                sensor
500                    .set_altitude_compensation(AltitudeCompensation::try_from(1000).unwrap())
501                    .await
502                    .unwrap();
503                sensor.shutdown().done();
504            }
505
506            #[test_macro]
507            async fn get_altitude_compensation_spec_example() {
508                let expected_transactions = [
509                    I2cTransaction::write(0x61 | 0x00, vec![0x51, 0x02]),
510                    I2cTransaction::read(0x61 | 0x01, vec![0x03, 0xE8, 0xD4]),
511                ];
512
513                let i2c = I2cMock::new(&expected_transactions);
514
515                let mut sensor = Scd30::new(i2c);
516
517                let altitude = sensor.get_altitude_compensation().await.unwrap();
518                assert_eq!(altitude, AltitudeCompensation::try_from(1000).unwrap());
519                sensor.shutdown().done();
520            }
521
522            #[test_macro]
523            async fn read_firmware_spec_example() {
524                let expected_transactions = [
525                    I2cTransaction::write(0x61 | 0x00, vec![0xD1, 0x00]),
526                    I2cTransaction::read(0x61 | 0x01, vec![0x03, 0x42, 0xF3]),
527                ];
528
529                let i2c = I2cMock::new(&expected_transactions);
530
531                let mut sensor = Scd30::new(i2c);
532
533                let version = sensor.read_firmware_version().await.unwrap();
534                assert_eq!(version.major, 3);
535                assert_eq!(version.minor, 66);
536                sensor.shutdown().done();
537            }
538
539            #[test_macro]
540            async fn execute_soft_reset_spec_example() {
541                let expected_transactions = [I2cTransaction::write(0x61 | 0x00, vec![0xD3, 0x04])];
542
543                let i2c = I2cMock::new(&expected_transactions);
544
545                let mut sensor = Scd30::new(i2c);
546
547                sensor.soft_reset().await.unwrap();
548                sensor.shutdown().done();
549            }
550
551            #[test_macro]
552            async fn read_errors_on_i2c_error() {
553                let expected_transactions = [
554                    I2cTransaction::write(0x61 | 0x00, vec![0xD1, 0x00]),
555                    I2cTransaction::read(0x61 | 0x01, vec![0x03, 0x42, 0xF3])
556                        .with_error(i2c::ErrorKind::Other),
557                ];
558                let i2c = I2cMock::new(&expected_transactions);
559
560                let mut sensor = Scd30::new(i2c);
561
562                let result = sensor.read::<3>(Command::ReadFirmwareVersion);
563                assert_eq!(
564                    result.await.unwrap_err(),
565                    Scd30Error::I2cError(i2c::ErrorKind::Other)
566                );
567                sensor.shutdown().done();
568            }
569
570            #[test_macro]
571            async fn write_errors_on_i2c_error() {
572                let expected_transactions = [I2cTransaction::write(0x61 | 0x00, vec![0xD3, 0x04])
573                    .with_error(i2c::ErrorKind::Other)];
574                let i2c = I2cMock::new(&expected_transactions);
575
576                let mut sensor = Scd30::new(i2c);
577
578                let result = sensor.write(Command::SoftReset, None);
579                assert_eq!(
580                    result.await.unwrap_err(),
581                    Scd30Error::I2cError(i2c::ErrorKind::Other)
582                );
583                sensor.shutdown().done();
584            }
585
586            #[test_macro]
587            async fn write_errors_on_too_big_send_data() {
588                let i2c = I2cMock::new(&[]);
589
590                let mut sensor = Scd30::new(i2c);
591
592                let result = sensor.write(
593                    Command::SetTemperatureOffset,
594                    Some([0x00, 0x00, 0x00, 0x00].as_slice()),
595                );
596                assert_eq!(result.await.unwrap_err(), Scd30Error::SentDataToBig);
597                sensor.shutdown().done();
598            }
599        }
600    }
601
602    #[cfg(feature=feature_)]
603    pub use inner::*;
604}