···
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.
7
+
//! ## Supported Devices
9
+
//! Tested with the following sensors:
10
+
//! - [SHTC3](https://www.sensirion.com/shtc3/)
12
+
//! ## Blocking / Non-Blocking Modes
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.
18
+
//! ## Clock Stretching
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.
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:
34
+
//! use linux_embedded_hal::{Delay, I2cdev};
35
+
//! use sachy_shtc3::ShtC3;
37
+
//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
38
+
//! let mut sht = ShtC3::new(dev);
43
+
//! Then, you can query information about the sensor:
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();
53
+
//! ### Measurements (Blocking)
55
+
//! For measuring your environment, you can either measure just temperature,
56
+
//! just humidity, or both:
59
+
//! use linux_embedded_hal::{Delay, I2cdev};
60
+
//! use sachy_shtc3::{ShtC3, PowerMode};
62
+
//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
63
+
//! let mut delay = Delay;
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();
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());
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].
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
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();
90
+
//! ### Measurements (Non-Blocking)
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.
97
+
//! use linux_embedded_hal::I2cdev;
98
+
//! use sachy_shtc3::{ShtC3, PowerMode};
100
+
//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
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();
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
111
+
//! - [`get_raw_measurement_result`](crate::ShtC3::get_raw_measurement_result())
112
+
//! - [`get_raw_partial_measurement_result`](crate::ShtC3::get_raw_partial_measurement_result())
114
+
//! The raw values are of type u16. They require a conversion formula for
115
+
//! conversion to a temperature / humidity value (see datasheet).
117
+
//! Invoking any command other than
118
+
//! [`wakeup`](crate::ShtC3::wakeup()) while the sensor is in
119
+
//! sleep mode will result in an error.
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.
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();
136
+
#![deny(unsafe_code, missing_docs)]
142
+
use embedded_hal::{
143
+
delay::DelayNs as BlockingDelayNs,
144
+
i2c::{self, I2c, SevenBitAddress},
148
+
use embedded_hal_async::delay::DelayNs;
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 {
159
+
/// Measurement power mode: Normal mode or low power mode.
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.
168
+
/// More details can be found in the ["Low Power Measurement Mode" application
169
+
/// note][an-low-power] by Sensirion.
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.
177
+
/// Low power measurement: Less energy consumption, but repeatability and
178
+
/// accuracy of measurements are negatively impacted.
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> {
188
+
/// CRC checksum validation failed
192
+
impl<E> From<CrcError> for Error<E> {
193
+
fn from(_value: CrcError) -> Self {
198
+
#[derive(Debug, PartialEq, Clone)]
199
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
202
+
impl<E> From<E> for Error<E>
206
+
fn from(e: E) -> Self {
211
+
/// I²C commands sent to the sensor.
212
+
#[derive(Debug, Copy, Clone)]
213
+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
215
+
/// Go into sleep mode.
217
+
/// Wake up from sleep mode.
219
+
/// Measurement commands.
221
+
power_mode: PowerMode,
222
+
order: MeasurementOrder,
224
+
/// Software reset.
226
+
/// Read ID register.
231
+
fn as_bytes(self) -> [u8; 2] {
233
+
Command::Sleep => [0xB0, 0x98],
234
+
Command::WakeUp => [0x35, 0x17],
236
+
power_mode: PowerMode::NormalMode,
237
+
order: MeasurementOrder::TemperatureFirst,
240
+
power_mode: PowerMode::NormalMode,
241
+
order: MeasurementOrder::HumidityFirst,
244
+
power_mode: PowerMode::LowPower,
245
+
order: MeasurementOrder::TemperatureFirst,
248
+
power_mode: PowerMode::LowPower,
249
+
order: MeasurementOrder::HumidityFirst,
251
+
Command::ReadIdRegister => [0xEF, 0xC8],
252
+
Command::SoftwareReset => [0x80, 0x5D],
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.
263
+
/// The I²C device address.
267
+
impl<I2C> ShtC3<I2C> {
268
+
/// Create a new instance of the driver for the SHTC3.
270
+
pub const fn new(i2c: I2C) -> Self {
271
+
Self { i2c, address: 0x70 }
274
+
/// Get the device's wakeup delay duration in microseconds
276
+
pub const fn wakeup_duration(&self) -> u32 {
280
+
/// Destroy driver instance, return I²C bus instance.
281
+
pub fn destroy(self) -> I2C {
285
+
/// Return the maximum measurement duration (depending on the mode) in
288
+
/// Maximum measurement duration (SHTC3 datasheet 3.1):
289
+
/// - Normal mode: 12.1 ms
290
+
/// - Low power mode: 0.8 ms
292
+
pub const fn max_measurement_duration(&self, mode: PowerMode) -> u32 {
294
+
PowerMode::NormalMode => 12100,
295
+
PowerMode::LowPower => 800,
299
+
/// Returns the reset duration for the SHTC3 in microseconds
301
+
pub const fn reset_duration(&self) -> u32 {
305
+
/// Iterate over the provided buffer and validate the CRC8 checksum.
307
+
/// If the checksum is wrong, return `CrcError`.
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
312
+
fn validate_crc(&self, buf: &[u8]) -> Result<(), CrcError> {
313
+
let mut chunks = buf.chunks_exact(3);
315
+
for chunk in chunks.by_ref() {
316
+
if crc8(&chunk[..2]) != chunk[2] {
317
+
return Err(CrcError);
321
+
#[cfg(feature = "defmt")]
322
+
if !chunks.remainder().is_empty() {
323
+
defmt::warn!("Remaining data in buffer was not CRC8 validated");
330
+
impl<I2C> ShtC3<I2C>
332
+
I2C: embedded_hal_async::i2c::I2c<embedded_hal_async::i2c::SevenBitAddress>,
334
+
/// Write an I²C command to the sensor.
335
+
async fn send_command_async(&mut self, command: Command) -> Result<(), Error<I2C::Error>> {
337
+
.write(self.address, &command.as_bytes())
339
+
.map_err(Error::I2c)
342
+
/// Read data into the provided buffer and validate the CRC8 checksum.
344
+
/// If the checksum is wrong, return `Error::Crc`.
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
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)?;
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?;
360
+
// Read id register
361
+
let mut buf = [0; 3];
362
+
self.read_with_crc_async(&mut buf).await?;
364
+
Ok(u16::from_be_bytes([buf[0], buf[1]]))
367
+
/// Return the 7-bit device identifier.
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;
377
+
/// Set sensor to sleep mode.
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
382
+
pub async fn sleep_async(&mut self) -> Result<(), Error<I2C::Error>> {
383
+
self.send_command_async(Command::Sleep).await
386
+
/// Trigger a soft reset. (async)
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;
400
+
/// Wake up sensor from [sleep mode](#method.sleep) and wait until it is ready. (async)
401
+
pub async fn wakeup_async(
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;
410
+
/// Run a temperature/humidity measurement and return the combined result.
412
+
/// This is an async function call.
413
+
pub async fn measure_async(
416
+
delay: &mut impl DelayNs,
417
+
) -> Result<Measurement, Error<I2C::Error>> {
418
+
self.send_command_async(Command::Measure {
420
+
order: MeasurementOrder::TemperatureFirst,
424
+
delay.delay_us(self.max_measurement_duration(mode)).await;
426
+
let mut buf = [0; 6];
427
+
self.read_with_crc_async(&mut buf).await?;
429
+
Ok(RawMeasurement {
430
+
temperature: u16::from_be_bytes([buf[0], buf[1]]),
431
+
humidity: u16::from_be_bytes([buf[3], buf[4]]),
437
+
/// General blocking functions.
438
+
impl<I2C> ShtC3<I2C>
440
+
I2C: I2c<SevenBitAddress>,
442
+
/// Write an I²C command to the sensor.
443
+
fn send_command(&mut self, command: Command) -> Result<(), Error<I2C::Error>> {
445
+
.write(self.address, &command.as_bytes())
446
+
.map_err(Error::I2c)
449
+
/// Read data into the provided buffer and validate the CRC8 checksum.
451
+
/// If the checksum is wrong, return `Error::Crc`.
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
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)?;
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)?;
467
+
// Read id register
468
+
let mut buf = [0; 3];
469
+
self.read_with_crc(&mut buf)?;
471
+
Ok(u16::from_be_bytes([buf[0], buf[1]]))
474
+
/// Return the 7-bit device identifier.
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;
484
+
/// Trigger a soft reset. (blocking)
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());
498
+
/// Trigger a soft reset.
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)
509
+
/// Set sensor to sleep mode.
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
514
+
pub fn sleep(&mut self) -> Result<(), Error<I2C::Error>> {
515
+
self.send_command(Command::Sleep)
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());
526
+
/// Non-blocking functions for starting / reading measurements.
527
+
impl<I2C> ShtC3<I2C>
529
+
I2C: I2c<SevenBitAddress>,
531
+
/// Start a measurement with the specified measurement order and write the
532
+
/// result into the provided buffer.
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(
538
+
power_mode: PowerMode,
539
+
order: MeasurementOrder,
540
+
) -> Result<(), Error<I2C::Error>> {
541
+
// Request measurement
542
+
self.send_command(Command::Measure { power_mode, order })
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)
550
+
/// Start a temperature measurement.
551
+
pub fn start_temperature_measurement(
554
+
) -> Result<(), Error<I2C::Error>> {
555
+
self.start_measure_partial(mode, MeasurementOrder::TemperatureFirst)
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)
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()?;
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))
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))
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]]),
591
+
/// Read the raw result of a partial temperature or humidity measurement.
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]]))
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)
606
+
/// Blocking functions for doing measurements.
607
+
impl<I2C> ShtC3<I2C>
609
+
I2C: I2c<SevenBitAddress>,
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));
616
+
/// Run a temperature/humidity measurement and return the combined result.
618
+
/// This is a blocking function call.
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()
629
+
/// Run a temperature measurement and return the result.
631
+
/// This is a blocking function call.
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(
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()
645
+
/// Run a humidity measurement and return the result.
647
+
/// This is a blocking function call.
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(
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()
664
+
extern crate alloc;
668
+
use embedded_hal::i2c::ErrorKind;
669
+
use embedded_hal_mock::eh1::{
671
+
i2c::{Mock as I2cMock, Transaction},
674
+
const SHT_ADDR: u8 = 0x70;
679
+
/// Test whether the `send_command` function propagates I²C errors.
681
+
fn send_command_error() {
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();
692
+
/// Test the `validate_crc` function.
694
+
fn validate_crc() {
695
+
let mock = I2cMock::new(&[]);
696
+
let sht = ShtC3::new(mock);
699
+
sht.validate_crc(&[]).unwrap();
700
+
sht.validate_crc(&[0xbe]).unwrap();
701
+
sht.validate_crc(&[0xbe, 0xef]).unwrap();
704
+
sht.validate_crc(&[0xbe, 0xef, 0x92]).unwrap();
707
+
match sht.validate_crc(&[0xbe, 0xef, 0x91]) {
708
+
Err(CrcError) => {}
709
+
Ok(_) => panic!("CRC check did not fail"),
712
+
// Valid CRC (8 bytes)
713
+
sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0x92, 0x00, 0x00])
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"),
722
+
sht.destroy().done();
725
+
/// Test the `read_with_crc` function.
727
+
fn read_with_crc() {
728
+
let mut buf = [0; 3];
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();
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"),
747
+
assert_eq!(buf, [0xbe, 0xef, 0x00]); // Buf was changed
748
+
sht.destroy().done();
752
+
mod factory_functions {
757
+
let mock = I2cMock::new(&[]);
758
+
let sht = ShtC3::new(mock);
759
+
assert_eq!(sht.address, 0x70);
760
+
sht.destroy().done();
767
+
/// Test the `raw_id_register` function.
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]),
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();
784
+
/// Test the `device_identifier` function.
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]),
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();
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")
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();
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")
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();
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]),
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)
879
+
assert_eq!(temperature.as_millidegrees_celsius(), 23_730); // 23.7°C
880
+
sht.destroy().done();
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]),
893
+
let mock = I2cMock::new(&expectations);
894
+
let mut sht = ShtC3::new(mock);
895
+
let mut delay = NoopDelay;
897
+
.measure_humidity(PowerMode::NormalMode, &mut delay)
899
+
assert_eq!(humidity.as_millipercent(), 62_968); // 62.9 %RH
900
+
sht.destroy().done();
903
+
/// Ensure that I²C write errors are handled when measuring.
905
+
fn measure_write_error() {
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);
912
+
.measure(PowerMode::LowPower, &mut NoopDelay)
914
+
assert_eq!(err, Error::I2c(ErrorKind::Other));
915
+
sht.destroy().done();
919
+
mod power_management {
922
+
/// Test the `sleep` function.
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();
932
+
/// Test the `wakeup` function.
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();
942
+
/// Test the `reset` function.
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();
953
+
mod max_measurement_duration {
957
+
fn shortcut_function() {
958
+
let c3 = ShtC3::new(I2cMock::new(&[]));
960
+
assert_eq!(c3.max_measurement_duration(PowerMode::NormalMode), 12100);
961
+
assert_eq!(c3.max_measurement_duration(PowerMode::LowPower), 800);
963
+
c3.destroy().done();