scd4x_rs/blocking/
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::delay::DelayNs;
16use embedded_hal::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 fn start_periodic_measurement(mut self) -> Result<Scd4x<I2C, D, Measuring>, Error> {
75        debug!("Send command 'start_periodic_measurement'");
76
77        commands::StartPeriodicMeasurement.execute(
78            self.address,
79            &mut self.i2c,
80            &mut self.delay,
81            (),
82        )?;
83
84        Ok(Scd4x {
85            i2c: self.i2c,
86            address: self.address,
87            delay: self.delay,
88            _state: PhantomData,
89        })
90    }
91
92    /// Set temperature offset
93    ///
94    /// # Errors
95    ///
96    /// Return an error if it cannot communicate with the sensor.
97    pub fn set_temperature_offset(&mut self, temperature_offset: Temperature) -> Result<(), Error> {
98        debug!("Send command 'set_temperature_offset'");
99
100        commands::SetTemperatureOffset.execute(
101            self.address,
102            &mut self.i2c,
103            &mut self.delay,
104            temperature_offset,
105        )
106    }
107
108    /// Get temperature offset
109    ///
110    /// # Errors
111    ///
112    /// Return an error if it cannot communicate with the sensor.
113    pub fn get_temperature_offset(&mut self) -> Result<Temperature, Error> {
114        debug!("Send command 'get_temperature_offset'");
115
116        commands::GetTemperatureOffset.execute(self.address, &mut self.i2c, &mut self.delay, ())
117    }
118
119    /// Set sensor altitude
120    ///
121    /// # Errors
122    ///
123    /// Return an error if it cannot communicate with the sensor.
124    pub fn set_sensor_altitude(&mut self, sensor_altitude: Altitude) -> Result<(), Error> {
125        debug!("Send command 'set_sensor_altitude'");
126
127        commands::SetSensorAltitude.execute(
128            self.address,
129            &mut self.i2c,
130            &mut self.delay,
131            sensor_altitude,
132        )
133    }
134
135    /// Get sensor altitude
136    ///
137    /// # Errors
138    ///
139    /// Return an error if it cannot communicate with the sensor.
140    pub fn get_sensor_altitude(&mut self) -> Result<Altitude, Error> {
141        debug!("Send command 'get_sensor_altitude'");
142
143        commands::GetSensorAltitude.execute(self.address, &mut self.i2c, &mut self.delay, ())
144    }
145
146    /// Perform forced recalibration
147    ///
148    /// # Errors
149    ///
150    /// Return an error if it cannot communicate with the sensor.
151    pub fn perform_forced_recalibration(&mut self, co2: Co2) -> Result<Option<Co2>, Error> {
152        debug!("Send command 'perform_forced_recalibration'");
153
154        commands::PerformForcedRecalibration.execute(
155            self.address,
156            &mut self.i2c,
157            &mut self.delay,
158            co2,
159        )
160    }
161
162    /// Set whether automatic self-calibration is enabled
163    ///
164    /// # Errors
165    ///
166    /// Return an error if it cannot communicate with the sensor.
167    pub fn set_automatic_self_calibration_enabled(&mut self, enabled: bool) -> Result<(), Error> {
168        debug!("Send command 'set_automatic_self_calibration_enabled'");
169
170        commands::SetAutomaticSelfCalibrationEnabled.execute(
171            self.address,
172            &mut self.i2c,
173            &mut self.delay,
174            enabled,
175        )
176    }
177
178    /// Query whether automatic self-calibration is enabled
179    ///
180    /// # Errors
181    ///
182    /// Return an error if it cannot communicate with the sensor.
183    pub fn get_automatic_self_calibration_enabled(&mut self) -> Result<bool, Error> {
184        debug!("Send command 'get_automatic_self_calibration_enabled'");
185
186        commands::GetAutomaticSelfCalibrationEnabled.execute(
187            self.address,
188            &mut self.i2c,
189            &mut self.delay,
190            (),
191        )
192    }
193
194    /// Start low-power periodic measurement
195    ///
196    /// # Errors
197    ///
198    /// Return an error if it cannot communicate with the sensor.
199    pub fn start_low_power_periodic_measurement(
200        mut self,
201    ) -> Result<Scd4x<I2C, D, Measuring>, Error> {
202        debug!("Send command 'start_low_power_periodic_measurement'");
203
204        commands::StartLowPowerPeriodicMeasurement.execute(
205            self.address,
206            &mut self.i2c,
207            &mut self.delay,
208            (),
209        )?;
210
211        Ok(Scd4x {
212            i2c: self.i2c,
213            address: self.address,
214            delay: self.delay,
215            _state: PhantomData,
216        })
217    }
218
219    /// Persist settings to EEPROM
220    ///
221    /// # Errors
222    ///
223    /// Return an error if it cannot communicate with the sensor.
224    pub fn persist_settings(&mut self) -> Result<(), Error> {
225        debug!("Send command 'persist_settings'");
226
227        commands::PersistSettings.execute(self.address, &mut self.i2c, &mut self.delay, ())
228    }
229
230    /// Obtain the serial number
231    ///
232    /// # Errors
233    ///
234    /// Return an error if it cannot communicate with the sensor.
235    pub fn get_serial_number(&mut self) -> Result<u64, Error> {
236        debug!("Send command 'get_serial_number'");
237
238        commands::GetSerialNumber.execute(self.address, &mut self.i2c, &mut self.delay, ())
239    }
240
241    /// Perform self-test
242    ///
243    /// # Errors
244    ///
245    /// Return an error if it cannot communicate with the sensor.
246    pub fn perform_self_test(&mut self) -> Result<bool, Error> {
247        debug!("Send command 'perform_self_test'");
248
249        commands::PerformSelfTest.execute(self.address, &mut self.i2c, &mut self.delay, ())
250    }
251
252    /// Perform factory reset
253    ///
254    /// # Errors
255    ///
256    /// Return an error if it cannot communicate with the sensor.
257    pub fn perform_factory_reset(&mut self) -> Result<(), Error> {
258        debug!("Send command 'perform_factory_reset'");
259
260        commands::PerformFactoryReset.execute(self.address, &mut self.i2c, &mut self.delay, ())
261    }
262
263    /// Reinitialize the sensor
264    ///
265    /// Send a soft-reset signal, obtain the calibration coefficients, and set
266    /// default sampling configuration.
267    ///
268    /// Note that the default sampling configuration disables measurement of
269    /// temperature, pressure and humidity.
270    ///
271    /// # Errors
272    ///
273    /// Return an error if it cannot communicate with the sensor.
274    pub fn reinit(&mut self) -> Result<(), Error> {
275        debug!("Send command 'reinit'");
276
277        commands::Reinitialize.execute(self.address, &mut self.i2c, &mut self.delay, ())
278    }
279
280    /// Read a single-shot measurement
281    ///
282    /// # Errors
283    ///
284    /// Return an error if it cannot communicate with the sensor.
285    pub fn measure_single_shot(mut self) -> Result<Scd4x<I2C, D, Measuring>, Error> {
286        debug!("Send command 'measure_single_shot'");
287
288        commands::MeasureSingleShot.execute(self.address, &mut self.i2c, &mut self.delay, ())?;
289
290        Ok(Scd4x {
291            i2c: self.i2c,
292            address: self.address,
293            delay: self.delay,
294            _state: PhantomData,
295        })
296    }
297
298    /// Read a single-shot measurement of humidity and temperature
299    ///
300    /// # Errors
301    ///
302    /// Return an error if it cannot communicate with the sensor.
303    pub fn measure_single_shot_rht_only(mut self) -> Result<Scd4x<I2C, D, Measuring>, Error> {
304        debug!("Send command 'measure_single_shot_rht_only'");
305
306        commands::MeasureSingleShotRhtOnly.execute(
307            self.address,
308            &mut self.i2c,
309            &mut self.delay,
310            (),
311        )?;
312
313        Ok(Scd4x {
314            i2c: self.i2c,
315            address: self.address,
316            delay: self.delay,
317            _state: PhantomData,
318        })
319    }
320}
321
322impl<I2C, D> Scd4x<I2C, D, Measuring>
323where
324    I2C: I2c,
325    D: DelayNs,
326{
327    /// Create a new sensor in measuring state using an I²C interface and a
328    /// delay function using the sensor's default address [`DEFAULT_ADDRESS`])
329    pub fn new_in_measuring(i2c: I2C, delay: D) -> Self {
330        Self::new_in_measuring_with_address(i2c, DEFAULT_ADDRESS, delay)
331    }
332
333    /// Create a new sensor in measuring state  using an I²C interface and a
334    /// delay function
335    pub fn new_in_measuring_with_address(i2c: I2C, address: u8, delay: D) -> Self {
336        Self {
337            i2c,
338            address,
339            delay,
340            _state: PhantomData,
341        }
342    }
343
344    /// Read a measurement from the sensor
345    ///
346    /// # Errors
347    ///
348    /// Return an error if it cannot communicate with the sensor.
349    pub fn read_measurement(&mut self) -> Result<Sample, Error> {
350        debug!("Send command 'read_measurement'");
351
352        commands::ReadMeasurement.execute(self.address, &mut self.i2c, &mut self.delay, ())
353    }
354
355    /// Query whether data is available to be read
356    ///
357    /// # Errors
358    ///
359    /// Return an error if it cannot communicate with the sensor.
360    pub fn get_data_ready_status(&mut self) -> Result<bool, Error> {
361        debug!("Send command 'get_data_ready_status'");
362
363        commands::GetDataReadyStatus.execute(self.address, &mut self.i2c, &mut self.delay, ())
364    }
365}
366
367impl<I2C, D, S> Scd4x<I2C, D, S>
368where
369    I2C: I2c,
370    D: DelayNs,
371    S: State,
372{
373    /// Release the I²C interface
374    pub fn release(self) -> I2C {
375        self.i2c
376    }
377
378    /// Stop periodic measurement
379    ///
380    /// # Errors
381    ///
382    /// Return an error if it cannot communicate with the sensor.
383    pub fn stop_periodic_measurement(mut self) -> Result<Scd4x<I2C, D, Idle>, Error> {
384        debug!("Send command 'stop_periodic_measurement'");
385
386        commands::StopPeriodicMeasurement.execute(
387            self.address,
388            &mut self.i2c,
389            &mut self.delay,
390            (),
391        )?;
392
393        Ok(Scd4x {
394            i2c: self.i2c,
395            address: self.address,
396            delay: self.delay,
397            _state: PhantomData,
398        })
399    }
400
401    /// Set ambient pressure
402    ///
403    /// # Errors
404    ///
405    /// Return an error if it cannot communicate with the sensor.
406    pub fn set_ambient_pressure(&mut self, ambient_pressure: Pressure) -> Result<(), Error> {
407        debug!("Send command 'set_ambient_pressure'");
408
409        commands::SetAmbientPressure.execute(
410            self.address,
411            &mut self.i2c,
412            &mut self.delay,
413            ambient_pressure,
414        )
415    }
416}
417
418#[cfg(test)]
419mod tests {
420    #![allow(clippy::panic_in_result_fn)]
421
422    use super::*;
423
424    use embedded_hal_mock::eh1::delay::NoopDelay as DelayMock;
425    use embedded_hal_mock::eh1::i2c::Mock as I2cMock;
426    use embedded_hal_mock::eh1::i2c::Transaction as I2cTransaction;
427
428    use crate::sample::altitude_from_meter;
429    use crate::sample::co2_from_ppm;
430    use crate::sample::humidity_from_number;
431    use crate::sample::pressure_from_hectopascal;
432    use crate::sample::temperature_from_celsius;
433    use crate::Error;
434
435    #[test]
436    fn test_get_serial_number() -> Result<(), Error> {
437        let expectations = [
438            I2cTransaction::write(DEFAULT_ADDRESS, vec![0x36, 0x82]),
439            I2cTransaction::read(
440                DEFAULT_ADDRESS,
441                vec![0xf8, 0x96, 0x31, 0x9f, 0x07, 0xc2, 0x3b, 0xbe, 0x89],
442            ),
443        ];
444        let i2c = I2cMock::new(&expectations);
445
446        let mut scd4x = Scd4x::new(i2c, DelayMock);
447
448        let serial_number = scd4x.get_serial_number()?;
449
450        assert_eq!(serial_number, 273_325_796_834_238);
451
452        scd4x.release().done();
453        Ok(())
454    }
455
456    #[test]
457    fn test_reinit() -> Result<(), Error> {
458        let expectations = [I2cTransaction::write(DEFAULT_ADDRESS, vec![0x36, 0x46])];
459        let i2c = I2cMock::new(&expectations);
460
461        let mut scd4x = Scd4x::new(i2c, DelayMock);
462
463        scd4x.reinit()?;
464
465        scd4x.release().done();
466        Ok(())
467    }
468
469    #[test]
470    fn test_measure_single_shot() -> Result<(), Error> {
471        let expectations = [I2cTransaction::write(DEFAULT_ADDRESS, vec![0x21, 0x9d])];
472        let i2c = I2cMock::new(&expectations);
473
474        let scd4x = Scd4x::new(i2c, DelayMock);
475
476        let scd4x = scd4x.measure_single_shot()?;
477
478        scd4x.release().done();
479        Ok(())
480    }
481
482    #[test]
483    fn test_measure_single_shot_rht_only() -> Result<(), Error> {
484        let expectations = [I2cTransaction::write(DEFAULT_ADDRESS, vec![0x21, 0x96])];
485        let i2c = I2cMock::new(&expectations);
486
487        let scd4x = Scd4x::new(i2c, DelayMock);
488
489        let scd4x = scd4x.measure_single_shot_rht_only()?;
490
491        scd4x.release().done();
492        Ok(())
493    }
494
495    #[test]
496    fn test_perform_factory_reset() -> Result<(), Error> {
497        let expectations = [I2cTransaction::write(DEFAULT_ADDRESS, vec![0x36, 0x32])];
498        let i2c = I2cMock::new(&expectations);
499
500        let mut scd4x = Scd4x::new(i2c, DelayMock);
501
502        scd4x.perform_factory_reset()?;
503
504        scd4x.release().done();
505        Ok(())
506    }
507
508    #[test]
509    fn test_perform_self_test() -> Result<(), Error> {
510        let expectations = [
511            I2cTransaction::write(DEFAULT_ADDRESS, vec![0x36, 0x39]),
512            I2cTransaction::read(DEFAULT_ADDRESS, vec![0x00, 0x00, 0x81]),
513        ];
514        let i2c = I2cMock::new(&expectations);
515
516        let mut scd4x = Scd4x::new(i2c, DelayMock);
517
518        let result = scd4x.perform_self_test()?;
519        assert!(result);
520
521        scd4x.release().done();
522        Ok(())
523    }
524
525    #[test]
526    fn test_persist_settings() -> Result<(), Error> {
527        let expectations = [I2cTransaction::write(DEFAULT_ADDRESS, vec![0x36, 0x15])];
528        let i2c = I2cMock::new(&expectations);
529
530        let mut scd4x = Scd4x::new(i2c, DelayMock);
531
532        scd4x.persist_settings()?;
533
534        scd4x.release().done();
535        Ok(())
536    }
537
538    #[test]
539    fn test_get_data_ready_status() -> Result<(), Error> {
540        let expectations = [
541            I2cTransaction::write(DEFAULT_ADDRESS, vec![0xe4, 0xb8]),
542            I2cTransaction::read(DEFAULT_ADDRESS, vec![0x80, 0x00, 0xa2]),
543        ];
544        let i2c = I2cMock::new(&expectations);
545
546        let mut scd4x = Scd4x::new_in_measuring(i2c, DelayMock);
547
548        let ready = scd4x.get_data_ready_status()?;
549        assert!(!ready);
550
551        scd4x.release().done();
552        Ok(())
553    }
554
555    #[test]
556    fn test_start_low_power_periodic_measurement() -> Result<(), Error> {
557        let expectations = [I2cTransaction::write(DEFAULT_ADDRESS, vec![0x21, 0xac])];
558        let i2c = I2cMock::new(&expectations);
559
560        let scd4x = Scd4x::new(i2c, DelayMock);
561
562        let scd4x = scd4x.start_low_power_periodic_measurement()?;
563
564        scd4x.release().done();
565        Ok(())
566    }
567
568    #[test]
569    fn test_get_automatic_self_calibration_enabled() -> Result<(), Error> {
570        let expectations = [
571            I2cTransaction::write(DEFAULT_ADDRESS, vec![0x23, 0x13]),
572            I2cTransaction::read(DEFAULT_ADDRESS, vec![0x00, 0x00, 0x81]),
573        ];
574        let i2c = I2cMock::new(&expectations);
575
576        let mut scd4x = Scd4x::new(i2c, DelayMock);
577
578        let enabled = scd4x.get_automatic_self_calibration_enabled()?;
579        assert!(!enabled);
580
581        scd4x.release().done();
582        Ok(())
583    }
584
585    #[test]
586    fn test_set_automatic_self_calibration_enabled() -> Result<(), Error> {
587        let expectations = [I2cTransaction::write(
588            DEFAULT_ADDRESS,
589            vec![0x24, 0x16, 0x00, 0x01, 0xb0],
590        )];
591        let i2c = I2cMock::new(&expectations);
592
593        let mut scd4x = Scd4x::new(i2c, DelayMock);
594
595        scd4x.set_automatic_self_calibration_enabled(true)?;
596
597        scd4x.release().done();
598        Ok(())
599    }
600
601    #[test]
602    fn test_perform_forced_recalibration() -> Result<(), Error> {
603        let expectations = [
604            I2cTransaction::write(DEFAULT_ADDRESS, vec![0x36, 0x2f, 0x01, 0xe0, 0xb4]),
605            I2cTransaction::read(DEFAULT_ADDRESS, vec![0x7f, 0xce, 0x7b]),
606        ];
607        let i2c = I2cMock::new(&expectations);
608
609        let mut scd4x = Scd4x::new(i2c, DelayMock);
610
611        let correction = scd4x.perform_forced_recalibration(co2_from_ppm(480.0))?;
612        assert_eq!(correction, Some(co2_from_ppm(-50.0)));
613
614        scd4x.release().done();
615        Ok(())
616    }
617
618    #[test]
619    fn test_perform_forced_recalibration_failure() -> Result<(), Error> {
620        let expectations = [
621            I2cTransaction::write(DEFAULT_ADDRESS, vec![0x36, 0x2f, 0x01, 0xe0, 0xb4]),
622            I2cTransaction::read(DEFAULT_ADDRESS, vec![0xff, 0xff, 0xac]),
623        ];
624        let i2c = I2cMock::new(&expectations);
625
626        let mut scd4x = Scd4x::new(i2c, DelayMock);
627
628        let correction = scd4x.perform_forced_recalibration(co2_from_ppm(480.0))?;
629        assert_eq!(correction, None);
630
631        scd4x.release().done();
632        Ok(())
633    }
634
635    #[test]
636    fn test_set_ambient_pressure() -> Result<(), Error> {
637        let expectations = [I2cTransaction::write(
638            DEFAULT_ADDRESS,
639            vec![0xe0, 0x00, 0x03, 0xdb, 0x42],
640        )];
641        let i2c = I2cMock::new(&expectations);
642
643        let mut scd4x = Scd4x::new(i2c, DelayMock);
644
645        scd4x.set_ambient_pressure(pressure_from_hectopascal(987.0))?;
646
647        scd4x.release().done();
648        Ok(())
649    }
650
651    #[test]
652    fn test_get_sensor_altitude() -> Result<(), Error> {
653        let expectations = [
654            I2cTransaction::write(DEFAULT_ADDRESS, vec![0x23, 0x22]),
655            I2cTransaction::read(DEFAULT_ADDRESS, vec![0x04, 0x4c, 0x42]),
656        ];
657        let i2c = I2cMock::new(&expectations);
658
659        let mut scd4x = Scd4x::new(i2c, DelayMock);
660
661        let sensor_altitude = scd4x.get_sensor_altitude()?;
662        assert_eq!(sensor_altitude, altitude_from_meter(1100.0));
663
664        scd4x.release().done();
665        Ok(())
666    }
667
668    #[test]
669    fn test_set_sensor_altitude() -> Result<(), Error> {
670        let expectations = [I2cTransaction::write(
671            DEFAULT_ADDRESS,
672            vec![0x24, 0x27, 0x07, 0x9e, 0x09],
673        )];
674        let i2c = I2cMock::new(&expectations);
675
676        let mut scd4x = Scd4x::new(i2c, DelayMock);
677
678        scd4x.set_sensor_altitude(altitude_from_meter(1950.0))?;
679
680        scd4x.release().done();
681        Ok(())
682    }
683
684    #[test]
685    fn test_get_temperature_offset() -> Result<(), Error> {
686        let expectations = [
687            I2cTransaction::write(DEFAULT_ADDRESS, vec![0x23, 0x18]),
688            I2cTransaction::read(DEFAULT_ADDRESS, vec![0x09, 0x12, 0x63]),
689        ];
690        let i2c = I2cMock::new(&expectations);
691
692        let mut scd4x = Scd4x::new(i2c, DelayMock);
693
694        let temperature_offset = scd4x.get_temperature_offset()?;
695        assert_eq!(temperature_offset, temperature_from_celsius(6.200_409));
696
697        scd4x.release().done();
698        Ok(())
699    }
700
701    #[test]
702    fn test_set_temperature_offset() -> Result<(), Error> {
703        let expectations = [I2cTransaction::write(
704            DEFAULT_ADDRESS,
705            vec![0x24, 0x1d, 0x07, 0xe6, 0x48],
706        )];
707        let i2c = I2cMock::new(&expectations);
708
709        let mut scd4x = Scd4x::new(i2c, DelayMock);
710
711        scd4x.set_temperature_offset(temperature_from_celsius(5.4))?;
712
713        scd4x.release().done();
714        Ok(())
715    }
716
717    #[test]
718    fn test_stop_periodic_measurement() -> Result<(), Error> {
719        let expectations = [I2cTransaction::write(DEFAULT_ADDRESS, vec![0x3f, 0x86])];
720        let i2c = I2cMock::new(&expectations);
721
722        let scd4x = Scd4x::new(i2c, DelayMock);
723
724        let scd4x = scd4x.stop_periodic_measurement()?;
725
726        scd4x.release().done();
727        Ok(())
728    }
729
730    #[test]
731    fn test_read_measurement() -> Result<(), Error> {
732        let expectations = [
733            I2cTransaction::write(DEFAULT_ADDRESS, vec![0xec, 0x05]),
734            I2cTransaction::read(
735                DEFAULT_ADDRESS,
736                vec![0x01, 0xf4, 0x33, 0x66, 0x67, 0xa2, 0x5e, 0xb9, 0x3c],
737            ),
738        ];
739        let i2c = I2cMock::new(&expectations);
740
741        let mut scd4x = Scd4x::new_in_measuring(i2c, DelayMock);
742
743        let sample = scd4x.read_measurement()?;
744        let expected = Sample {
745            co2: co2_from_ppm(500.0),
746            temperature: temperature_from_celsius(25.001_602),
747            humidity: humidity_from_number(37.001_038),
748        };
749
750        assert_eq!(sample, expected);
751
752        scd4x.release().done();
753        Ok(())
754    }
755
756    #[test]
757    fn test_start_periodic_measurement() -> Result<(), Error> {
758        let expectations = [I2cTransaction::write(DEFAULT_ADDRESS, vec![0x21, 0xb1])];
759        let i2c = I2cMock::new(&expectations);
760
761        let scd4x = Scd4x::new(i2c, DelayMock);
762
763        let scd4x = scd4x.start_periodic_measurement()?;
764
765        scd4x.release().done();
766
767        Ok(())
768    }
769}