···
+
//! This is a platform agnostic Rust driver for the Sensirion SHTC3 temperature /
+
//! humidity sensor, based on the
+
//! [`embedded-hal`](https://github.com/rust-embedded/embedded-hal) traits.
+
//! ## Supported Devices
+
//! Tested with the following sensors:
+
//! - [SHTC3](https://www.sensirion.com/shtc3/)
+
//! ## Blocking / Non-Blocking Modes
+
//! This driver provides blocking and non-blocking calls. The blocking calls delay the execution
+
//! until the measurement is done and return the results. The non-blocking ones just start the
+
//! measurement and allow the application code to do other stuff and get the results afterwards.
+
//! ## Clock Stretching
+
//! While the sensor would provide measurement commands with clock stretching to indicate when the
+
//! measurement is done, this is not implemented and probably won't be.
+
//! Instantiate a new driver instance using a [blocking I²C HAL
+
//! implementation](https://docs.rs/embedded-hal/0.2.*/embedded_hal/blocking/i2c/index.html)
+
//! and a [blocking `Delay`
+
//! instance](https://docs.rs/embedded-hal/0.2.*/embedded_hal/blocking/delay/index.html).
+
//! For example, using `linux-embedded-hal` and an SHTC3 sensor:
+
//! use linux_embedded_hal::{Delay, I2cdev};
+
//! use sachy_shtc3::ShtC3;
+
//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
+
//! let mut sht = ShtC3::new(dev);
+
//! Then, you can query information about the sensor:
+
//! use linux_embedded_hal::{Delay, I2cdev};
+
//! use sachy_shtc3::ShtC3;
+
//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
+
//! let device_id = sht.device_identifier().unwrap();
+
//! let raw_id = sht.raw_id_register().unwrap();
+
//! ### Measurements (Blocking)
+
//! For measuring your environment, you can either measure just temperature,
+
//! just humidity, or both:
+
//! use linux_embedded_hal::{Delay, I2cdev};
+
//! use sachy_shtc3::{ShtC3, PowerMode};
+
//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
+
//! let mut delay = Delay;
+
//! let temperature = sht.measure_temperature(PowerMode::NormalMode, &mut delay).unwrap();
+
//! let humidity = sht.measure_humidity(PowerMode::NormalMode, &mut delay).unwrap();
+
//! let combined = sht.measure(PowerMode::NormalMode, &mut delay).unwrap();
+
//! println!("Temperature: {} °C", temperature.as_degrees_celsius());
+
//! println!("Humidity: {} %RH", humidity.as_percent());
+
//! println!("Combined: {} °C / {} %RH",
+
//! combined.temperature.as_degrees_celsius(),
+
//! combined.humidity.as_percent());
+
//! You can also use the low power mode for less power consumption, at the cost
+
//! of reduced repeatability and accuracy of the sensor signals. For more
+
//! information, see the ["Low Power Measurement Mode" application note][low-power].
+
//! [low-power]: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHTC3_Low_Power_Measurement_Mode.pdf
+
//! use linux_embedded_hal::{Delay, I2cdev};
+
//! use sachy_shtc3::{ShtC3, PowerMode};
+
//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
+
//! let mut delay = Delay;
+
//! let measurement = sht.measure(PowerMode::LowPower, &mut delay).unwrap();
+
//! ### Measurements (Non-Blocking)
+
//! If you want to avoid blocking measurements, you can use the non-blocking
+
//! commands instead. You are, however, responsible for ensuring the correct
+
//! timing of the calls.
+
//! use linux_embedded_hal::I2cdev;
+
//! use sachy_shtc3::{ShtC3, PowerMode};
+
//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
+
//! sht.start_measurement(PowerMode::NormalMode).unwrap();
+
//! // Wait for at least `max_measurement_duration(&sht, PowerMode::NormalMode)` µs
+
//! let result = sht.get_measurement_result().unwrap();
+
//! In non-blocking mode, if desired, you can also read the raw 16-bit
+
//! measurement results from the sensor by using the following two methods
+
//! - [`get_raw_measurement_result`](crate::ShtC3::get_raw_measurement_result())
+
//! - [`get_raw_partial_measurement_result`](crate::ShtC3::get_raw_partial_measurement_result())
+
//! The raw values are of type u16. They require a conversion formula for
+
//! conversion to a temperature / humidity value (see datasheet).
+
//! Invoking any command other than
+
//! [`wakeup`](crate::ShtC3::wakeup()) while the sensor is in
+
//! sleep mode will result in an error.
+
//! The SHTC3 provides a soft reset mechanism that forces the system into a
+
//! well-defined state without removing the power supply. If the system is in
+
//! its idle state (i.e. if no measurement is in progress) the soft reset
+
//! command can be sent. This triggers the sensor to reset all internal state
+
//! machines and reload calibration data from the memory.
+
//! use linux_embedded_hal::{Delay, I2cdev};
+
//! use sachy_shtc3::{ShtC3, PowerMode};
+
//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
+
//! let mut delay = Delay;
+
//! sht.reset(&mut delay).unwrap();
+
#![deny(unsafe_code, missing_docs)]
+
delay::DelayNs as BlockingDelayNs,
+
i2c::{self, I2c, SevenBitAddress},
+
use embedded_hal_async::delay::DelayNs;
+
/// Whether temperature or humidity is returned first when doing a measurement.
+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
enum MeasurementOrder {
+
/// Measurement power mode: Normal mode or low power mode.
+
/// The sensors provides a low power measurement mode. Using the low power mode
+
/// significantly shortens the measurement duration and thus minimizes the
+
/// energy consumption per measurement. The benefit of ultra-low power
+
/// consumption comes at the cost of reduced repeatability of the sensor
+
/// signals: while the impact on the relative humidity signal is negligible and
+
/// does not affect accuracy, it has an effect on temperature accuracy.
+
/// More details can be found in the ["Low Power Measurement Mode" application
+
/// note][an-low-power] by Sensirion.
+
/// [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
+
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
/// Normal measurement.
+
/// Low power measurement: Less energy consumption, but repeatability and
+
/// accuracy of measurements are negatively impacted.
+
/// All possible errors in this crate
+
#[derive(Debug, PartialEq, Clone)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
/// CRC checksum validation failed
+
impl<E> From<CrcError> for Error<E> {
+
fn from(_value: CrcError) -> Self {
+
#[derive(Debug, PartialEq, Clone)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
impl<E> From<E> for Error<E>
+
fn from(e: E) -> Self {
+
/// I²C commands sent to the sensor.
+
#[derive(Debug, Copy, Clone)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
/// Go into sleep mode.
+
/// Wake up from sleep mode.
+
/// Measurement commands.
+
order: MeasurementOrder,
+
fn as_bytes(self) -> [u8; 2] {
+
Command::Sleep => [0xB0, 0x98],
+
Command::WakeUp => [0x35, 0x17],
+
power_mode: PowerMode::NormalMode,
+
order: MeasurementOrder::TemperatureFirst,
+
power_mode: PowerMode::NormalMode,
+
order: MeasurementOrder::HumidityFirst,
+
power_mode: PowerMode::LowPower,
+
order: MeasurementOrder::TemperatureFirst,
+
power_mode: PowerMode::LowPower,
+
order: MeasurementOrder::HumidityFirst,
+
Command::ReadIdRegister => [0xEF, 0xC8],
+
Command::SoftwareReset => [0x80, 0x5D],
+
/// Driver for the SHTC3 sensor.
+
#[derive(Debug, Default)]
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
+
pub struct ShtC3<I2C> {
+
/// The concrete I²C device implementation.
+
/// The I²C device address.
+
/// Create a new instance of the driver for the SHTC3.
+
pub const fn new(i2c: I2C) -> Self {
+
Self { i2c, address: 0x70 }
+
/// Get the device's wakeup delay duration in microseconds
+
pub const fn wakeup_duration(&self) -> u32 {
+
/// Destroy driver instance, return I²C bus instance.
+
pub fn destroy(self) -> I2C {
+
/// Return the maximum measurement duration (depending on the mode) in
+
/// Maximum measurement duration (SHTC3 datasheet 3.1):
+
/// - Normal mode: 12.1 ms
+
/// - Low power mode: 0.8 ms
+
pub const fn max_measurement_duration(&self, mode: PowerMode) -> u32 {
+
PowerMode::NormalMode => 12100,
+
PowerMode::LowPower => 800,
+
/// Returns the reset duration for the SHTC3 in microseconds
+
pub const fn reset_duration(&self) -> u32 {
+
/// Iterate over the provided buffer and validate the CRC8 checksum.
+
/// If the checksum is wrong, return `CrcError`.
+
/// Note: This method will consider every third byte a checksum byte. If
+
/// the buffer size is not a multiple of 3, then not all data will be
+
fn validate_crc(&self, buf: &[u8]) -> Result<(), CrcError> {
+
let mut chunks = buf.chunks_exact(3);
+
for chunk in chunks.by_ref() {
+
if crc8(&chunk[..2]) != chunk[2] {
+
#[cfg(feature = "defmt")]
+
if !chunks.remainder().is_empty() {
+
defmt::warn!("Remaining data in buffer was not CRC8 validated");
+
I2C: embedded_hal_async::i2c::I2c<embedded_hal_async::i2c::SevenBitAddress>,
+
/// Write an I²C command to the sensor.
+
async fn send_command_async(&mut self, command: Command) -> Result<(), Error<I2C::Error>> {
+
.write(self.address, &command.as_bytes())
+
/// Read data into the provided buffer and validate the CRC8 checksum.
+
/// If the checksum is wrong, return `Error::Crc`.
+
/// Note: This method will consider every third byte a checksum byte. If
+
/// the buffer size is not a multiple of 3, then not all data will be
+
async fn read_with_crc_async(&mut self, buf: &mut [u8]) -> Result<(), Error<I2C::Error>> {
+
self.i2c.read(self.address, buf).await?;
+
self.validate_crc(buf)?;
+
/// Return the raw ID register.
+
pub async fn raw_id_register_async(&mut self) -> Result<u16, Error<I2C::Error>> {
+
// Request serial number
+
self.send_command_async(Command::ReadIdRegister).await?;
+
self.read_with_crc_async(&mut buf).await?;
+
Ok(u16::from_be_bytes([buf[0], buf[1]]))
+
/// Return the 7-bit device identifier.
+
/// Should be 0x47 (71) for the SHTC3.
+
pub async fn device_identifier_async(&mut self) -> Result<u8, Error<I2C::Error>> {
+
let ident = self.raw_id_register_async().await?;
+
let lsb = (ident & 0b0011_1111) as u8;
+
let msb = ((ident & 0b0000_1000_0000_0000) >> 5) as u8;
+
/// Set sensor to sleep mode.
+
/// When in sleep mode, the sensor consumes around 0.3-0.6 µA. It requires
+
/// a dedicated [`wakeup`](#method.wakeup) command to enable further I2C
+
pub async fn sleep_async(&mut self) -> Result<(), Error<I2C::Error>> {
+
self.send_command_async(Command::Sleep).await
+
/// Trigger a soft reset. (async)
+
/// The SHTC3 provides a soft reset mechanism that forces the system into a
+
/// well-defined state without removing the power supply. If the system is
+
/// in its idle state (i.e. if no measurement is in progress) the soft
+
/// reset command can be sent. This triggers the sensor to reset all
+
/// internal state machines and reload calibration data from the memory.
+
pub async fn reset_async(&mut self, delay: &mut impl DelayNs) -> Result<(), Error<I2C::Error>> {
+
self.send_command_async(Command::SoftwareReset).await?;
+
delay.delay_us(self.reset_duration()).await;
+
/// Wake up sensor from [sleep mode](#method.sleep) and wait until it is ready. (async)
+
pub async fn wakeup_async(
+
delay: &mut impl DelayNs,
+
) -> Result<(), Error<I2C::Error>> {
+
self.send_command_async(Command::WakeUp).await?;
+
delay.delay_us(self.wakeup_duration()).await;
+
/// Run a temperature/humidity measurement and return the combined result.
+
/// This is an async function call.
+
pub async fn measure_async(
+
delay: &mut impl DelayNs,
+
) -> Result<Measurement, Error<I2C::Error>> {
+
self.send_command_async(Command::Measure {
+
order: MeasurementOrder::TemperatureFirst,
+
delay.delay_us(self.max_measurement_duration(mode)).await;
+
self.read_with_crc_async(&mut buf).await?;
+
temperature: u16::from_be_bytes([buf[0], buf[1]]),
+
humidity: u16::from_be_bytes([buf[3], buf[4]]),
+
/// General blocking functions.
+
I2C: I2c<SevenBitAddress>,
+
/// Write an I²C command to the sensor.
+
fn send_command(&mut self, command: Command) -> Result<(), Error<I2C::Error>> {
+
.write(self.address, &command.as_bytes())
+
/// Read data into the provided buffer and validate the CRC8 checksum.
+
/// If the checksum is wrong, return `Error::Crc`.
+
/// Note: This method will consider every third byte a checksum byte. If
+
/// the buffer size is not a multiple of 3, then not all data will be
+
fn read_with_crc(&mut self, buf: &mut [u8]) -> Result<(), Error<I2C::Error>> {
+
self.i2c.read(self.address, buf)?;
+
self.validate_crc(buf)?;
+
/// Return the raw ID register.
+
pub fn raw_id_register(&mut self) -> Result<u16, Error<I2C::Error>> {
+
// Request serial number
+
self.send_command(Command::ReadIdRegister)?;
+
self.read_with_crc(&mut buf)?;
+
Ok(u16::from_be_bytes([buf[0], buf[1]]))
+
/// Return the 7-bit device identifier.
+
/// Should be 0x47 (71) for the SHTC3.
+
pub fn device_identifier(&mut self) -> Result<u8, Error<I2C::Error>> {
+
let ident = self.raw_id_register()?;
+
let lsb = (ident & 0b0011_1111) as u8;
+
let msb = ((ident & 0b0000_1000_0000_0000) >> 5) as u8;
+
/// Trigger a soft reset. (blocking)
+
/// The SHTC3 provides a soft reset mechanism that forces the system into a
+
/// well-defined state without removing the power supply. If the system is
+
/// in its idle state (i.e. if no measurement is in progress) the soft
+
/// reset command can be sent. This triggers the sensor to reset all
+
/// internal state machines and reload calibration data from the memory.
+
pub fn reset(&mut self, delay: &mut impl BlockingDelayNs) -> Result<(), Error<I2C::Error>> {
+
self.send_command(Command::SoftwareReset)?;
+
delay.delay_us(self.reset_duration());
+
/// Trigger a soft reset.
+
/// The SHTC3 provides a soft reset mechanism that forces the system into a
+
/// well-defined state without removing the power supply. If the system is
+
/// in its idle state (i.e. if no measurement is in progress) the soft
+
/// reset command can be sent. This triggers the sensor to reset all
+
/// internal state machines and reload calibration data from the memory.
+
pub fn start_reset(&mut self) -> Result<(), Error<I2C::Error>> {
+
self.send_command(Command::SoftwareReset)
+
/// Set sensor to sleep mode.
+
/// When in sleep mode, the sensor consumes around 0.3-0.6 µA. It requires
+
/// a dedicated [`wakeup`](#method.wakeup) command to enable further I2C
+
pub fn sleep(&mut self) -> Result<(), Error<I2C::Error>> {
+
self.send_command(Command::Sleep)
+
/// Wake up sensor from [sleep mode](#method.sleep) and wait until it is ready.
+
pub fn wakeup(&mut self, delay: &mut impl BlockingDelayNs) -> Result<(), Error<I2C::Error>> {
+
delay.delay_us(self.wakeup_duration());
+
/// Non-blocking functions for starting / reading measurements.
+
I2C: I2c<SevenBitAddress>,
+
/// Start a measurement with the specified measurement order and write the
+
/// result into the provided buffer.
+
/// If you just need one of the two measurements, provide a 3-byte buffer
+
/// instead of a 6-byte buffer.
+
fn start_measure_partial(
+
order: MeasurementOrder,
+
) -> Result<(), Error<I2C::Error>> {
+
self.send_command(Command::Measure { power_mode, order })
+
/// Start a combined temperature / humidity measurement.
+
pub fn start_measurement(&mut self, mode: PowerMode) -> Result<(), Error<I2C::Error>> {
+
self.start_measure_partial(mode, MeasurementOrder::TemperatureFirst)
+
/// Start a temperature measurement.
+
pub fn start_temperature_measurement(
+
) -> Result<(), Error<I2C::Error>> {
+
self.start_measure_partial(mode, MeasurementOrder::TemperatureFirst)
+
/// Start a humidity measurement.
+
pub fn start_humidity_measurement(&mut self, mode: PowerMode) -> Result<(), Error<I2C::Error>> {
+
self.start_measure_partial(mode, MeasurementOrder::HumidityFirst)
+
/// Read the result of a temperature / humidity measurement.
+
pub fn get_measurement_result(&mut self) -> Result<Measurement, Error<I2C::Error>> {
+
let raw = self.get_raw_measurement_result()?;
+
/// Read the result of a temperature measurement.
+
pub fn get_temperature_measurement_result(&mut self) -> Result<Temperature, Error<I2C::Error>> {
+
let raw = self.get_raw_partial_measurement_result()?;
+
Ok(Temperature::from_raw(raw))
+
/// Read the result of a humidity measurement.
+
pub fn get_humidity_measurement_result(&mut self) -> Result<Humidity, Error<I2C::Error>> {
+
let raw = self.get_raw_partial_measurement_result()?;
+
Ok(Humidity::from_raw(raw))
+
/// Read the raw result of a combined temperature / humidity measurement.
+
pub fn get_raw_measurement_result(&mut self) -> Result<RawMeasurement, Error<I2C::Error>> {
+
self.read_with_crc(&mut buf)?;
+
temperature: u16::from_be_bytes([buf[0], buf[1]]),
+
humidity: u16::from_be_bytes([buf[3], buf[4]]),
+
/// Read the raw result of a partial temperature or humidity measurement.
+
/// Return the raw 3-byte buffer (after validating CRC).
+
pub fn get_raw_partial_measurement_result(&mut self) -> Result<u16, Error<I2C::Error>> {
+
self.read_with_crc(&mut buf)?;
+
Ok(u16::from_be_bytes([buf[0], buf[1]]))
+
/// Wake up sensor from [sleep mode](#method.sleep).
+
pub fn start_wakeup(&mut self) -> Result<(), Error<I2C::Error>> {
+
self.send_command(Command::WakeUp)
+
/// Blocking functions for doing measurements.
+
I2C: I2c<SevenBitAddress>,
+
/// Wait the maximum time needed for the given measurement mode
+
pub fn wait_for_measurement(&mut self, mode: PowerMode, delay: &mut impl BlockingDelayNs) {
+
delay.delay_us(self.max_measurement_duration(mode));
+
/// Run a temperature/humidity measurement and return the combined result.
+
/// This is a blocking function call.
+
delay: &mut impl BlockingDelayNs,
+
) -> Result<Measurement, Error<I2C::Error>> {
+
self.start_measurement(mode)?;
+
self.wait_for_measurement(mode, delay);
+
self.get_measurement_result()
+
/// Run a temperature measurement and return the result.
+
/// This is a blocking function call.
+
/// Internally, it will request a measurement in "temperature first" mode
+
/// and only read the first half of the measurement response.
+
pub fn measure_temperature(
+
delay: &mut impl BlockingDelayNs,
+
) -> Result<Temperature, Error<I2C::Error>> {
+
self.start_temperature_measurement(mode)?;
+
self.wait_for_measurement(mode, delay);
+
self.get_temperature_measurement_result()
+
/// Run a humidity measurement and return the result.
+
/// This is a blocking function call.
+
/// Internally, it will request a measurement in "humidity first" mode
+
/// and only read the first half of the measurement response.
+
pub fn measure_humidity(
+
delay: &mut impl BlockingDelayNs,
+
) -> Result<Humidity, Error<I2C::Error>> {
+
self.start_humidity_measurement(mode)?;
+
self.wait_for_measurement(mode, delay);
+
self.get_humidity_measurement_result()
+
use embedded_hal::i2c::ErrorKind;
+
use embedded_hal_mock::eh1::{
+
i2c::{Mock as I2cMock, Transaction},
+
const SHT_ADDR: u8 = 0x70;
+
/// Test whether the `send_command` function propagates I²C errors.
+
fn send_command_error() {
+
[Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8])
+
.with_error(ErrorKind::Other)];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let err = sht.send_command(Command::ReadIdRegister).unwrap_err();
+
assert_eq!(err, Error::I2c(ErrorKind::Other));
+
/// Test the `validate_crc` function.
+
let mock = I2cMock::new(&[]);
+
let sht = ShtC3::new(mock);
+
sht.validate_crc(&[]).unwrap();
+
sht.validate_crc(&[0xbe]).unwrap();
+
sht.validate_crc(&[0xbe, 0xef]).unwrap();
+
sht.validate_crc(&[0xbe, 0xef, 0x92]).unwrap();
+
match sht.validate_crc(&[0xbe, 0xef, 0x91]) {
+
Ok(_) => panic!("CRC check did not fail"),
+
sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0x92, 0x00, 0x00])
+
// Invalid CRC (8 bytes)
+
match sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0xff, 0x00, 0x00]) {
+
Ok(_) => panic!("CRC check did not fail"),
+
/// Test the `read_with_crc` function.
+
let expectations = [Transaction::read(SHT_ADDR, alloc::vec![0xbe, 0xef, 0x92])];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
sht.read_with_crc(&mut buf).unwrap();
+
assert_eq!(buf, [0xbe, 0xef, 0x92]);
+
let expectations = [Transaction::read(SHT_ADDR, alloc::vec![0xbe, 0xef, 0x00])];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
match sht.read_with_crc(&mut buf) {
+
Err(_) => panic!("Invalid error: Must be Crc"),
+
Ok(_) => panic!("CRC check did not fail"),
+
assert_eq!(buf, [0xbe, 0xef, 0x00]); // Buf was changed
+
mod factory_functions {
+
let mock = I2cMock::new(&[]);
+
let sht = ShtC3::new(mock);
+
assert_eq!(sht.address, 0x70);
+
/// Test the `raw_id_register` function.
+
let crc = crc8(&[msb, lsb]);
+
Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]),
+
Transaction::read(SHT_ADDR, alloc::vec![msb, lsb, crc]),
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let val = sht.raw_id_register().unwrap();
+
assert_eq!(val, (msb as u16) << 8 | (lsb as u16));
+
/// Test the `device_identifier` function.
+
fn device_identifier() {
+
let crc = crc8(&[msb, lsb]);
+
Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]),
+
Transaction::read(SHT_ADDR, alloc::vec![msb, lsb, crc]),
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let ident = sht.device_identifier().unwrap();
+
assert_eq!(ident, 0b01000111);
+
// Expect a write command: Normal mode measurement, temperature
+
// first, no clock stretching.
+
Transaction::write(SHT_ADDR, alloc::vec![0x78, 0x66]),
+
// Return the measurement result (using example values from the
+
// datasheet, section 5.4 "Measuring and Reading the Signals")
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let mut delay = NoopDelay;
+
let measurement = sht.measure(PowerMode::NormalMode, &mut delay).unwrap();
+
assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); // 23.7°C
+
assert_eq!(measurement.humidity.as_millipercent(), 62_968); // 62.9 %RH
+
fn measure_low_power() {
+
// Expect a write command: Low power mode measurement, temperature
+
// first, no clock stretching.
+
Transaction::write(SHT_ADDR, alloc::vec![0x60, 0x9C]),
+
// Return the measurement result (using example values from the
+
// datasheet, section 5.4 "Measuring and Reading the Signals")
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let mut delay = NoopDelay;
+
let measurement = sht.measure(PowerMode::LowPower, &mut delay).unwrap();
+
assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); // 23.7°C
+
assert_eq!(measurement.humidity.as_millipercent(), 62_968); // 62.9 %RH
+
fn measure_temperature_only() {
+
// Expect a write command: Normal mode measurement, temperature
+
// first, no clock stretching.
+
Transaction::write(SHT_ADDR, alloc::vec![0x78, 0x66]),
+
// Return the measurement result (using example values from the
+
// datasheet, section 5.4 "Measuring and Reading the Signals")
+
Transaction::read(SHT_ADDR, alloc::vec![0b0110_0100, 0b1000_1011, 0b1100_0111]),
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let mut delay = NoopDelay;
+
.measure_temperature(PowerMode::NormalMode, &mut delay)
+
assert_eq!(temperature.as_millidegrees_celsius(), 23_730); // 23.7°C
+
fn measure_humidity_only() {
+
// Expect a write command: Normal mode measurement, humidity
+
// first, no clock stretching.
+
Transaction::write(SHT_ADDR, alloc::vec![0x58, 0xE0]),
+
// Return the measurement result (using example values from the
+
// datasheet, section 5.4 "Measuring and Reading the Signals")
+
Transaction::read(SHT_ADDR, alloc::vec![0b1010_0001, 0b0011_0011, 0b0001_1100]),
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
let mut delay = NoopDelay;
+
.measure_humidity(PowerMode::NormalMode, &mut delay)
+
assert_eq!(humidity.as_millipercent(), 62_968); // 62.9 %RH
+
/// Ensure that I²C write errors are handled when measuring.
+
fn measure_write_error() {
+
[Transaction::write(SHT_ADDR, alloc::vec![0x60, 0x9C])
+
.with_error(ErrorKind::Other)];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
.measure(PowerMode::LowPower, &mut NoopDelay)
+
assert_eq!(err, Error::I2c(ErrorKind::Other));
+
/// Test the `sleep` function.
+
let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0xB0, 0x98])];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
/// Test the `wakeup` function.
+
let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0x35, 0x17])];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
sht.wakeup(&mut NoopDelay).unwrap();
+
/// Test the `reset` function.
+
let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0x80, 0x5D])];
+
let mock = I2cMock::new(&expectations);
+
let mut sht = ShtC3::new(mock);
+
sht.reset(&mut NoopDelay).unwrap();
+
mod max_measurement_duration {
+
fn shortcut_function() {
+
let c3 = ShtC3::new(I2cMock::new(&[]));
+
assert_eq!(c3.max_measurement_duration(PowerMode::NormalMode), 12100);
+
assert_eq!(c3.max_measurement_duration(PowerMode::LowPower), 800);