Skip to main content

sht4x/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![deny(missing_docs)]
4//! `embedded-hal` 1.0 driver for the Sensirion SHT4x family
5//! (SHT40, SHT41, SHT45) temperature & humidity sensors.
6//!
7//! Supports both blocking and `async` APIs behind cargo features.
8//!
9//! ```no_run
10//! # #[cfg(feature = "blocking")]
11//! # fn run<I, D>(i2c: I, delay: D) -> Result<(), sht4x::Error<I::Error>>
12//! # where I: embedded_hal::i2c::I2c, D: embedded_hal::delay::DelayNs {
13//! use sht4x::{Sht4x, Precision, DEFAULT_ADDRESS};
14//! let mut sensor = Sht4x::new(i2c, delay, DEFAULT_ADDRESS);
15//! let m = sensor.measure(Precision::High)?;
16//! let _ = (m.temperature_celsius(), m.humidity_percent());
17//! # Ok(()) }
18//! ```
19
20pub mod commands;
21mod crc;
22mod error;
23mod types;
24
25pub use error::Error;
26pub use types::{HeaterDuration, HeaterPower, Measurement, Precision};
27
28/// Default I²C address for the SHT4x family.
29pub const DEFAULT_ADDRESS: u8 = 0x44;
30
31/// Length of a measurement read (T word + CRC + RH word + CRC).
32const MEASUREMENT_LEN: usize = 6;
33/// Length of a serial-number read (2 words, each with CRC).
34const SERIAL_LEN: usize = 6;
35
36/// SHT4x driver, generic over an I²C bus and a delay source.
37///
38/// Construct with [`Sht4x::new`]. The same struct provides either the
39/// blocking API (default feature `blocking`) or the async API
40/// (feature `async`), depending on which `embedded-hal` traits the
41/// provided `I2C` / `D` implement.
42pub struct Sht4x<I2C, D> {
43    i2c: I2C,
44    delay: D,
45    address: u8,
46}
47
48impl<I2C, D> Sht4x<I2C, D> {
49    /// Create a new driver instance.
50    ///
51    /// `address` is usually [`DEFAULT_ADDRESS`] (0x44) unless you have a
52    /// part variant with a non-standard address.
53    pub fn new(i2c: I2C, delay: D, address: u8) -> Self {
54        Self {
55            i2c,
56            delay,
57            address,
58        }
59    }
60
61    /// Release the bus and delay back to the caller.
62    pub fn release(self) -> (I2C, D) {
63        (self.i2c, self.delay)
64    }
65}
66
67/// Decode a 6-byte `[t_hi, t_lo, t_crc, h_hi, h_lo, h_crc]` reply.
68fn decode_measurement<E>(buf: &[u8; MEASUREMENT_LEN]) -> Result<Measurement, Error<E>> {
69    let t = [buf[0], buf[1], buf[2]];
70    let h = [buf[3], buf[4], buf[5]];
71    if !crc::verify_word(&t) || !crc::verify_word(&h) {
72        return Err(Error::Crc);
73    }
74    let raw_t = u16::from_be_bytes([t[0], t[1]]);
75    let raw_h = u16::from_be_bytes([h[0], h[1]]);
76    Ok(Measurement::from_raw(raw_t, raw_h))
77}
78
79/// Decode a 6-byte serial-number reply into a `u32`.
80fn decode_serial<E>(buf: &[u8; SERIAL_LEN]) -> Result<u32, Error<E>> {
81    let w0 = [buf[0], buf[1], buf[2]];
82    let w1 = [buf[3], buf[4], buf[5]];
83    if !crc::verify_word(&w0) || !crc::verify_word(&w1) {
84        return Err(Error::Crc);
85    }
86    let hi = u16::from_be_bytes([w0[0], w0[1]]);
87    let lo = u16::from_be_bytes([w1[0], w1[1]]);
88    Ok(((hi as u32) << 16) | lo as u32)
89}
90
91#[cfg(feature = "blocking")]
92mod blocking_impl {
93    use super::*;
94    use embedded_hal::delay::DelayNs;
95    use embedded_hal::i2c::I2c;
96
97    impl<I2C, D> Sht4x<I2C, D>
98    where
99        I2C: I2c,
100        D: DelayNs,
101    {
102        /// Trigger a measurement and read back temperature + humidity.
103        pub fn measure(&mut self, precision: Precision) -> Result<Measurement, Error<I2C::Error>> {
104            self.i2c
105                .write(self.address, &[precision.command()])
106                .map_err(Error::I2c)?;
107            self.delay.delay_us(precision.duration_us());
108            let mut buf = [0u8; MEASUREMENT_LEN];
109            self.i2c
110                .read(self.address, &mut buf)
111                .map_err(Error::I2c)?;
112            decode_measurement(&buf)
113        }
114
115        /// Activate the on-die heater for the given power & duration,
116        /// then return the (high-precision) measurement taken at the end.
117        pub fn measure_with_heater(
118            &mut self,
119            power: HeaterPower,
120            duration: HeaterDuration,
121        ) -> Result<Measurement, Error<I2C::Error>> {
122            let cmd = types::heater_command(power, duration);
123            self.i2c.write(self.address, &[cmd]).map_err(Error::I2c)?;
124            self.delay.delay_us(duration.duration_us());
125            let mut buf = [0u8; MEASUREMENT_LEN];
126            self.i2c
127                .read(self.address, &mut buf)
128                .map_err(Error::I2c)?;
129            decode_measurement(&buf)
130        }
131
132        /// Read the 32-bit factory serial number.
133        pub fn read_serial_number(&mut self) -> Result<u32, Error<I2C::Error>> {
134            self.i2c
135                .write(self.address, &[commands::READ_SERIAL])
136                .map_err(Error::I2c)?;
137            self.delay.delay_us(commands::duration_us::SERIAL);
138            let mut buf = [0u8; SERIAL_LEN];
139            self.i2c
140                .read(self.address, &mut buf)
141                .map_err(Error::I2c)?;
142            decode_serial(&buf)
143        }
144
145        /// Issue a soft reset. Waits ~1 ms for the sensor to recover.
146        pub fn soft_reset(&mut self) -> Result<(), Error<I2C::Error>> {
147            self.i2c
148                .write(self.address, &[commands::SOFT_RESET])
149                .map_err(Error::I2c)?;
150            self.delay.delay_us(commands::duration_us::SOFT_RESET);
151            Ok(())
152        }
153    }
154}
155
156#[cfg(feature = "async")]
157#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
158mod async_impl {
159    use super::*;
160    use embedded_hal_async::delay::DelayNs;
161    use embedded_hal_async::i2c::I2c;
162
163    impl<I2C, D> Sht4x<I2C, D>
164    where
165        I2C: I2c,
166        D: DelayNs,
167    {
168        /// Async: trigger a measurement and read back temperature + humidity.
169        pub async fn measure_async(
170            &mut self,
171            precision: Precision,
172        ) -> Result<Measurement, Error<I2C::Error>> {
173            self.i2c
174                .write(self.address, &[precision.command()])
175                .await
176                .map_err(Error::I2c)?;
177            self.delay.delay_us(precision.duration_us()).await;
178            let mut buf = [0u8; MEASUREMENT_LEN];
179            self.i2c
180                .read(self.address, &mut buf)
181                .await
182                .map_err(Error::I2c)?;
183            decode_measurement(&buf)
184        }
185
186        /// Async: heater-assisted measurement.
187        pub async fn measure_with_heater_async(
188            &mut self,
189            power: HeaterPower,
190            duration: HeaterDuration,
191        ) -> Result<Measurement, Error<I2C::Error>> {
192            let cmd = types::heater_command(power, duration);
193            self.i2c
194                .write(self.address, &[cmd])
195                .await
196                .map_err(Error::I2c)?;
197            self.delay.delay_us(duration.duration_us()).await;
198            let mut buf = [0u8; MEASUREMENT_LEN];
199            self.i2c
200                .read(self.address, &mut buf)
201                .await
202                .map_err(Error::I2c)?;
203            decode_measurement(&buf)
204        }
205
206        /// Async: read the 32-bit factory serial number.
207        pub async fn read_serial_number_async(&mut self) -> Result<u32, Error<I2C::Error>> {
208            self.i2c
209                .write(self.address, &[commands::READ_SERIAL])
210                .await
211                .map_err(Error::I2c)?;
212            self.delay.delay_us(commands::duration_us::SERIAL).await;
213            let mut buf = [0u8; SERIAL_LEN];
214            self.i2c
215                .read(self.address, &mut buf)
216                .await
217                .map_err(Error::I2c)?;
218            decode_serial(&buf)
219        }
220
221        /// Async: issue a soft reset.
222        pub async fn soft_reset_async(&mut self) -> Result<(), Error<I2C::Error>> {
223            self.i2c
224                .write(self.address, &[commands::SOFT_RESET])
225                .await
226                .map_err(Error::I2c)?;
227            self.delay.delay_us(commands::duration_us::SOFT_RESET).await;
228            Ok(())
229        }
230    }
231}
232
233#[doc(hidden)]
234pub mod __private {
235    //! Re-exports for the integration tests; not a stable API.
236    pub use crate::crc::crc8;
237}