Skip to main content

embedded_sht3x/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(unsafe_code, missing_docs)]
3#![no_std]
4
5use bitflags::bitflags;
6use core::fmt::Display;
7use crc::{Crc, CRC_8_NRSC_5};
8
9#[cfg(not(feature = "async"))]
10use embedded_hal as hal;
11#[cfg(feature = "async")]
12use embedded_hal_async as hal;
13
14use hal::i2c::{Operation, SevenBitAddress};
15
16pub use weather_utils::Temperature;
17use weather_utils::{Celsius, RelativeHumidity, TemperatureAndRelativeHumidity};
18
19/// The I2C address when the ADDR pin is connected to logic low
20pub const I2C_ADDRESS_LOGIC_LOW: SevenBitAddress = 0x44;
21/// The I2C address when the ADDR pin is connected to logic high
22pub const I2C_ADDRESS_LOGIC_HIGH: SevenBitAddress = 0x45;
23/// The default I2C address (ADDR pin connected to low)
24pub const DEFAULT_I2C_ADDRESS: SevenBitAddress = I2C_ADDRESS_LOGIC_LOW;
25
26const CLEAR_STATUS_COMMAND: &[u8] = &[0x30, 0x41];
27const DISABLE_HEATER_COMMAND: &[u8] = &[0x30, 0x66];
28const ENABLE_HEATER_COMMAND: &[u8] = &[0x30, 0x6d];
29const GET_STATUS_COMMAND: &[u8] = &[0xf3, 0x2d];
30const MEASUREMENT_HIGH_REPEATIBILITY_COMMAND: &[u8] = &[0x2c, 0x06];
31const MEASUREMENT_MEDIUM_REPEATIBILITY_COMMAND: &[u8] = &[0x2c, 0x0d];
32const MEASUREMENT_LOW_REPEATIBILITY_COMMAND: &[u8] = &[0x2c, 0x10];
33const RESET_COMMAND: &[u8] = &[0x30, 0xa2];
34
35/// All possible errors generated when using the Sht3x struct
36#[derive(Debug)]
37pub enum Error<I2cE>
38where
39    I2cE: hal::i2c::Error,
40{
41    /// I²C bus error
42    I2c(I2cE),
43    /// The computed CRC and the one sent by the device mismatch
44    BadCrc,
45}
46
47impl<I2cE> From<I2cE> for Error<I2cE>
48where
49    I2cE: hal::i2c::Error,
50{
51    fn from(value: I2cE) -> Self {
52        Error::I2c(value)
53    }
54}
55
56/// The repeatability influences the measurement duration and the energy consumption of the sensor
57/// It also gives a more or less accurate measurement
58///
59/// Here are the repeatability values for humidity and temperature:
60///  - Low repeatability: 0.21 %RH - 0.15 °C
61///  - Medium repeatability: 0.15 %RH - 0.08 °C
62///  - High repeatability: 0.08 %RH - 0.04 °C
63///
64/// The measurement durations are the following:
65///  - Low repeatability: 4 ms (with supply voltage of 2.4-5.5 V) or 4.5 ms (with supply voltage of 2.15-2.4 V)
66///  - Medium repeatability: 6 ms (with supply voltage of 2.4-5.5 V) or 6.5 ms (with supply voltage of 2.15-2.4 V)
67///  - High repeatability: 15 ms (with supply voltage of 2.4-5.5 V) or 15.5 ms (with supply voltage of 2.15-2.4 V)
68#[derive(Debug)]
69pub enum Repeatability {
70    /// High repeatability: 0.08 %RH - 0.04 °C
71    High,
72    /// Medium repeatability: 0.15 %RH - 0.08 °C
73    Medium,
74    /// Low repeatability: 0.21 %RH - 0.15 °C
75    Low,
76}
77
78bitflags! {
79    /// The status of the sensor.
80    ///
81    /// It gives information on the operational status of the heater, the alert
82    /// mode and on the execution status of the last command and the last write
83    /// sequence.
84    #[derive(Debug)]
85    pub struct Status: u16 {
86        /// Write data checksum status
87        ///
88        /// - '0': checksum of last write transfer was correct
89        /// - '1': checksum of last write transfer was incorrect
90        const WRITE_DATA_CHECKSUM = 1 << 0;
91        /// Command status
92        ///
93        /// - '0': last command executed successfully
94        /// - '1': last command not processed. It was either invalid or failed
95        /// the integrated command checksum
96        const COMMAND = 1 << 1;
97        /// System reset detected
98        ///
99        /// - '0': no reset detected since last [Sht3x<I2C, D>::clear_status()]
100        /// call
101        /// - '1': reset detected (hard reset, supply fail or soft reset
102        /// ([Sht3x<I2c, D>::reset()])
103        const RESET = 1 << 4;
104        /// Temperature tracking alert
105        ///
106        /// - '0': no alert
107        /// - '1': alert
108        const T_TRACKING_ALERT = 1 << 10;
109        /// Relative humidity tracking alert
110        ///
111        /// - '0': no alert
112        /// - '1': alert
113        const RH_TRACKING_ALERT = 1 << 11;
114        /// Heater status
115        ///
116        /// - '0': Heater OFF
117        /// - '1': Heater ON
118        const HEATER = 1 << 13;
119        /// Alert pending status
120        ///
121        /// - '0': no pending alerts
122        /// - '1': at least one pending alert
123        const ALERT_PENDING = 1 << 15;
124    }
125}
126
127impl Display for Status {
128    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
129        bitflags::parser::to_writer(self, f)
130    }
131}
132
133#[derive(Clone, Copy, Debug, Default)]
134struct SensorMeasurement {
135    humidity: f32,
136    temperature: f32,
137}
138
139impl From<SensorMeasurement> for TemperatureAndRelativeHumidity<Celsius> {
140    fn from(value: SensorMeasurement) -> Self {
141        TemperatureAndRelativeHumidity {
142            temperature: Celsius(value.temperature),
143            relative_humidity: RelativeHumidity::new(value.humidity).unwrap(),
144        }
145    }
146}
147
148/// SHT3x device driver
149#[derive(Debug)]
150pub struct Sht3x<I2C, D> {
151    address: SevenBitAddress,
152    delay: D,
153    i2c: I2C,
154    /// The repeatability to use for measurements (defaults to medium).
155    pub repeatability: Repeatability,
156}
157
158impl<I2C, D> Sht3x<I2C, D>
159where
160    I2C: hal::i2c::I2c,
161    D: hal::delay::DelayNs,
162{
163    /// Clear the status of the sensor.
164    ///
165    /// All the flags of the status register will be cleared (set to zero).
166    #[maybe_async_cfg::maybe(
167        sync(not(feature = "async"), keep_self),
168        async(feature = "async", keep_self)
169    )]
170    pub async fn clear_status(&mut self) -> Result<(), Error<I2C::Error>> {
171        self.i2c.write(self.address, CLEAR_STATUS_COMMAND).await?;
172        Ok(())
173    }
174
175    /// Deactivate the internal heater.
176    #[maybe_async_cfg::maybe(
177        sync(not(feature = "async"), keep_self),
178        async(feature = "async", keep_self)
179    )]
180    pub async fn disable_heater(&mut self) -> Result<(), Error<I2C::Error>> {
181        self.i2c.write(self.address, DISABLE_HEATER_COMMAND).await?;
182        Ok(())
183    }
184
185    /// Activate the internal heater.
186    #[maybe_async_cfg::maybe(
187        sync(not(feature = "async"), keep_self),
188        async(feature = "async", keep_self)
189    )]
190    pub async fn enable_heater(&mut self) -> Result<(), Error<I2C::Error>> {
191        self.i2c.write(self.address, ENABLE_HEATER_COMMAND).await?;
192        Ok(())
193    }
194
195    /// Get the current status of the sensor
196    #[maybe_async_cfg::maybe(
197        sync(not(feature = "async"), keep_self),
198        async(feature = "async", keep_self)
199    )]
200    pub async fn get_status(&mut self) -> Result<Status, Error<I2C::Error>> {
201        let mut data = [0u8; 3];
202        let mut operations = [
203            Operation::Write(GET_STATUS_COMMAND),
204            Operation::Read(&mut data),
205        ];
206        self.i2c.transaction(self.address, &mut operations).await?;
207        let status: &[u8; 2] = &data[0..2].try_into().unwrap();
208        let status_crc = data[2];
209        Self::check_crc(status, status_crc)?;
210        Ok(Status::from_bits_retain(Self::get_u16_value(status)))
211    }
212
213    /// Perform a single-shot measurement
214    ///
215    /// This driver uses clock stretching so the result of the measurement is returned
216    /// as soon as the data is available after the measurement command has been sent to the sensor.
217    /// Therefore this call will take at least 4 ms and at most 15.5 ms depending on the chosen
218    /// repeatability and the supply voltage of the sensor.
219    #[maybe_async_cfg::maybe(
220        sync(not(feature = "async"), keep_self),
221        async(feature = "async", keep_self)
222    )]
223    pub async fn single_measurement(
224        &mut self,
225    ) -> Result<TemperatureAndRelativeHumidity<Celsius>, Error<I2C::Error>> {
226        let command = match self.repeatability {
227            Repeatability::High => MEASUREMENT_HIGH_REPEATIBILITY_COMMAND,
228            Repeatability::Medium => MEASUREMENT_MEDIUM_REPEATIBILITY_COMMAND,
229            Repeatability::Low => MEASUREMENT_LOW_REPEATIBILITY_COMMAND,
230        };
231        let mut data = [0u8; 6];
232        let mut operations = [Operation::Write(command), Operation::Read(&mut data)];
233        self.i2c.transaction(self.address, &mut operations).await?;
234        let temperature: &[u8; 2] = &data[0..2].try_into().unwrap();
235        let temperature_crc = data[2];
236        let humidity: &[u8; 2] = &data[3..5].try_into().unwrap();
237        let humidity_crc = data[5];
238        Self::check_crc(temperature, temperature_crc)?;
239        Self::check_crc(humidity, humidity_crc)?;
240        let temperature = Self::get_u16_value(temperature);
241        let humidity = Self::get_u16_value(humidity);
242
243        let measurement = SensorMeasurement {
244            temperature: ((temperature as f32 * 175.0) / 65535.0) - 45.0,
245            humidity: (humidity as f32 * 100.0) / 65535.0,
246        };
247        Ok(measurement.into())
248    }
249
250    /// Create a new instance of the SHT3x device.
251    #[maybe_async_cfg::maybe(
252        sync(not(feature = "async"), keep_self),
253        async(feature = "async", keep_self)
254    )]
255    pub async fn new(
256        i2c: I2C,
257        address: SevenBitAddress,
258        delay: D,
259    ) -> Result<Self, Error<I2C::Error>> {
260        let mut dev = Self {
261            address,
262            delay,
263            i2c,
264            repeatability: Repeatability::Medium,
265        };
266
267        dev.reset().await?;
268
269        Ok(dev)
270    }
271
272    /// Perform a soft reset to force the system into a well-defined state without removing
273    /// the power supply.
274    #[maybe_async_cfg::maybe(
275        sync(not(feature = "async"), keep_self),
276        async(feature = "async", keep_self)
277    )]
278    pub async fn reset(&mut self) -> Result<(), Error<I2C::Error>> {
279        self.i2c.write(self.address, RESET_COMMAND).await?;
280        self.delay.delay_us(1500).await; // Wait for the sensor to enter idle state
281        Ok(())
282    }
283
284    fn calc_crc(data: &[u8; 2]) -> u8 {
285        let crc = Crc::<u8>::new(&CRC_8_NRSC_5);
286        let mut digest = crc.digest();
287        digest.update(data);
288        digest.finalize()
289    }
290
291    fn check_crc(data: &[u8; 2], expected_crc: u8) -> Result<(), Error<I2C::Error>> {
292        if Self::calc_crc(data) != expected_crc {
293            Err(Error::BadCrc)
294        } else {
295            Ok(())
296        }
297    }
298
299    #[inline]
300    fn get_u16_value(data: &[u8; 2]) -> u16 {
301        (data[0] as u16) << 8 | (data[1] as u16)
302    }
303}
304
305#[cfg(test)]
306mod tests {
307    use core::fmt::Write;
308
309    use embedded_hal::i2c::ErrorKind;
310    use embedded_hal_mock::eh1::delay::StdSleep as Delay;
311    use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
312    use heapless::String;
313
314    use super::*;
315
316    fn create_device() -> Sht3x<I2cMock, Delay> {
317        let expectations = [I2cTransaction::write(
318            DEFAULT_I2C_ADDRESS,
319            RESET_COMMAND.to_vec(),
320        )];
321        let i2c = I2cMock::new(&expectations);
322        let mut device = Sht3x::new(i2c, DEFAULT_I2C_ADDRESS, Delay {}).unwrap();
323        device.i2c.done();
324        device
325    }
326
327    #[test]
328    fn clear_status() {
329        let expectations = [
330            I2cTransaction::write(DEFAULT_I2C_ADDRESS, CLEAR_STATUS_COMMAND.to_vec()),
331            I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
332            I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
333            I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x00, 0x00, 0x81].to_vec()),
334            I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
335        ];
336        let mut device = create_device();
337        device.i2c.update_expectations(&expectations);
338        assert!(matches!(device.clear_status(), Ok(())));
339        let status = device.get_status();
340        let status = match status {
341            Ok(s) => s,
342            Err(e) => panic!("Expected Ok(status), got Err({e:?})"),
343        };
344        assert!(!status.contains(Status::WRITE_DATA_CHECKSUM));
345        assert!(!status.contains(Status::COMMAND));
346        assert!(!status.contains(Status::RESET));
347        assert!(!status.contains(Status::T_TRACKING_ALERT));
348        assert!(!status.contains(Status::RH_TRACKING_ALERT));
349        assert!(!status.contains(Status::HEATER));
350        assert!(!status.contains(Status::ALERT_PENDING));
351        let mut buffer: String<64> = String::new();
352        write!(&mut buffer, "{status}").unwrap();
353        assert_eq!(buffer, "");
354        device.i2c.done();
355    }
356
357    #[test]
358    fn get_status() {
359        let expectations = [
360            I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
361            I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
362            I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x00, 0x00, 0x81].to_vec()),
363            I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
364        ];
365        let mut device = create_device();
366        device.i2c.update_expectations(&expectations);
367        let status = device.get_status();
368        let status = match status {
369            Ok(s) => s,
370            Err(e) => panic!("Expected Ok(status), got Err({e:?})"),
371        };
372        assert!(!status.contains(Status::WRITE_DATA_CHECKSUM));
373        assert!(!status.contains(Status::COMMAND));
374        assert!(!status.contains(Status::RESET));
375        assert!(!status.contains(Status::T_TRACKING_ALERT));
376        assert!(!status.contains(Status::RH_TRACKING_ALERT));
377        assert!(!status.contains(Status::HEATER));
378        assert!(!status.contains(Status::ALERT_PENDING));
379        let mut buffer: String<64> = String::new();
380        write!(&mut buffer, "{status}").unwrap();
381        assert_eq!(buffer, "");
382        device.i2c.done();
383    }
384
385    #[test]
386    fn get_status_bad_crc() {
387        let expectations = [
388            I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
389            I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
390            I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x00, 0x00, 0x73].to_vec()),
391            I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
392        ];
393        let mut device = create_device();
394        device.i2c.update_expectations(&expectations);
395        let err = device.get_status().expect_err("Bad CRC");
396        assert!(matches!(err, Error::BadCrc));
397        device.i2c.done();
398    }
399
400    #[test]
401    fn heater() {
402        let expectations = [
403            I2cTransaction::write(DEFAULT_I2C_ADDRESS, ENABLE_HEATER_COMMAND.to_vec()),
404            I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
405            I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
406            I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x20, 0x03, 0x0e].to_vec()),
407            I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
408            I2cTransaction::write(DEFAULT_I2C_ADDRESS, DISABLE_HEATER_COMMAND.to_vec()),
409            I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
410            I2cTransaction::write(DEFAULT_I2C_ADDRESS, GET_STATUS_COMMAND.to_vec()),
411            I2cTransaction::read(DEFAULT_I2C_ADDRESS, [0x00, 0x03, 0xd2].to_vec()),
412            I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
413        ];
414        let mut device = create_device();
415        device.i2c.update_expectations(&expectations);
416        assert!(matches!(device.enable_heater(), Ok(())));
417        let status = device.get_status();
418        let status = match status {
419            Ok(s) => s,
420            Err(e) => panic!("Expected Ok(status), got Err({e:?})"),
421        };
422        assert!(status.contains(Status::WRITE_DATA_CHECKSUM));
423        assert!(status.contains(Status::COMMAND));
424        assert!(!status.contains(Status::RESET));
425        assert!(!status.contains(Status::T_TRACKING_ALERT));
426        assert!(!status.contains(Status::RH_TRACKING_ALERT));
427        assert!(status.contains(Status::HEATER));
428        assert!(!status.contains(Status::ALERT_PENDING));
429        let mut buffer: String<64> = String::new();
430        write!(&mut buffer, "{status}").unwrap();
431        assert_eq!(buffer, "WRITE_DATA_CHECKSUM | COMMAND | HEATER");
432        assert!(matches!(device.disable_heater(), Ok(())));
433        let status = device.get_status();
434        let status = match status {
435            Ok(s) => s,
436            Err(e) => panic!("Expected Ok(status), got Err({e:?})"),
437        };
438        assert!(status.contains(Status::WRITE_DATA_CHECKSUM));
439        assert!(status.contains(Status::COMMAND));
440        assert!(!status.contains(Status::RESET));
441        assert!(!status.contains(Status::T_TRACKING_ALERT));
442        assert!(!status.contains(Status::RH_TRACKING_ALERT));
443        assert!(!status.contains(Status::HEATER));
444        assert!(!status.contains(Status::ALERT_PENDING));
445        let mut buffer: String<64> = String::new();
446        write!(&mut buffer, "{status}").unwrap();
447        assert_eq!(buffer, "WRITE_DATA_CHECKSUM | COMMAND");
448        device.i2c.done();
449    }
450
451    #[test]
452    fn reset() {
453        let expectations = [I2cTransaction::write(
454            DEFAULT_I2C_ADDRESS,
455            RESET_COMMAND.to_vec(),
456        )];
457        let mut device = create_device();
458        device.i2c.update_expectations(&expectations);
459        assert!(matches!(device.reset(), Ok(())));
460        device.i2c.done();
461    }
462
463    #[test]
464    fn reset_with_arbitration_loss_error() {
465        let expectations = [
466            I2cTransaction::write(DEFAULT_I2C_ADDRESS, RESET_COMMAND.to_vec())
467                .with_error(ErrorKind::ArbitrationLoss),
468        ];
469        let mut device = create_device();
470        device.i2c.update_expectations(&expectations);
471        assert!(matches!(device.reset(), Err(Error::I2c(_))));
472        device.i2c.done();
473    }
474
475    #[test]
476    fn single_measurement_high_repeatability() {
477        let expectations = [
478            I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
479            I2cTransaction::write(
480                DEFAULT_I2C_ADDRESS,
481                MEASUREMENT_HIGH_REPEATIBILITY_COMMAND.to_vec(),
482            ),
483            I2cTransaction::read(
484                DEFAULT_I2C_ADDRESS,
485                [0x5f, 0x58, 0x38, 0x7b, 0xb2, 0x7d].to_vec(),
486            ),
487            I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
488        ];
489        let mut device = create_device();
490        device.repeatability = Repeatability::High;
491        device.i2c.update_expectations(&expectations);
492        let measurement = device.single_measurement();
493        let measurement = match measurement {
494            Ok(m) => m,
495            Err(e) => panic!("Expected Ok(measurement), got Err({e:?})"),
496        };
497        assert_eq!(
498            measurement,
499            TemperatureAndRelativeHumidity {
500                temperature: Celsius(20.18),
501                relative_humidity: RelativeHumidity::new(48.32).unwrap()
502            }
503        );
504        device.i2c.done();
505    }
506
507    #[test]
508    fn single_measurement_low_repeatability() {
509        let expectations = [
510            I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
511            I2cTransaction::write(
512                DEFAULT_I2C_ADDRESS,
513                MEASUREMENT_LOW_REPEATIBILITY_COMMAND.to_vec(),
514            ),
515            I2cTransaction::read(
516                DEFAULT_I2C_ADDRESS,
517                [0x5f, 0x58, 0x38, 0x7b, 0xb2, 0x7d].to_vec(),
518            ),
519            I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
520        ];
521        let mut device = create_device();
522        device.repeatability = Repeatability::Low;
523        device.i2c.update_expectations(&expectations);
524        let measurement = device.single_measurement();
525        let measurement = match measurement {
526            Ok(m) => m,
527            Err(e) => panic!("Expected Ok(measurement), got Err({e:?})"),
528        };
529        assert_eq!(
530            measurement,
531            TemperatureAndRelativeHumidity {
532                temperature: Celsius(20.18),
533                relative_humidity: RelativeHumidity::new(48.32).unwrap()
534            }
535        );
536        device.i2c.done();
537    }
538
539    #[test]
540    fn single_measurement_medium_repeatability() {
541        let expectations = [
542            I2cTransaction::transaction_start(DEFAULT_I2C_ADDRESS),
543            I2cTransaction::write(
544                DEFAULT_I2C_ADDRESS,
545                MEASUREMENT_MEDIUM_REPEATIBILITY_COMMAND.to_vec(),
546            ),
547            I2cTransaction::read(
548                DEFAULT_I2C_ADDRESS,
549                [0x71, 0x17, 0x9a, 0xcb, 0x91, 0x39].to_vec(),
550            ),
551            I2cTransaction::transaction_end(DEFAULT_I2C_ADDRESS),
552        ];
553        let mut device = create_device();
554        device.i2c.update_expectations(&expectations);
555        let measurement = device.single_measurement();
556        let measurement = match measurement {
557            Ok(m) => m,
558            Err(e) => panic!("Expected Ok(measurement), got Err({e:?})"),
559        };
560        assert_eq!(
561            measurement,
562            TemperatureAndRelativeHumidity {
563                temperature: Celsius(32.31),
564                relative_humidity: RelativeHumidity::new(79.52).unwrap()
565            }
566        );
567        device.i2c.done();
568    }
569}