···1+//! # Introduction
2+//!
3+//! This is a platform agnostic Rust driver for the Sensirion SHTC3 temperature /
4+//! humidity sensor, based on the
5+//! [`embedded-hal`](https://github.com/rust-embedded/embedded-hal) traits.
6+//!
7+//! ## Supported Devices
8+//!
9+//! Tested with the following sensors:
10+//! - [SHTC3](https://www.sensirion.com/shtc3/)
11+//!
12+//! ## Blocking / Non-Blocking Modes
13+//!
14+//! This driver provides blocking and non-blocking calls. The blocking calls delay the execution
15+//! until the measurement is done and return the results. The non-blocking ones just start the
16+//! measurement and allow the application code to do other stuff and get the results afterwards.
17+//!
18+//! ## Clock Stretching
19+//!
20+//! While the sensor would provide measurement commands with clock stretching to indicate when the
21+//! measurement is done, this is not implemented and probably won't be.
22+//!
23+//! ## Usage
24+//!
25+//! ### Setup
26+//!
27+//! Instantiate a new driver instance using a [blocking I²C HAL
28+//! implementation](https://docs.rs/embedded-hal/0.2.*/embedded_hal/blocking/i2c/index.html)
29+//! and a [blocking `Delay`
30+//! instance](https://docs.rs/embedded-hal/0.2.*/embedded_hal/blocking/delay/index.html).
31+//! For example, using `linux-embedded-hal` and an SHTC3 sensor:
32+//!
33+//! ```no_run
34+//! use linux_embedded_hal::{Delay, I2cdev};
35+//! use sachy_shtc3::ShtC3;
36+//!
37+//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
38+//! let mut sht = ShtC3::new(dev);
39+//! ```
40+//!
41+//! ### Device Info
42+//!
43+//! Then, you can query information about the sensor:
44+//!
45+//! ```no_run
46+//! use linux_embedded_hal::{Delay, I2cdev};
47+//! use sachy_shtc3::ShtC3;
48+//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
49+//! let device_id = sht.device_identifier().unwrap();
50+//! let raw_id = sht.raw_id_register().unwrap();
51+//! ```
52+//!
53+//! ### Measurements (Blocking)
54+//!
55+//! For measuring your environment, you can either measure just temperature,
56+//! just humidity, or both:
57+//!
58+//! ```no_run
59+//! use linux_embedded_hal::{Delay, I2cdev};
60+//! use sachy_shtc3::{ShtC3, PowerMode};
61+//!
62+//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
63+//! let mut delay = Delay;
64+//!
65+//! let temperature = sht.measure_temperature(PowerMode::NormalMode, &mut delay).unwrap();
66+//! let humidity = sht.measure_humidity(PowerMode::NormalMode, &mut delay).unwrap();
67+//! let combined = sht.measure(PowerMode::NormalMode, &mut delay).unwrap();
68+//!
69+//! println!("Temperature: {} °C", temperature.as_degrees_celsius());
70+//! println!("Humidity: {} %RH", humidity.as_percent());
71+//! println!("Combined: {} °C / {} %RH",
72+//! combined.temperature.as_degrees_celsius(),
73+//! combined.humidity.as_percent());
74+//! ```
75+//!
76+//! You can also use the low power mode for less power consumption, at the cost
77+//! of reduced repeatability and accuracy of the sensor signals. For more
78+//! information, see the ["Low Power Measurement Mode" application note][low-power].
79+//!
80+//! [low-power]: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHTC3_Low_Power_Measurement_Mode.pdf
81+//!
82+//! ```no_run
83+//! use linux_embedded_hal::{Delay, I2cdev};
84+//! use sachy_shtc3::{ShtC3, PowerMode};
85+//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
86+//! let mut delay = Delay;
87+//! let measurement = sht.measure(PowerMode::LowPower, &mut delay).unwrap();
88+//! ```
89+//!
90+//! ### Measurements (Non-Blocking)
91+//!
92+//! If you want to avoid blocking measurements, you can use the non-blocking
93+//! commands instead. You are, however, responsible for ensuring the correct
94+//! timing of the calls.
95+//!
96+//! ```no_run
97+//! use linux_embedded_hal::I2cdev;
98+//! use sachy_shtc3::{ShtC3, PowerMode};
99+//!
100+//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
101+//!
102+//! sht.start_measurement(PowerMode::NormalMode).unwrap();
103+//! // Wait for at least `max_measurement_duration(&sht, PowerMode::NormalMode)` µs
104+//! let result = sht.get_measurement_result().unwrap();
105+//! ```
106+//!
107+//! In non-blocking mode, if desired, you can also read the raw 16-bit
108+//! measurement results from the sensor by using the following two methods
109+//! instead:
110+//!
111+//! - [`get_raw_measurement_result`](crate::ShtC3::get_raw_measurement_result())
112+//! - [`get_raw_partial_measurement_result`](crate::ShtC3::get_raw_partial_measurement_result())
113+//!
114+//! The raw values are of type u16. They require a conversion formula for
115+//! conversion to a temperature / humidity value (see datasheet).
116+//!
117+//! Invoking any command other than
118+//! [`wakeup`](crate::ShtC3::wakeup()) while the sensor is in
119+//! sleep mode will result in an error.
120+//!
121+//! ### Soft Reset
122+//!
123+//! The SHTC3 provides a soft reset mechanism that forces the system into a
124+//! well-defined state without removing the power supply. If the system is in
125+//! its idle state (i.e. if no measurement is in progress) the soft reset
126+//! command can be sent. This triggers the sensor to reset all internal state
127+//! machines and reload calibration data from the memory.
128+//!
129+//! ```no_run
130+//! use linux_embedded_hal::{Delay, I2cdev};
131+//! use sachy_shtc3::{ShtC3, PowerMode};
132+//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
133+//! let mut delay = Delay;
134+//! sht.reset(&mut delay).unwrap();
135+//! ```
136+#![deny(unsafe_code, missing_docs)]
137+#![no_std]
138+139+mod crc;
140+mod types;
141+142+use embedded_hal::{
143+ delay::DelayNs as BlockingDelayNs,
144+ i2c::{self, I2c, SevenBitAddress},
145+};
146+147+use crc::crc8;
148+use embedded_hal_async::delay::DelayNs;
149+pub use types::*;
150+151+/// Whether temperature or humidity is returned first when doing a measurement.
152+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
153+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
154+enum MeasurementOrder {
155+ TemperatureFirst,
156+ HumidityFirst,
157+}
158+159+/// Measurement power mode: Normal mode or low power mode.
160+///
161+/// The sensors provides a low power measurement mode. Using the low power mode
162+/// significantly shortens the measurement duration and thus minimizes the
163+/// energy consumption per measurement. The benefit of ultra-low power
164+/// consumption comes at the cost of reduced repeatability of the sensor
165+/// signals: while the impact on the relative humidity signal is negligible and
166+/// does not affect accuracy, it has an effect on temperature accuracy.
167+///
168+/// More details can be found in the ["Low Power Measurement Mode" application
169+/// note][an-low-power] by Sensirion.
170+///
171+/// [an-low-power]: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHTC3_Low_Power_Measurement_Mode.pdf
172+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
173+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
174+pub enum PowerMode {
175+ /// Normal measurement.
176+ NormalMode,
177+ /// Low power measurement: Less energy consumption, but repeatability and
178+ /// accuracy of measurements are negatively impacted.
179+ LowPower,
180+}
181+182+/// All possible errors in this crate
183+#[derive(Debug, PartialEq, Clone)]
184+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
185+pub enum Error<E> {
186+ /// I²C bus error
187+ I2c(E),
188+ /// CRC checksum validation failed
189+ Crc,
190+}
191+192+impl<E> From<CrcError> for Error<E> {
193+ fn from(_value: CrcError) -> Self {
194+ Self::Crc
195+ }
196+}
197+198+#[derive(Debug, PartialEq, Clone)]
199+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
200+struct CrcError;
201+202+impl<E> From<E> for Error<E>
203+where
204+ E: i2c::Error,
205+{
206+ fn from(e: E) -> Self {
207+ Error::I2c(e)
208+ }
209+}
210+211+/// I²C commands sent to the sensor.
212+#[derive(Debug, Copy, Clone)]
213+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
214+enum Command {
215+ /// Go into sleep mode.
216+ Sleep,
217+ /// Wake up from sleep mode.
218+ WakeUp,
219+ /// Measurement commands.
220+ Measure {
221+ power_mode: PowerMode,
222+ order: MeasurementOrder,
223+ },
224+ /// Software reset.
225+ SoftwareReset,
226+ /// Read ID register.
227+ ReadIdRegister,
228+}
229+230+impl Command {
231+ fn as_bytes(self) -> [u8; 2] {
232+ match self {
233+ Command::Sleep => [0xB0, 0x98],
234+ Command::WakeUp => [0x35, 0x17],
235+ Command::Measure {
236+ power_mode: PowerMode::NormalMode,
237+ order: MeasurementOrder::TemperatureFirst,
238+ } => [0x78, 0x66],
239+ Command::Measure {
240+ power_mode: PowerMode::NormalMode,
241+ order: MeasurementOrder::HumidityFirst,
242+ } => [0x58, 0xE0],
243+ Command::Measure {
244+ power_mode: PowerMode::LowPower,
245+ order: MeasurementOrder::TemperatureFirst,
246+ } => [0x60, 0x9C],
247+ Command::Measure {
248+ power_mode: PowerMode::LowPower,
249+ order: MeasurementOrder::HumidityFirst,
250+ } => [0x40, 0x1A],
251+ Command::ReadIdRegister => [0xEF, 0xC8],
252+ Command::SoftwareReset => [0x80, 0x5D],
253+ }
254+ }
255+}
256+257+/// Driver for the SHTC3 sensor.
258+#[derive(Debug, Default)]
259+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
260+pub struct ShtC3<I2C> {
261+ /// The concrete I²C device implementation.
262+ i2c: I2C,
263+ /// The I²C device address.
264+ address: u8,
265+}
266+267+impl<I2C> ShtC3<I2C> {
268+ /// Create a new instance of the driver for the SHTC3.
269+ #[inline]
270+ pub const fn new(i2c: I2C) -> Self {
271+ Self { i2c, address: 0x70 }
272+ }
273+274+ /// Get the device's wakeup delay duration in microseconds
275+ #[inline(always)]
276+ pub const fn wakeup_duration(&self) -> u32 {
277+ 240
278+ }
279+280+ /// Destroy driver instance, return I²C bus instance.
281+ pub fn destroy(self) -> I2C {
282+ self.i2c
283+ }
284+285+ /// Return the maximum measurement duration (depending on the mode) in
286+ /// microseconds.
287+ ///
288+ /// Maximum measurement duration (SHTC3 datasheet 3.1):
289+ /// - Normal mode: 12.1 ms
290+ /// - Low power mode: 0.8 ms
291+ #[inline(always)]
292+ pub const fn max_measurement_duration(&self, mode: PowerMode) -> u32 {
293+ match mode {
294+ PowerMode::NormalMode => 12100,
295+ PowerMode::LowPower => 800,
296+ }
297+ }
298+299+ /// Returns the reset duration for the SHTC3 in microseconds
300+ #[inline(always)]
301+ pub const fn reset_duration(&self) -> u32 {
302+ 240_000
303+ }
304+305+ /// Iterate over the provided buffer and validate the CRC8 checksum.
306+ ///
307+ /// If the checksum is wrong, return `CrcError`.
308+ ///
309+ /// Note: This method will consider every third byte a checksum byte. If
310+ /// the buffer size is not a multiple of 3, then not all data will be
311+ /// validated.
312+ fn validate_crc(&self, buf: &[u8]) -> Result<(), CrcError> {
313+ let mut chunks = buf.chunks_exact(3);
314+315+ for chunk in chunks.by_ref() {
316+ if crc8(&chunk[..2]) != chunk[2] {
317+ return Err(CrcError);
318+ }
319+ }
320+321+ #[cfg(feature = "defmt")]
322+ if !chunks.remainder().is_empty() {
323+ defmt::warn!("Remaining data in buffer was not CRC8 validated");
324+ }
325+326+ Ok(())
327+ }
328+}
329+330+impl<I2C> ShtC3<I2C>
331+where
332+ I2C: embedded_hal_async::i2c::I2c<embedded_hal_async::i2c::SevenBitAddress>,
333+{
334+ /// Write an I²C command to the sensor.
335+ async fn send_command_async(&mut self, command: Command) -> Result<(), Error<I2C::Error>> {
336+ self.i2c
337+ .write(self.address, &command.as_bytes())
338+ .await
339+ .map_err(Error::I2c)
340+ }
341+342+ /// Read data into the provided buffer and validate the CRC8 checksum.
343+ ///
344+ /// If the checksum is wrong, return `Error::Crc`.
345+ ///
346+ /// Note: This method will consider every third byte a checksum byte. If
347+ /// the buffer size is not a multiple of 3, then not all data will be
348+ /// validated.
349+ async fn read_with_crc_async(&mut self, buf: &mut [u8]) -> Result<(), Error<I2C::Error>> {
350+ self.i2c.read(self.address, buf).await?;
351+ self.validate_crc(buf)?;
352+ Ok(())
353+ }
354+355+ /// Return the raw ID register.
356+ pub async fn raw_id_register_async(&mut self) -> Result<u16, Error<I2C::Error>> {
357+ // Request serial number
358+ self.send_command_async(Command::ReadIdRegister).await?;
359+360+ // Read id register
361+ let mut buf = [0; 3];
362+ self.read_with_crc_async(&mut buf).await?;
363+364+ Ok(u16::from_be_bytes([buf[0], buf[1]]))
365+ }
366+367+ /// Return the 7-bit device identifier.
368+ ///
369+ /// Should be 0x47 (71) for the SHTC3.
370+ pub async fn device_identifier_async(&mut self) -> Result<u8, Error<I2C::Error>> {
371+ let ident = self.raw_id_register_async().await?;
372+ let lsb = (ident & 0b0011_1111) as u8;
373+ let msb = ((ident & 0b0000_1000_0000_0000) >> 5) as u8;
374+ Ok(lsb | msb)
375+ }
376+377+ /// Set sensor to sleep mode.
378+ ///
379+ /// When in sleep mode, the sensor consumes around 0.3-0.6 µA. It requires
380+ /// a dedicated [`wakeup`](#method.wakeup) command to enable further I2C
381+ /// communication.
382+ pub async fn sleep_async(&mut self) -> Result<(), Error<I2C::Error>> {
383+ self.send_command_async(Command::Sleep).await
384+ }
385+386+ /// Trigger a soft reset. (async)
387+ ///
388+ /// The SHTC3 provides a soft reset mechanism that forces the system into a
389+ /// well-defined state without removing the power supply. If the system is
390+ /// in its idle state (i.e. if no measurement is in progress) the soft
391+ /// reset command can be sent. This triggers the sensor to reset all
392+ /// internal state machines and reload calibration data from the memory.
393+ pub async fn reset_async(&mut self, delay: &mut impl DelayNs) -> Result<(), Error<I2C::Error>> {
394+ self.send_command_async(Command::SoftwareReset).await?;
395+ // Table 5: 180-240 µs
396+ delay.delay_us(self.reset_duration()).await;
397+ Ok(())
398+ }
399+400+ /// Wake up sensor from [sleep mode](#method.sleep) and wait until it is ready. (async)
401+ pub async fn wakeup_async(
402+ &mut self,
403+ delay: &mut impl DelayNs,
404+ ) -> Result<(), Error<I2C::Error>> {
405+ self.send_command_async(Command::WakeUp).await?;
406+ delay.delay_us(self.wakeup_duration()).await;
407+ Ok(())
408+ }
409+410+ /// Run a temperature/humidity measurement and return the combined result.
411+ ///
412+ /// This is an async function call.
413+ pub async fn measure_async(
414+ &mut self,
415+ mode: PowerMode,
416+ delay: &mut impl DelayNs,
417+ ) -> Result<Measurement, Error<I2C::Error>> {
418+ self.send_command_async(Command::Measure {
419+ power_mode: mode,
420+ order: MeasurementOrder::TemperatureFirst,
421+ })
422+ .await?;
423+424+ delay.delay_us(self.max_measurement_duration(mode)).await;
425+426+ let mut buf = [0; 6];
427+ self.read_with_crc_async(&mut buf).await?;
428+429+ Ok(RawMeasurement {
430+ temperature: u16::from_be_bytes([buf[0], buf[1]]),
431+ humidity: u16::from_be_bytes([buf[3], buf[4]]),
432+ }
433+ .into())
434+ }
435+}
436+437+/// General blocking functions.
438+impl<I2C> ShtC3<I2C>
439+where
440+ I2C: I2c<SevenBitAddress>,
441+{
442+ /// Write an I²C command to the sensor.
443+ fn send_command(&mut self, command: Command) -> Result<(), Error<I2C::Error>> {
444+ self.i2c
445+ .write(self.address, &command.as_bytes())
446+ .map_err(Error::I2c)
447+ }
448+449+ /// Read data into the provided buffer and validate the CRC8 checksum.
450+ ///
451+ /// If the checksum is wrong, return `Error::Crc`.
452+ ///
453+ /// Note: This method will consider every third byte a checksum byte. If
454+ /// the buffer size is not a multiple of 3, then not all data will be
455+ /// validated.
456+ fn read_with_crc(&mut self, buf: &mut [u8]) -> Result<(), Error<I2C::Error>> {
457+ self.i2c.read(self.address, buf)?;
458+ self.validate_crc(buf)?;
459+ Ok(())
460+ }
461+462+ /// Return the raw ID register.
463+ pub fn raw_id_register(&mut self) -> Result<u16, Error<I2C::Error>> {
464+ // Request serial number
465+ self.send_command(Command::ReadIdRegister)?;
466+467+ // Read id register
468+ let mut buf = [0; 3];
469+ self.read_with_crc(&mut buf)?;
470+471+ Ok(u16::from_be_bytes([buf[0], buf[1]]))
472+ }
473+474+ /// Return the 7-bit device identifier.
475+ ///
476+ /// Should be 0x47 (71) for the SHTC3.
477+ pub fn device_identifier(&mut self) -> Result<u8, Error<I2C::Error>> {
478+ let ident = self.raw_id_register()?;
479+ let lsb = (ident & 0b0011_1111) as u8;
480+ let msb = ((ident & 0b0000_1000_0000_0000) >> 5) as u8;
481+ Ok(lsb | msb)
482+ }
483+484+ /// Trigger a soft reset. (blocking)
485+ ///
486+ /// The SHTC3 provides a soft reset mechanism that forces the system into a
487+ /// well-defined state without removing the power supply. If the system is
488+ /// in its idle state (i.e. if no measurement is in progress) the soft
489+ /// reset command can be sent. This triggers the sensor to reset all
490+ /// internal state machines and reload calibration data from the memory.
491+ pub fn reset(&mut self, delay: &mut impl BlockingDelayNs) -> Result<(), Error<I2C::Error>> {
492+ self.send_command(Command::SoftwareReset)?;
493+ // Table 5: 180-240 µs
494+ delay.delay_us(self.reset_duration());
495+ Ok(())
496+ }
497+498+ /// Trigger a soft reset.
499+ ///
500+ /// The SHTC3 provides a soft reset mechanism that forces the system into a
501+ /// well-defined state without removing the power supply. If the system is
502+ /// in its idle state (i.e. if no measurement is in progress) the soft
503+ /// reset command can be sent. This triggers the sensor to reset all
504+ /// internal state machines and reload calibration data from the memory.
505+ pub fn start_reset(&mut self) -> Result<(), Error<I2C::Error>> {
506+ self.send_command(Command::SoftwareReset)
507+ }
508+509+ /// Set sensor to sleep mode.
510+ ///
511+ /// When in sleep mode, the sensor consumes around 0.3-0.6 µA. It requires
512+ /// a dedicated [`wakeup`](#method.wakeup) command to enable further I2C
513+ /// communication.
514+ pub fn sleep(&mut self) -> Result<(), Error<I2C::Error>> {
515+ self.send_command(Command::Sleep)
516+ }
517+518+ /// Wake up sensor from [sleep mode](#method.sleep) and wait until it is ready.
519+ pub fn wakeup(&mut self, delay: &mut impl BlockingDelayNs) -> Result<(), Error<I2C::Error>> {
520+ self.start_wakeup()?;
521+ delay.delay_us(self.wakeup_duration());
522+ Ok(())
523+ }
524+}
525+526+/// Non-blocking functions for starting / reading measurements.
527+impl<I2C> ShtC3<I2C>
528+where
529+ I2C: I2c<SevenBitAddress>,
530+{
531+ /// Start a measurement with the specified measurement order and write the
532+ /// result into the provided buffer.
533+ ///
534+ /// If you just need one of the two measurements, provide a 3-byte buffer
535+ /// instead of a 6-byte buffer.
536+ fn start_measure_partial(
537+ &mut self,
538+ power_mode: PowerMode,
539+ order: MeasurementOrder,
540+ ) -> Result<(), Error<I2C::Error>> {
541+ // Request measurement
542+ self.send_command(Command::Measure { power_mode, order })
543+ }
544+545+ /// Start a combined temperature / humidity measurement.
546+ pub fn start_measurement(&mut self, mode: PowerMode) -> Result<(), Error<I2C::Error>> {
547+ self.start_measure_partial(mode, MeasurementOrder::TemperatureFirst)
548+ }
549+550+ /// Start a temperature measurement.
551+ pub fn start_temperature_measurement(
552+ &mut self,
553+ mode: PowerMode,
554+ ) -> Result<(), Error<I2C::Error>> {
555+ self.start_measure_partial(mode, MeasurementOrder::TemperatureFirst)
556+ }
557+558+ /// Start a humidity measurement.
559+ pub fn start_humidity_measurement(&mut self, mode: PowerMode) -> Result<(), Error<I2C::Error>> {
560+ self.start_measure_partial(mode, MeasurementOrder::HumidityFirst)
561+ }
562+563+ /// Read the result of a temperature / humidity measurement.
564+ pub fn get_measurement_result(&mut self) -> Result<Measurement, Error<I2C::Error>> {
565+ let raw = self.get_raw_measurement_result()?;
566+ Ok(raw.into())
567+ }
568+569+ /// Read the result of a temperature measurement.
570+ pub fn get_temperature_measurement_result(&mut self) -> Result<Temperature, Error<I2C::Error>> {
571+ let raw = self.get_raw_partial_measurement_result()?;
572+ Ok(Temperature::from_raw(raw))
573+ }
574+575+ /// Read the result of a humidity measurement.
576+ pub fn get_humidity_measurement_result(&mut self) -> Result<Humidity, Error<I2C::Error>> {
577+ let raw = self.get_raw_partial_measurement_result()?;
578+ Ok(Humidity::from_raw(raw))
579+ }
580+581+ /// Read the raw result of a combined temperature / humidity measurement.
582+ pub fn get_raw_measurement_result(&mut self) -> Result<RawMeasurement, Error<I2C::Error>> {
583+ let mut buf = [0; 6];
584+ self.read_with_crc(&mut buf)?;
585+ Ok(RawMeasurement {
586+ temperature: u16::from_be_bytes([buf[0], buf[1]]),
587+ humidity: u16::from_be_bytes([buf[3], buf[4]]),
588+ })
589+ }
590+591+ /// Read the raw result of a partial temperature or humidity measurement.
592+ ///
593+ /// Return the raw 3-byte buffer (after validating CRC).
594+ pub fn get_raw_partial_measurement_result(&mut self) -> Result<u16, Error<I2C::Error>> {
595+ let mut buf = [0; 3];
596+ self.read_with_crc(&mut buf)?;
597+ Ok(u16::from_be_bytes([buf[0], buf[1]]))
598+ }
599+600+ /// Wake up sensor from [sleep mode](#method.sleep).
601+ pub fn start_wakeup(&mut self) -> Result<(), Error<I2C::Error>> {
602+ self.send_command(Command::WakeUp)
603+ }
604+}
605+606+/// Blocking functions for doing measurements.
607+impl<I2C> ShtC3<I2C>
608+where
609+ I2C: I2c<SevenBitAddress>,
610+{
611+ /// Wait the maximum time needed for the given measurement mode
612+ pub fn wait_for_measurement(&mut self, mode: PowerMode, delay: &mut impl BlockingDelayNs) {
613+ delay.delay_us(self.max_measurement_duration(mode));
614+ }
615+616+ /// Run a temperature/humidity measurement and return the combined result.
617+ ///
618+ /// This is a blocking function call.
619+ pub fn measure(
620+ &mut self,
621+ mode: PowerMode,
622+ delay: &mut impl BlockingDelayNs,
623+ ) -> Result<Measurement, Error<I2C::Error>> {
624+ self.start_measurement(mode)?;
625+ self.wait_for_measurement(mode, delay);
626+ self.get_measurement_result()
627+ }
628+629+ /// Run a temperature measurement and return the result.
630+ ///
631+ /// This is a blocking function call.
632+ ///
633+ /// Internally, it will request a measurement in "temperature first" mode
634+ /// and only read the first half of the measurement response.
635+ pub fn measure_temperature(
636+ &mut self,
637+ mode: PowerMode,
638+ delay: &mut impl BlockingDelayNs,
639+ ) -> Result<Temperature, Error<I2C::Error>> {
640+ self.start_temperature_measurement(mode)?;
641+ self.wait_for_measurement(mode, delay);
642+ self.get_temperature_measurement_result()
643+ }
644+645+ /// Run a humidity measurement and return the result.
646+ ///
647+ /// This is a blocking function call.
648+ ///
649+ /// Internally, it will request a measurement in "humidity first" mode
650+ /// and only read the first half of the measurement response.
651+ pub fn measure_humidity(
652+ &mut self,
653+ mode: PowerMode,
654+ delay: &mut impl BlockingDelayNs,
655+ ) -> Result<Humidity, Error<I2C::Error>> {
656+ self.start_humidity_measurement(mode)?;
657+ self.wait_for_measurement(mode, delay);
658+ self.get_humidity_measurement_result()
659+ }
660+}
661+662+#[cfg(test)]
663+mod tests {
664+ extern crate alloc;
665+666+ use super::*;
667+668+ use embedded_hal::i2c::ErrorKind;
669+ use embedded_hal_mock::eh1::{
670+ delay::NoopDelay,
671+ i2c::{Mock as I2cMock, Transaction},
672+ };
673+674+ const SHT_ADDR: u8 = 0x70;
675+676+ mod core {
677+ use super::*;
678+679+ /// Test whether the `send_command` function propagates I²C errors.
680+ #[test]
681+ fn send_command_error() {
682+ let expectations =
683+ [Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8])
684+ .with_error(ErrorKind::Other)];
685+ let mock = I2cMock::new(&expectations);
686+ let mut sht = ShtC3::new(mock);
687+ let err = sht.send_command(Command::ReadIdRegister).unwrap_err();
688+ assert_eq!(err, Error::I2c(ErrorKind::Other));
689+ sht.destroy().done();
690+ }
691+692+ /// Test the `validate_crc` function.
693+ #[test]
694+ fn validate_crc() {
695+ let mock = I2cMock::new(&[]);
696+ let sht = ShtC3::new(mock);
697+698+ // Not enough data
699+ sht.validate_crc(&[]).unwrap();
700+ sht.validate_crc(&[0xbe]).unwrap();
701+ sht.validate_crc(&[0xbe, 0xef]).unwrap();
702+703+ // Valid CRC
704+ sht.validate_crc(&[0xbe, 0xef, 0x92]).unwrap();
705+706+ // Invalid CRC
707+ match sht.validate_crc(&[0xbe, 0xef, 0x91]) {
708+ Err(CrcError) => {}
709+ Ok(_) => panic!("CRC check did not fail"),
710+ }
711+712+ // Valid CRC (8 bytes)
713+ sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0x92, 0x00, 0x00])
714+ .unwrap();
715+716+ // Invalid CRC (8 bytes)
717+ match sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0xff, 0x00, 0x00]) {
718+ Err(CrcError) => {}
719+ Ok(_) => panic!("CRC check did not fail"),
720+ }
721+722+ sht.destroy().done();
723+ }
724+725+ /// Test the `read_with_crc` function.
726+ #[test]
727+ fn read_with_crc() {
728+ let mut buf = [0; 3];
729+730+ // Valid CRC
731+ let expectations = [Transaction::read(SHT_ADDR, alloc::vec![0xbe, 0xef, 0x92])];
732+ let mock = I2cMock::new(&expectations);
733+ let mut sht = ShtC3::new(mock);
734+ sht.read_with_crc(&mut buf).unwrap();
735+ assert_eq!(buf, [0xbe, 0xef, 0x92]);
736+ sht.destroy().done();
737+738+ // Invalid CRC
739+ let expectations = [Transaction::read(SHT_ADDR, alloc::vec![0xbe, 0xef, 0x00])];
740+ let mock = I2cMock::new(&expectations);
741+ let mut sht = ShtC3::new(mock);
742+ match sht.read_with_crc(&mut buf) {
743+ Err(Error::Crc) => {}
744+ Err(_) => panic!("Invalid error: Must be Crc"),
745+ Ok(_) => panic!("CRC check did not fail"),
746+ }
747+ assert_eq!(buf, [0xbe, 0xef, 0x00]); // Buf was changed
748+ sht.destroy().done();
749+ }
750+ }
751+752+ mod factory_functions {
753+ use super::*;
754+755+ #[test]
756+ fn new_shtc3() {
757+ let mock = I2cMock::new(&[]);
758+ let sht = ShtC3::new(mock);
759+ assert_eq!(sht.address, 0x70);
760+ sht.destroy().done();
761+ }
762+ }
763+764+ mod device_info {
765+ use super::*;
766+767+ /// Test the `raw_id_register` function.
768+ #[test]
769+ fn raw_id_register() {
770+ let msb = 0b00001000;
771+ let lsb = 0b00000111;
772+ let crc = crc8(&[msb, lsb]);
773+ let expectations = [
774+ Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]),
775+ Transaction::read(SHT_ADDR, alloc::vec![msb, lsb, crc]),
776+ ];
777+ let mock = I2cMock::new(&expectations);
778+ let mut sht = ShtC3::new(mock);
779+ let val = sht.raw_id_register().unwrap();
780+ assert_eq!(val, (msb as u16) << 8 | (lsb as u16));
781+ sht.destroy().done();
782+ }
783+784+ /// Test the `device_identifier` function.
785+ #[test]
786+ fn device_identifier() {
787+ let msb = 0b00001000;
788+ let lsb = 0b00000111;
789+ let crc = crc8(&[msb, lsb]);
790+ let expectations = [
791+ Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]),
792+ Transaction::read(SHT_ADDR, alloc::vec![msb, lsb, crc]),
793+ ];
794+ let mock = I2cMock::new(&expectations);
795+ let mut sht = ShtC3::new(mock);
796+ let ident = sht.device_identifier().unwrap();
797+ assert_eq!(ident, 0b01000111);
798+ sht.destroy().done();
799+ }
800+ }
801+802+ mod measurements {
803+ use super::*;
804+805+ #[test]
806+ fn measure_normal() {
807+ let expectations = [
808+ // Expect a write command: Normal mode measurement, temperature
809+ // first, no clock stretching.
810+ Transaction::write(SHT_ADDR, alloc::vec![0x78, 0x66]),
811+ // Return the measurement result (using example values from the
812+ // datasheet, section 5.4 "Measuring and Reading the Signals")
813+ Transaction::read(
814+ SHT_ADDR,
815+ alloc::vec![
816+ 0b0110_0100,
817+ 0b1000_1011,
818+ 0b1100_0111,
819+ 0b1010_0001,
820+ 0b0011_0011,
821+ 0b0001_1100,
822+ ],
823+ ),
824+ ];
825+ let mock = I2cMock::new(&expectations);
826+ let mut sht = ShtC3::new(mock);
827+ let mut delay = NoopDelay;
828+ let measurement = sht.measure(PowerMode::NormalMode, &mut delay).unwrap();
829+ assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); // 23.7°C
830+ assert_eq!(measurement.humidity.as_millipercent(), 62_968); // 62.9 %RH
831+ sht.destroy().done();
832+ }
833+834+ #[test]
835+ fn measure_low_power() {
836+ let expectations = [
837+ // Expect a write command: Low power mode measurement, temperature
838+ // first, no clock stretching.
839+ Transaction::write(SHT_ADDR, alloc::vec![0x60, 0x9C]),
840+ // Return the measurement result (using example values from the
841+ // datasheet, section 5.4 "Measuring and Reading the Signals")
842+ Transaction::read(
843+ SHT_ADDR,
844+ alloc::vec![
845+ 0b0110_0100,
846+ 0b1000_1011,
847+ 0b1100_0111,
848+ 0b1010_0001,
849+ 0b0011_0011,
850+ 0b0001_1100,
851+ ],
852+ ),
853+ ];
854+ let mock = I2cMock::new(&expectations);
855+ let mut sht = ShtC3::new(mock);
856+ let mut delay = NoopDelay;
857+ let measurement = sht.measure(PowerMode::LowPower, &mut delay).unwrap();
858+ assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); // 23.7°C
859+ assert_eq!(measurement.humidity.as_millipercent(), 62_968); // 62.9 %RH
860+ sht.destroy().done();
861+ }
862+863+ #[test]
864+ fn measure_temperature_only() {
865+ let expectations = [
866+ // Expect a write command: Normal mode measurement, temperature
867+ // first, no clock stretching.
868+ Transaction::write(SHT_ADDR, alloc::vec![0x78, 0x66]),
869+ // Return the measurement result (using example values from the
870+ // datasheet, section 5.4 "Measuring and Reading the Signals")
871+ Transaction::read(SHT_ADDR, alloc::vec![0b0110_0100, 0b1000_1011, 0b1100_0111]),
872+ ];
873+ let mock = I2cMock::new(&expectations);
874+ let mut sht = ShtC3::new(mock);
875+ let mut delay = NoopDelay;
876+ let temperature = sht
877+ .measure_temperature(PowerMode::NormalMode, &mut delay)
878+ .unwrap();
879+ assert_eq!(temperature.as_millidegrees_celsius(), 23_730); // 23.7°C
880+ sht.destroy().done();
881+ }
882+883+ #[test]
884+ fn measure_humidity_only() {
885+ let expectations = [
886+ // Expect a write command: Normal mode measurement, humidity
887+ // first, no clock stretching.
888+ Transaction::write(SHT_ADDR, alloc::vec![0x58, 0xE0]),
889+ // Return the measurement result (using example values from the
890+ // datasheet, section 5.4 "Measuring and Reading the Signals")
891+ Transaction::read(SHT_ADDR, alloc::vec![0b1010_0001, 0b0011_0011, 0b0001_1100]),
892+ ];
893+ let mock = I2cMock::new(&expectations);
894+ let mut sht = ShtC3::new(mock);
895+ let mut delay = NoopDelay;
896+ let humidity = sht
897+ .measure_humidity(PowerMode::NormalMode, &mut delay)
898+ .unwrap();
899+ assert_eq!(humidity.as_millipercent(), 62_968); // 62.9 %RH
900+ sht.destroy().done();
901+ }
902+903+ /// Ensure that I²C write errors are handled when measuring.
904+ #[test]
905+ fn measure_write_error() {
906+ let expectations =
907+ [Transaction::write(SHT_ADDR, alloc::vec![0x60, 0x9C])
908+ .with_error(ErrorKind::Other)];
909+ let mock = I2cMock::new(&expectations);
910+ let mut sht = ShtC3::new(mock);
911+ let err = sht
912+ .measure(PowerMode::LowPower, &mut NoopDelay)
913+ .unwrap_err();
914+ assert_eq!(err, Error::I2c(ErrorKind::Other));
915+ sht.destroy().done();
916+ }
917+ }
918+919+ mod power_management {
920+ use super::*;
921+922+ /// Test the `sleep` function.
923+ #[test]
924+ fn sleep() {
925+ let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0xB0, 0x98])];
926+ let mock = I2cMock::new(&expectations);
927+ let mut sht = ShtC3::new(mock);
928+ sht.sleep().unwrap();
929+ sht.destroy().done();
930+ }
931+932+ /// Test the `wakeup` function.
933+ #[test]
934+ fn wakeup() {
935+ let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0x35, 0x17])];
936+ let mock = I2cMock::new(&expectations);
937+ let mut sht = ShtC3::new(mock);
938+ sht.wakeup(&mut NoopDelay).unwrap();
939+ sht.destroy().done();
940+ }
941+942+ /// Test the `reset` function.
943+ #[test]
944+ fn reset() {
945+ let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0x80, 0x5D])];
946+ let mock = I2cMock::new(&expectations);
947+ let mut sht = ShtC3::new(mock);
948+ sht.reset(&mut NoopDelay).unwrap();
949+ sht.destroy().done();
950+ }
951+ }
952+953+ mod max_measurement_duration {
954+ use super::*;
955+956+ #[test]
957+ fn shortcut_function() {
958+ let c3 = ShtC3::new(I2cMock::new(&[]));
959+960+ assert_eq!(c3.max_measurement_duration(PowerMode::NormalMode), 12100);
961+ assert_eq!(c3.max_measurement_duration(PowerMode::LowPower), 800);
962+963+ c3.destroy().done();
964+ }
965+ }
966+}