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