···11+/// Calculate the CRC8 checksum.
22+///
33+/// Implementation based on the reference implementation by Sensirion.
44+#[inline]
55+pub(crate) const fn crc8(data: &[u8]) -> u8 {
66+ const CRC8_POLYNOMIAL: u8 = 0x31;
77+ let mut crc: u8 = u8::MAX;
88+ let mut i = 0;
99+1010+ while i < data.len() {
1111+ crc ^= data[i];
1212+ i += 1;
1313+1414+ let mut c = 0;
1515+ while c < 8 {
1616+ c += 1;
1717+ if (crc & 0x80) > 0 {
1818+ crc = (crc << 1) ^ CRC8_POLYNOMIAL;
1919+ } else {
2020+ crc <<= 1;
2121+ }
2222+ }
2323+ }
2424+2525+ crc
2626+}
2727+2828+#[cfg(test)]
2929+mod tests {
3030+ use super::*;
3131+3232+ /// Test the crc8 function against the test value provided in the
3333+ /// SHTC3 datasheet (section 5.10).
3434+ #[test]
3535+ fn crc8_test_value() {
3636+ assert_eq!(crc8(&[0x00]), 0xac);
3737+ assert_eq!(crc8(&[0xbe, 0xef]), 0x92);
3838+ }
3939+}
+966
sachy-shtc3/src/lib.rs
···11+//! # Introduction
22+//!
33+//! This is a platform agnostic Rust driver for the Sensirion SHTC3 temperature /
44+//! humidity sensor, based on the
55+//! [`embedded-hal`](https://github.com/rust-embedded/embedded-hal) traits.
66+//!
77+//! ## Supported Devices
88+//!
99+//! Tested with the following sensors:
1010+//! - [SHTC3](https://www.sensirion.com/shtc3/)
1111+//!
1212+//! ## Blocking / Non-Blocking Modes
1313+//!
1414+//! This driver provides blocking and non-blocking calls. The blocking calls delay the execution
1515+//! until the measurement is done and return the results. The non-blocking ones just start the
1616+//! measurement and allow the application code to do other stuff and get the results afterwards.
1717+//!
1818+//! ## Clock Stretching
1919+//!
2020+//! While the sensor would provide measurement commands with clock stretching to indicate when the
2121+//! measurement is done, this is not implemented and probably won't be.
2222+//!
2323+//! ## Usage
2424+//!
2525+//! ### Setup
2626+//!
2727+//! Instantiate a new driver instance using a [blocking I²C HAL
2828+//! implementation](https://docs.rs/embedded-hal/0.2.*/embedded_hal/blocking/i2c/index.html)
2929+//! and a [blocking `Delay`
3030+//! instance](https://docs.rs/embedded-hal/0.2.*/embedded_hal/blocking/delay/index.html).
3131+//! For example, using `linux-embedded-hal` and an SHTC3 sensor:
3232+//!
3333+//! ```no_run
3434+//! use linux_embedded_hal::{Delay, I2cdev};
3535+//! use sachy_shtc3::ShtC3;
3636+//!
3737+//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
3838+//! let mut sht = ShtC3::new(dev);
3939+//! ```
4040+//!
4141+//! ### Device Info
4242+//!
4343+//! Then, you can query information about the sensor:
4444+//!
4545+//! ```no_run
4646+//! use linux_embedded_hal::{Delay, I2cdev};
4747+//! use sachy_shtc3::ShtC3;
4848+//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
4949+//! let device_id = sht.device_identifier().unwrap();
5050+//! let raw_id = sht.raw_id_register().unwrap();
5151+//! ```
5252+//!
5353+//! ### Measurements (Blocking)
5454+//!
5555+//! For measuring your environment, you can either measure just temperature,
5656+//! just humidity, or both:
5757+//!
5858+//! ```no_run
5959+//! use linux_embedded_hal::{Delay, I2cdev};
6060+//! use sachy_shtc3::{ShtC3, PowerMode};
6161+//!
6262+//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
6363+//! let mut delay = Delay;
6464+//!
6565+//! let temperature = sht.measure_temperature(PowerMode::NormalMode, &mut delay).unwrap();
6666+//! let humidity = sht.measure_humidity(PowerMode::NormalMode, &mut delay).unwrap();
6767+//! let combined = sht.measure(PowerMode::NormalMode, &mut delay).unwrap();
6868+//!
6969+//! println!("Temperature: {} °C", temperature.as_degrees_celsius());
7070+//! println!("Humidity: {} %RH", humidity.as_percent());
7171+//! println!("Combined: {} °C / {} %RH",
7272+//! combined.temperature.as_degrees_celsius(),
7373+//! combined.humidity.as_percent());
7474+//! ```
7575+//!
7676+//! You can also use the low power mode for less power consumption, at the cost
7777+//! of reduced repeatability and accuracy of the sensor signals. For more
7878+//! information, see the ["Low Power Measurement Mode" application note][low-power].
7979+//!
8080+//! [low-power]: https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/2_Humidity_Sensors/Sensirion_Humidity_Sensors_SHTC3_Low_Power_Measurement_Mode.pdf
8181+//!
8282+//! ```no_run
8383+//! use linux_embedded_hal::{Delay, I2cdev};
8484+//! use sachy_shtc3::{ShtC3, PowerMode};
8585+//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
8686+//! let mut delay = Delay;
8787+//! let measurement = sht.measure(PowerMode::LowPower, &mut delay).unwrap();
8888+//! ```
8989+//!
9090+//! ### Measurements (Non-Blocking)
9191+//!
9292+//! If you want to avoid blocking measurements, you can use the non-blocking
9393+//! commands instead. You are, however, responsible for ensuring the correct
9494+//! timing of the calls.
9595+//!
9696+//! ```no_run
9797+//! use linux_embedded_hal::I2cdev;
9898+//! use sachy_shtc3::{ShtC3, PowerMode};
9999+//!
100100+//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
101101+//!
102102+//! sht.start_measurement(PowerMode::NormalMode).unwrap();
103103+//! // Wait for at least `max_measurement_duration(&sht, PowerMode::NormalMode)` µs
104104+//! let result = sht.get_measurement_result().unwrap();
105105+//! ```
106106+//!
107107+//! In non-blocking mode, if desired, you can also read the raw 16-bit
108108+//! measurement results from the sensor by using the following two methods
109109+//! instead:
110110+//!
111111+//! - [`get_raw_measurement_result`](crate::ShtC3::get_raw_measurement_result())
112112+//! - [`get_raw_partial_measurement_result`](crate::ShtC3::get_raw_partial_measurement_result())
113113+//!
114114+//! The raw values are of type u16. They require a conversion formula for
115115+//! conversion to a temperature / humidity value (see datasheet).
116116+//!
117117+//! Invoking any command other than
118118+//! [`wakeup`](crate::ShtC3::wakeup()) while the sensor is in
119119+//! sleep mode will result in an error.
120120+//!
121121+//! ### Soft Reset
122122+//!
123123+//! The SHTC3 provides a soft reset mechanism that forces the system into a
124124+//! well-defined state without removing the power supply. If the system is in
125125+//! its idle state (i.e. if no measurement is in progress) the soft reset
126126+//! command can be sent. This triggers the sensor to reset all internal state
127127+//! machines and reload calibration data from the memory.
128128+//!
129129+//! ```no_run
130130+//! use linux_embedded_hal::{Delay, I2cdev};
131131+//! use sachy_shtc3::{ShtC3, PowerMode};
132132+//! let mut sht = ShtC3::new(I2cdev::new("/dev/i2c-1").unwrap());
133133+//! let mut delay = Delay;
134134+//! sht.reset(&mut delay).unwrap();
135135+//! ```
136136+#![deny(unsafe_code, missing_docs)]
137137+#![no_std]
138138+139139+mod crc;
140140+mod types;
141141+142142+use embedded_hal::{
143143+ delay::DelayNs as BlockingDelayNs,
144144+ i2c::{self, I2c, SevenBitAddress},
145145+};
146146+147147+use crc::crc8;
148148+use embedded_hal_async::delay::DelayNs;
149149+pub use types::*;
150150+151151+/// Whether temperature or humidity is returned first when doing a measurement.
152152+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
153153+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
154154+enum MeasurementOrder {
155155+ TemperatureFirst,
156156+ HumidityFirst,
157157+}
158158+159159+/// Measurement power mode: Normal mode or low power mode.
160160+///
161161+/// The sensors provides a low power measurement mode. Using the low power mode
162162+/// significantly shortens the measurement duration and thus minimizes the
163163+/// energy consumption per measurement. The benefit of ultra-low power
164164+/// consumption comes at the cost of reduced repeatability of the sensor
165165+/// signals: while the impact on the relative humidity signal is negligible and
166166+/// does not affect accuracy, it has an effect on temperature accuracy.
167167+///
168168+/// More details can be found in the ["Low Power Measurement Mode" application
169169+/// note][an-low-power] by Sensirion.
170170+///
171171+/// [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
172172+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
173173+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
174174+pub enum PowerMode {
175175+ /// Normal measurement.
176176+ NormalMode,
177177+ /// Low power measurement: Less energy consumption, but repeatability and
178178+ /// accuracy of measurements are negatively impacted.
179179+ LowPower,
180180+}
181181+182182+/// All possible errors in this crate
183183+#[derive(Debug, PartialEq, Clone)]
184184+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
185185+pub enum Error<E> {
186186+ /// I²C bus error
187187+ I2c(E),
188188+ /// CRC checksum validation failed
189189+ Crc,
190190+}
191191+192192+impl<E> From<CrcError> for Error<E> {
193193+ fn from(_value: CrcError) -> Self {
194194+ Self::Crc
195195+ }
196196+}
197197+198198+#[derive(Debug, PartialEq, Clone)]
199199+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
200200+struct CrcError;
201201+202202+impl<E> From<E> for Error<E>
203203+where
204204+ E: i2c::Error,
205205+{
206206+ fn from(e: E) -> Self {
207207+ Error::I2c(e)
208208+ }
209209+}
210210+211211+/// I²C commands sent to the sensor.
212212+#[derive(Debug, Copy, Clone)]
213213+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
214214+enum Command {
215215+ /// Go into sleep mode.
216216+ Sleep,
217217+ /// Wake up from sleep mode.
218218+ WakeUp,
219219+ /// Measurement commands.
220220+ Measure {
221221+ power_mode: PowerMode,
222222+ order: MeasurementOrder,
223223+ },
224224+ /// Software reset.
225225+ SoftwareReset,
226226+ /// Read ID register.
227227+ ReadIdRegister,
228228+}
229229+230230+impl Command {
231231+ fn as_bytes(self) -> [u8; 2] {
232232+ match self {
233233+ Command::Sleep => [0xB0, 0x98],
234234+ Command::WakeUp => [0x35, 0x17],
235235+ Command::Measure {
236236+ power_mode: PowerMode::NormalMode,
237237+ order: MeasurementOrder::TemperatureFirst,
238238+ } => [0x78, 0x66],
239239+ Command::Measure {
240240+ power_mode: PowerMode::NormalMode,
241241+ order: MeasurementOrder::HumidityFirst,
242242+ } => [0x58, 0xE0],
243243+ Command::Measure {
244244+ power_mode: PowerMode::LowPower,
245245+ order: MeasurementOrder::TemperatureFirst,
246246+ } => [0x60, 0x9C],
247247+ Command::Measure {
248248+ power_mode: PowerMode::LowPower,
249249+ order: MeasurementOrder::HumidityFirst,
250250+ } => [0x40, 0x1A],
251251+ Command::ReadIdRegister => [0xEF, 0xC8],
252252+ Command::SoftwareReset => [0x80, 0x5D],
253253+ }
254254+ }
255255+}
256256+257257+/// Driver for the SHTC3 sensor.
258258+#[derive(Debug, Default)]
259259+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
260260+pub struct ShtC3<I2C> {
261261+ /// The concrete I²C device implementation.
262262+ i2c: I2C,
263263+ /// The I²C device address.
264264+ address: u8,
265265+}
266266+267267+impl<I2C> ShtC3<I2C> {
268268+ /// Create a new instance of the driver for the SHTC3.
269269+ #[inline]
270270+ pub const fn new(i2c: I2C) -> Self {
271271+ Self { i2c, address: 0x70 }
272272+ }
273273+274274+ /// Get the device's wakeup delay duration in microseconds
275275+ #[inline(always)]
276276+ pub const fn wakeup_duration(&self) -> u32 {
277277+ 240
278278+ }
279279+280280+ /// Destroy driver instance, return I²C bus instance.
281281+ pub fn destroy(self) -> I2C {
282282+ self.i2c
283283+ }
284284+285285+ /// Return the maximum measurement duration (depending on the mode) in
286286+ /// microseconds.
287287+ ///
288288+ /// Maximum measurement duration (SHTC3 datasheet 3.1):
289289+ /// - Normal mode: 12.1 ms
290290+ /// - Low power mode: 0.8 ms
291291+ #[inline(always)]
292292+ pub const fn max_measurement_duration(&self, mode: PowerMode) -> u32 {
293293+ match mode {
294294+ PowerMode::NormalMode => 12100,
295295+ PowerMode::LowPower => 800,
296296+ }
297297+ }
298298+299299+ /// Returns the reset duration for the SHTC3 in microseconds
300300+ #[inline(always)]
301301+ pub const fn reset_duration(&self) -> u32 {
302302+ 240_000
303303+ }
304304+305305+ /// Iterate over the provided buffer and validate the CRC8 checksum.
306306+ ///
307307+ /// If the checksum is wrong, return `CrcError`.
308308+ ///
309309+ /// Note: This method will consider every third byte a checksum byte. If
310310+ /// the buffer size is not a multiple of 3, then not all data will be
311311+ /// validated.
312312+ fn validate_crc(&self, buf: &[u8]) -> Result<(), CrcError> {
313313+ let mut chunks = buf.chunks_exact(3);
314314+315315+ for chunk in chunks.by_ref() {
316316+ if crc8(&chunk[..2]) != chunk[2] {
317317+ return Err(CrcError);
318318+ }
319319+ }
320320+321321+ #[cfg(feature = "defmt")]
322322+ if !chunks.remainder().is_empty() {
323323+ defmt::warn!("Remaining data in buffer was not CRC8 validated");
324324+ }
325325+326326+ Ok(())
327327+ }
328328+}
329329+330330+impl<I2C> ShtC3<I2C>
331331+where
332332+ I2C: embedded_hal_async::i2c::I2c<embedded_hal_async::i2c::SevenBitAddress>,
333333+{
334334+ /// Write an I²C command to the sensor.
335335+ async fn send_command_async(&mut self, command: Command) -> Result<(), Error<I2C::Error>> {
336336+ self.i2c
337337+ .write(self.address, &command.as_bytes())
338338+ .await
339339+ .map_err(Error::I2c)
340340+ }
341341+342342+ /// Read data into the provided buffer and validate the CRC8 checksum.
343343+ ///
344344+ /// If the checksum is wrong, return `Error::Crc`.
345345+ ///
346346+ /// Note: This method will consider every third byte a checksum byte. If
347347+ /// the buffer size is not a multiple of 3, then not all data will be
348348+ /// validated.
349349+ async fn read_with_crc_async(&mut self, buf: &mut [u8]) -> Result<(), Error<I2C::Error>> {
350350+ self.i2c.read(self.address, buf).await?;
351351+ self.validate_crc(buf)?;
352352+ Ok(())
353353+ }
354354+355355+ /// Return the raw ID register.
356356+ pub async fn raw_id_register_async(&mut self) -> Result<u16, Error<I2C::Error>> {
357357+ // Request serial number
358358+ self.send_command_async(Command::ReadIdRegister).await?;
359359+360360+ // Read id register
361361+ let mut buf = [0; 3];
362362+ self.read_with_crc_async(&mut buf).await?;
363363+364364+ Ok(u16::from_be_bytes([buf[0], buf[1]]))
365365+ }
366366+367367+ /// Return the 7-bit device identifier.
368368+ ///
369369+ /// Should be 0x47 (71) for the SHTC3.
370370+ pub async fn device_identifier_async(&mut self) -> Result<u8, Error<I2C::Error>> {
371371+ let ident = self.raw_id_register_async().await?;
372372+ let lsb = (ident & 0b0011_1111) as u8;
373373+ let msb = ((ident & 0b0000_1000_0000_0000) >> 5) as u8;
374374+ Ok(lsb | msb)
375375+ }
376376+377377+ /// Set sensor to sleep mode.
378378+ ///
379379+ /// When in sleep mode, the sensor consumes around 0.3-0.6 µA. It requires
380380+ /// a dedicated [`wakeup`](#method.wakeup) command to enable further I2C
381381+ /// communication.
382382+ pub async fn sleep_async(&mut self) -> Result<(), Error<I2C::Error>> {
383383+ self.send_command_async(Command::Sleep).await
384384+ }
385385+386386+ /// Trigger a soft reset. (async)
387387+ ///
388388+ /// The SHTC3 provides a soft reset mechanism that forces the system into a
389389+ /// well-defined state without removing the power supply. If the system is
390390+ /// in its idle state (i.e. if no measurement is in progress) the soft
391391+ /// reset command can be sent. This triggers the sensor to reset all
392392+ /// internal state machines and reload calibration data from the memory.
393393+ pub async fn reset_async(&mut self, delay: &mut impl DelayNs) -> Result<(), Error<I2C::Error>> {
394394+ self.send_command_async(Command::SoftwareReset).await?;
395395+ // Table 5: 180-240 µs
396396+ delay.delay_us(self.reset_duration()).await;
397397+ Ok(())
398398+ }
399399+400400+ /// Wake up sensor from [sleep mode](#method.sleep) and wait until it is ready. (async)
401401+ pub async fn wakeup_async(
402402+ &mut self,
403403+ delay: &mut impl DelayNs,
404404+ ) -> Result<(), Error<I2C::Error>> {
405405+ self.send_command_async(Command::WakeUp).await?;
406406+ delay.delay_us(self.wakeup_duration()).await;
407407+ Ok(())
408408+ }
409409+410410+ /// Run a temperature/humidity measurement and return the combined result.
411411+ ///
412412+ /// This is an async function call.
413413+ pub async fn measure_async(
414414+ &mut self,
415415+ mode: PowerMode,
416416+ delay: &mut impl DelayNs,
417417+ ) -> Result<Measurement, Error<I2C::Error>> {
418418+ self.send_command_async(Command::Measure {
419419+ power_mode: mode,
420420+ order: MeasurementOrder::TemperatureFirst,
421421+ })
422422+ .await?;
423423+424424+ delay.delay_us(self.max_measurement_duration(mode)).await;
425425+426426+ let mut buf = [0; 6];
427427+ self.read_with_crc_async(&mut buf).await?;
428428+429429+ Ok(RawMeasurement {
430430+ temperature: u16::from_be_bytes([buf[0], buf[1]]),
431431+ humidity: u16::from_be_bytes([buf[3], buf[4]]),
432432+ }
433433+ .into())
434434+ }
435435+}
436436+437437+/// General blocking functions.
438438+impl<I2C> ShtC3<I2C>
439439+where
440440+ I2C: I2c<SevenBitAddress>,
441441+{
442442+ /// Write an I²C command to the sensor.
443443+ fn send_command(&mut self, command: Command) -> Result<(), Error<I2C::Error>> {
444444+ self.i2c
445445+ .write(self.address, &command.as_bytes())
446446+ .map_err(Error::I2c)
447447+ }
448448+449449+ /// Read data into the provided buffer and validate the CRC8 checksum.
450450+ ///
451451+ /// If the checksum is wrong, return `Error::Crc`.
452452+ ///
453453+ /// Note: This method will consider every third byte a checksum byte. If
454454+ /// the buffer size is not a multiple of 3, then not all data will be
455455+ /// validated.
456456+ fn read_with_crc(&mut self, buf: &mut [u8]) -> Result<(), Error<I2C::Error>> {
457457+ self.i2c.read(self.address, buf)?;
458458+ self.validate_crc(buf)?;
459459+ Ok(())
460460+ }
461461+462462+ /// Return the raw ID register.
463463+ pub fn raw_id_register(&mut self) -> Result<u16, Error<I2C::Error>> {
464464+ // Request serial number
465465+ self.send_command(Command::ReadIdRegister)?;
466466+467467+ // Read id register
468468+ let mut buf = [0; 3];
469469+ self.read_with_crc(&mut buf)?;
470470+471471+ Ok(u16::from_be_bytes([buf[0], buf[1]]))
472472+ }
473473+474474+ /// Return the 7-bit device identifier.
475475+ ///
476476+ /// Should be 0x47 (71) for the SHTC3.
477477+ pub fn device_identifier(&mut self) -> Result<u8, Error<I2C::Error>> {
478478+ let ident = self.raw_id_register()?;
479479+ let lsb = (ident & 0b0011_1111) as u8;
480480+ let msb = ((ident & 0b0000_1000_0000_0000) >> 5) as u8;
481481+ Ok(lsb | msb)
482482+ }
483483+484484+ /// Trigger a soft reset. (blocking)
485485+ ///
486486+ /// The SHTC3 provides a soft reset mechanism that forces the system into a
487487+ /// well-defined state without removing the power supply. If the system is
488488+ /// in its idle state (i.e. if no measurement is in progress) the soft
489489+ /// reset command can be sent. This triggers the sensor to reset all
490490+ /// internal state machines and reload calibration data from the memory.
491491+ pub fn reset(&mut self, delay: &mut impl BlockingDelayNs) -> Result<(), Error<I2C::Error>> {
492492+ self.send_command(Command::SoftwareReset)?;
493493+ // Table 5: 180-240 µs
494494+ delay.delay_us(self.reset_duration());
495495+ Ok(())
496496+ }
497497+498498+ /// Trigger a soft reset.
499499+ ///
500500+ /// The SHTC3 provides a soft reset mechanism that forces the system into a
501501+ /// well-defined state without removing the power supply. If the system is
502502+ /// in its idle state (i.e. if no measurement is in progress) the soft
503503+ /// reset command can be sent. This triggers the sensor to reset all
504504+ /// internal state machines and reload calibration data from the memory.
505505+ pub fn start_reset(&mut self) -> Result<(), Error<I2C::Error>> {
506506+ self.send_command(Command::SoftwareReset)
507507+ }
508508+509509+ /// Set sensor to sleep mode.
510510+ ///
511511+ /// When in sleep mode, the sensor consumes around 0.3-0.6 µA. It requires
512512+ /// a dedicated [`wakeup`](#method.wakeup) command to enable further I2C
513513+ /// communication.
514514+ pub fn sleep(&mut self) -> Result<(), Error<I2C::Error>> {
515515+ self.send_command(Command::Sleep)
516516+ }
517517+518518+ /// Wake up sensor from [sleep mode](#method.sleep) and wait until it is ready.
519519+ pub fn wakeup(&mut self, delay: &mut impl BlockingDelayNs) -> Result<(), Error<I2C::Error>> {
520520+ self.start_wakeup()?;
521521+ delay.delay_us(self.wakeup_duration());
522522+ Ok(())
523523+ }
524524+}
525525+526526+/// Non-blocking functions for starting / reading measurements.
527527+impl<I2C> ShtC3<I2C>
528528+where
529529+ I2C: I2c<SevenBitAddress>,
530530+{
531531+ /// Start a measurement with the specified measurement order and write the
532532+ /// result into the provided buffer.
533533+ ///
534534+ /// If you just need one of the two measurements, provide a 3-byte buffer
535535+ /// instead of a 6-byte buffer.
536536+ fn start_measure_partial(
537537+ &mut self,
538538+ power_mode: PowerMode,
539539+ order: MeasurementOrder,
540540+ ) -> Result<(), Error<I2C::Error>> {
541541+ // Request measurement
542542+ self.send_command(Command::Measure { power_mode, order })
543543+ }
544544+545545+ /// Start a combined temperature / humidity measurement.
546546+ pub fn start_measurement(&mut self, mode: PowerMode) -> Result<(), Error<I2C::Error>> {
547547+ self.start_measure_partial(mode, MeasurementOrder::TemperatureFirst)
548548+ }
549549+550550+ /// Start a temperature measurement.
551551+ pub fn start_temperature_measurement(
552552+ &mut self,
553553+ mode: PowerMode,
554554+ ) -> Result<(), Error<I2C::Error>> {
555555+ self.start_measure_partial(mode, MeasurementOrder::TemperatureFirst)
556556+ }
557557+558558+ /// Start a humidity measurement.
559559+ pub fn start_humidity_measurement(&mut self, mode: PowerMode) -> Result<(), Error<I2C::Error>> {
560560+ self.start_measure_partial(mode, MeasurementOrder::HumidityFirst)
561561+ }
562562+563563+ /// Read the result of a temperature / humidity measurement.
564564+ pub fn get_measurement_result(&mut self) -> Result<Measurement, Error<I2C::Error>> {
565565+ let raw = self.get_raw_measurement_result()?;
566566+ Ok(raw.into())
567567+ }
568568+569569+ /// Read the result of a temperature measurement.
570570+ pub fn get_temperature_measurement_result(&mut self) -> Result<Temperature, Error<I2C::Error>> {
571571+ let raw = self.get_raw_partial_measurement_result()?;
572572+ Ok(Temperature::from_raw(raw))
573573+ }
574574+575575+ /// Read the result of a humidity measurement.
576576+ pub fn get_humidity_measurement_result(&mut self) -> Result<Humidity, Error<I2C::Error>> {
577577+ let raw = self.get_raw_partial_measurement_result()?;
578578+ Ok(Humidity::from_raw(raw))
579579+ }
580580+581581+ /// Read the raw result of a combined temperature / humidity measurement.
582582+ pub fn get_raw_measurement_result(&mut self) -> Result<RawMeasurement, Error<I2C::Error>> {
583583+ let mut buf = [0; 6];
584584+ self.read_with_crc(&mut buf)?;
585585+ Ok(RawMeasurement {
586586+ temperature: u16::from_be_bytes([buf[0], buf[1]]),
587587+ humidity: u16::from_be_bytes([buf[3], buf[4]]),
588588+ })
589589+ }
590590+591591+ /// Read the raw result of a partial temperature or humidity measurement.
592592+ ///
593593+ /// Return the raw 3-byte buffer (after validating CRC).
594594+ pub fn get_raw_partial_measurement_result(&mut self) -> Result<u16, Error<I2C::Error>> {
595595+ let mut buf = [0; 3];
596596+ self.read_with_crc(&mut buf)?;
597597+ Ok(u16::from_be_bytes([buf[0], buf[1]]))
598598+ }
599599+600600+ /// Wake up sensor from [sleep mode](#method.sleep).
601601+ pub fn start_wakeup(&mut self) -> Result<(), Error<I2C::Error>> {
602602+ self.send_command(Command::WakeUp)
603603+ }
604604+}
605605+606606+/// Blocking functions for doing measurements.
607607+impl<I2C> ShtC3<I2C>
608608+where
609609+ I2C: I2c<SevenBitAddress>,
610610+{
611611+ /// Wait the maximum time needed for the given measurement mode
612612+ pub fn wait_for_measurement(&mut self, mode: PowerMode, delay: &mut impl BlockingDelayNs) {
613613+ delay.delay_us(self.max_measurement_duration(mode));
614614+ }
615615+616616+ /// Run a temperature/humidity measurement and return the combined result.
617617+ ///
618618+ /// This is a blocking function call.
619619+ pub fn measure(
620620+ &mut self,
621621+ mode: PowerMode,
622622+ delay: &mut impl BlockingDelayNs,
623623+ ) -> Result<Measurement, Error<I2C::Error>> {
624624+ self.start_measurement(mode)?;
625625+ self.wait_for_measurement(mode, delay);
626626+ self.get_measurement_result()
627627+ }
628628+629629+ /// Run a temperature measurement and return the result.
630630+ ///
631631+ /// This is a blocking function call.
632632+ ///
633633+ /// Internally, it will request a measurement in "temperature first" mode
634634+ /// and only read the first half of the measurement response.
635635+ pub fn measure_temperature(
636636+ &mut self,
637637+ mode: PowerMode,
638638+ delay: &mut impl BlockingDelayNs,
639639+ ) -> Result<Temperature, Error<I2C::Error>> {
640640+ self.start_temperature_measurement(mode)?;
641641+ self.wait_for_measurement(mode, delay);
642642+ self.get_temperature_measurement_result()
643643+ }
644644+645645+ /// Run a humidity measurement and return the result.
646646+ ///
647647+ /// This is a blocking function call.
648648+ ///
649649+ /// Internally, it will request a measurement in "humidity first" mode
650650+ /// and only read the first half of the measurement response.
651651+ pub fn measure_humidity(
652652+ &mut self,
653653+ mode: PowerMode,
654654+ delay: &mut impl BlockingDelayNs,
655655+ ) -> Result<Humidity, Error<I2C::Error>> {
656656+ self.start_humidity_measurement(mode)?;
657657+ self.wait_for_measurement(mode, delay);
658658+ self.get_humidity_measurement_result()
659659+ }
660660+}
661661+662662+#[cfg(test)]
663663+mod tests {
664664+ extern crate alloc;
665665+666666+ use super::*;
667667+668668+ use embedded_hal::i2c::ErrorKind;
669669+ use embedded_hal_mock::eh1::{
670670+ delay::NoopDelay,
671671+ i2c::{Mock as I2cMock, Transaction},
672672+ };
673673+674674+ const SHT_ADDR: u8 = 0x70;
675675+676676+ mod core {
677677+ use super::*;
678678+679679+ /// Test whether the `send_command` function propagates I²C errors.
680680+ #[test]
681681+ fn send_command_error() {
682682+ let expectations =
683683+ [Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8])
684684+ .with_error(ErrorKind::Other)];
685685+ let mock = I2cMock::new(&expectations);
686686+ let mut sht = ShtC3::new(mock);
687687+ let err = sht.send_command(Command::ReadIdRegister).unwrap_err();
688688+ assert_eq!(err, Error::I2c(ErrorKind::Other));
689689+ sht.destroy().done();
690690+ }
691691+692692+ /// Test the `validate_crc` function.
693693+ #[test]
694694+ fn validate_crc() {
695695+ let mock = I2cMock::new(&[]);
696696+ let sht = ShtC3::new(mock);
697697+698698+ // Not enough data
699699+ sht.validate_crc(&[]).unwrap();
700700+ sht.validate_crc(&[0xbe]).unwrap();
701701+ sht.validate_crc(&[0xbe, 0xef]).unwrap();
702702+703703+ // Valid CRC
704704+ sht.validate_crc(&[0xbe, 0xef, 0x92]).unwrap();
705705+706706+ // Invalid CRC
707707+ match sht.validate_crc(&[0xbe, 0xef, 0x91]) {
708708+ Err(CrcError) => {}
709709+ Ok(_) => panic!("CRC check did not fail"),
710710+ }
711711+712712+ // Valid CRC (8 bytes)
713713+ sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0x92, 0x00, 0x00])
714714+ .unwrap();
715715+716716+ // Invalid CRC (8 bytes)
717717+ match sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0xff, 0x00, 0x00]) {
718718+ Err(CrcError) => {}
719719+ Ok(_) => panic!("CRC check did not fail"),
720720+ }
721721+722722+ sht.destroy().done();
723723+ }
724724+725725+ /// Test the `read_with_crc` function.
726726+ #[test]
727727+ fn read_with_crc() {
728728+ let mut buf = [0; 3];
729729+730730+ // Valid CRC
731731+ let expectations = [Transaction::read(SHT_ADDR, alloc::vec![0xbe, 0xef, 0x92])];
732732+ let mock = I2cMock::new(&expectations);
733733+ let mut sht = ShtC3::new(mock);
734734+ sht.read_with_crc(&mut buf).unwrap();
735735+ assert_eq!(buf, [0xbe, 0xef, 0x92]);
736736+ sht.destroy().done();
737737+738738+ // Invalid CRC
739739+ let expectations = [Transaction::read(SHT_ADDR, alloc::vec![0xbe, 0xef, 0x00])];
740740+ let mock = I2cMock::new(&expectations);
741741+ let mut sht = ShtC3::new(mock);
742742+ match sht.read_with_crc(&mut buf) {
743743+ Err(Error::Crc) => {}
744744+ Err(_) => panic!("Invalid error: Must be Crc"),
745745+ Ok(_) => panic!("CRC check did not fail"),
746746+ }
747747+ assert_eq!(buf, [0xbe, 0xef, 0x00]); // Buf was changed
748748+ sht.destroy().done();
749749+ }
750750+ }
751751+752752+ mod factory_functions {
753753+ use super::*;
754754+755755+ #[test]
756756+ fn new_shtc3() {
757757+ let mock = I2cMock::new(&[]);
758758+ let sht = ShtC3::new(mock);
759759+ assert_eq!(sht.address, 0x70);
760760+ sht.destroy().done();
761761+ }
762762+ }
763763+764764+ mod device_info {
765765+ use super::*;
766766+767767+ /// Test the `raw_id_register` function.
768768+ #[test]
769769+ fn raw_id_register() {
770770+ let msb = 0b00001000;
771771+ let lsb = 0b00000111;
772772+ let crc = crc8(&[msb, lsb]);
773773+ let expectations = [
774774+ Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]),
775775+ Transaction::read(SHT_ADDR, alloc::vec![msb, lsb, crc]),
776776+ ];
777777+ let mock = I2cMock::new(&expectations);
778778+ let mut sht = ShtC3::new(mock);
779779+ let val = sht.raw_id_register().unwrap();
780780+ assert_eq!(val, (msb as u16) << 8 | (lsb as u16));
781781+ sht.destroy().done();
782782+ }
783783+784784+ /// Test the `device_identifier` function.
785785+ #[test]
786786+ fn device_identifier() {
787787+ let msb = 0b00001000;
788788+ let lsb = 0b00000111;
789789+ let crc = crc8(&[msb, lsb]);
790790+ let expectations = [
791791+ Transaction::write(SHT_ADDR, alloc::vec![0xef, 0xc8]),
792792+ Transaction::read(SHT_ADDR, alloc::vec![msb, lsb, crc]),
793793+ ];
794794+ let mock = I2cMock::new(&expectations);
795795+ let mut sht = ShtC3::new(mock);
796796+ let ident = sht.device_identifier().unwrap();
797797+ assert_eq!(ident, 0b01000111);
798798+ sht.destroy().done();
799799+ }
800800+ }
801801+802802+ mod measurements {
803803+ use super::*;
804804+805805+ #[test]
806806+ fn measure_normal() {
807807+ let expectations = [
808808+ // Expect a write command: Normal mode measurement, temperature
809809+ // first, no clock stretching.
810810+ Transaction::write(SHT_ADDR, alloc::vec![0x78, 0x66]),
811811+ // Return the measurement result (using example values from the
812812+ // datasheet, section 5.4 "Measuring and Reading the Signals")
813813+ Transaction::read(
814814+ SHT_ADDR,
815815+ alloc::vec![
816816+ 0b0110_0100,
817817+ 0b1000_1011,
818818+ 0b1100_0111,
819819+ 0b1010_0001,
820820+ 0b0011_0011,
821821+ 0b0001_1100,
822822+ ],
823823+ ),
824824+ ];
825825+ let mock = I2cMock::new(&expectations);
826826+ let mut sht = ShtC3::new(mock);
827827+ let mut delay = NoopDelay;
828828+ let measurement = sht.measure(PowerMode::NormalMode, &mut delay).unwrap();
829829+ assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); // 23.7°C
830830+ assert_eq!(measurement.humidity.as_millipercent(), 62_968); // 62.9 %RH
831831+ sht.destroy().done();
832832+ }
833833+834834+ #[test]
835835+ fn measure_low_power() {
836836+ let expectations = [
837837+ // Expect a write command: Low power mode measurement, temperature
838838+ // first, no clock stretching.
839839+ Transaction::write(SHT_ADDR, alloc::vec![0x60, 0x9C]),
840840+ // Return the measurement result (using example values from the
841841+ // datasheet, section 5.4 "Measuring and Reading the Signals")
842842+ Transaction::read(
843843+ SHT_ADDR,
844844+ alloc::vec![
845845+ 0b0110_0100,
846846+ 0b1000_1011,
847847+ 0b1100_0111,
848848+ 0b1010_0001,
849849+ 0b0011_0011,
850850+ 0b0001_1100,
851851+ ],
852852+ ),
853853+ ];
854854+ let mock = I2cMock::new(&expectations);
855855+ let mut sht = ShtC3::new(mock);
856856+ let mut delay = NoopDelay;
857857+ let measurement = sht.measure(PowerMode::LowPower, &mut delay).unwrap();
858858+ assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); // 23.7°C
859859+ assert_eq!(measurement.humidity.as_millipercent(), 62_968); // 62.9 %RH
860860+ sht.destroy().done();
861861+ }
862862+863863+ #[test]
864864+ fn measure_temperature_only() {
865865+ let expectations = [
866866+ // Expect a write command: Normal mode measurement, temperature
867867+ // first, no clock stretching.
868868+ Transaction::write(SHT_ADDR, alloc::vec![0x78, 0x66]),
869869+ // Return the measurement result (using example values from the
870870+ // datasheet, section 5.4 "Measuring and Reading the Signals")
871871+ Transaction::read(SHT_ADDR, alloc::vec![0b0110_0100, 0b1000_1011, 0b1100_0111]),
872872+ ];
873873+ let mock = I2cMock::new(&expectations);
874874+ let mut sht = ShtC3::new(mock);
875875+ let mut delay = NoopDelay;
876876+ let temperature = sht
877877+ .measure_temperature(PowerMode::NormalMode, &mut delay)
878878+ .unwrap();
879879+ assert_eq!(temperature.as_millidegrees_celsius(), 23_730); // 23.7°C
880880+ sht.destroy().done();
881881+ }
882882+883883+ #[test]
884884+ fn measure_humidity_only() {
885885+ let expectations = [
886886+ // Expect a write command: Normal mode measurement, humidity
887887+ // first, no clock stretching.
888888+ Transaction::write(SHT_ADDR, alloc::vec![0x58, 0xE0]),
889889+ // Return the measurement result (using example values from the
890890+ // datasheet, section 5.4 "Measuring and Reading the Signals")
891891+ Transaction::read(SHT_ADDR, alloc::vec![0b1010_0001, 0b0011_0011, 0b0001_1100]),
892892+ ];
893893+ let mock = I2cMock::new(&expectations);
894894+ let mut sht = ShtC3::new(mock);
895895+ let mut delay = NoopDelay;
896896+ let humidity = sht
897897+ .measure_humidity(PowerMode::NormalMode, &mut delay)
898898+ .unwrap();
899899+ assert_eq!(humidity.as_millipercent(), 62_968); // 62.9 %RH
900900+ sht.destroy().done();
901901+ }
902902+903903+ /// Ensure that I²C write errors are handled when measuring.
904904+ #[test]
905905+ fn measure_write_error() {
906906+ let expectations =
907907+ [Transaction::write(SHT_ADDR, alloc::vec![0x60, 0x9C])
908908+ .with_error(ErrorKind::Other)];
909909+ let mock = I2cMock::new(&expectations);
910910+ let mut sht = ShtC3::new(mock);
911911+ let err = sht
912912+ .measure(PowerMode::LowPower, &mut NoopDelay)
913913+ .unwrap_err();
914914+ assert_eq!(err, Error::I2c(ErrorKind::Other));
915915+ sht.destroy().done();
916916+ }
917917+ }
918918+919919+ mod power_management {
920920+ use super::*;
921921+922922+ /// Test the `sleep` function.
923923+ #[test]
924924+ fn sleep() {
925925+ let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0xB0, 0x98])];
926926+ let mock = I2cMock::new(&expectations);
927927+ let mut sht = ShtC3::new(mock);
928928+ sht.sleep().unwrap();
929929+ sht.destroy().done();
930930+ }
931931+932932+ /// Test the `wakeup` function.
933933+ #[test]
934934+ fn wakeup() {
935935+ let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0x35, 0x17])];
936936+ let mock = I2cMock::new(&expectations);
937937+ let mut sht = ShtC3::new(mock);
938938+ sht.wakeup(&mut NoopDelay).unwrap();
939939+ sht.destroy().done();
940940+ }
941941+942942+ /// Test the `reset` function.
943943+ #[test]
944944+ fn reset() {
945945+ let expectations = [Transaction::write(SHT_ADDR, alloc::vec![0x80, 0x5D])];
946946+ let mock = I2cMock::new(&expectations);
947947+ let mut sht = ShtC3::new(mock);
948948+ sht.reset(&mut NoopDelay).unwrap();
949949+ sht.destroy().done();
950950+ }
951951+ }
952952+953953+ mod max_measurement_duration {
954954+ use super::*;
955955+956956+ #[test]
957957+ fn shortcut_function() {
958958+ let c3 = ShtC3::new(I2cMock::new(&[]));
959959+960960+ assert_eq!(c3.max_measurement_duration(PowerMode::NormalMode), 12100);
961961+ assert_eq!(c3.max_measurement_duration(PowerMode::LowPower), 800);
962962+963963+ c3.destroy().done();
964964+ }
965965+ }
966966+}
+199
sachy-shtc3/src/types.rs
···11+/// A temperature measurement.
22+#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
33+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
44+pub struct Temperature(i32);
55+66+/// A humidity measurement.
77+#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
88+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
99+pub struct Humidity(i32);
1010+1111+/// A combined temperature / humidity measurement.
1212+#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
1313+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
1414+pub struct Measurement {
1515+ /// The measured temperature.
1616+ pub temperature: Temperature,
1717+ /// The measured humidity.
1818+ pub humidity: Humidity,
1919+}
2020+2121+impl core::ops::AddAssign for Measurement {
2222+ fn add_assign(&mut self, rhs: Self) {
2323+ self.temperature.0 += rhs.temperature.0;
2424+ self.humidity.0 += rhs.humidity.0;
2525+ }
2626+}
2727+2828+impl core::ops::DivAssign<i32> for Measurement {
2929+ fn div_assign(&mut self, rhs: i32) {
3030+ self.temperature.0 /= rhs;
3131+ self.humidity.0 /= rhs;
3232+ }
3333+}
3434+3535+/// A combined raw temperature / humidity measurement.
3636+///
3737+/// The raw values are of type u16. They require a conversion formula for
3838+/// conversion to a temperature / humidity value (see datasheet).
3939+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
4040+#[cfg_attr(feature = "defmt", derive(defmt::Format))]
4141+pub struct RawMeasurement {
4242+ /// The measured temperature (raw value).
4343+ pub temperature: u16,
4444+ /// The measured humidity (raw value).
4545+ pub humidity: u16,
4646+}
4747+4848+impl From<RawMeasurement> for Measurement {
4949+ fn from(other: RawMeasurement) -> Self {
5050+ Self {
5151+ temperature: Temperature::from_raw(other.temperature),
5252+ humidity: Humidity::from_raw(other.humidity),
5353+ }
5454+ }
5555+}
5656+5757+impl Temperature {
5858+ /// Create a new `Temperature` from a raw measurement result.
5959+ pub const fn from_raw(raw: u16) -> Self {
6060+ Self(convert_temperature(raw))
6161+ }
6262+6363+ /// Return temperature in milli-degrees celsius.
6464+ pub const fn as_millidegrees_celsius(&self) -> i32 {
6565+ self.0
6666+ }
6767+6868+ /// Return temperature in degrees celcius with 0.01 precision
6969+ pub const fn as_10mk_celsius(&self) -> i16 {
7070+ (self.0 / 10) as i16
7171+ }
7272+7373+ /// Return temperature in degrees celsius.
7474+ pub const fn as_degrees_celsius(&self) -> f32 {
7575+ self.0 as f32 / 1000.0
7676+ }
7777+}
7878+7979+impl Humidity {
8080+ /// Create a new `Humidity` from a raw measurement result.
8181+ pub const fn from_raw(raw: u16) -> Self {
8282+ Self(convert_humidity(raw))
8383+ }
8484+8585+ /// Return relative humidity in 1/100 %RH
8686+ pub const fn as_10mk_percent(&self) -> u16 {
8787+ (self.0 / 10).unsigned_abs() as u16
8888+ }
8989+9090+ /// Return relative humidity in 1/1000 %RH.
9191+ pub const fn as_millipercent(&self) -> i32 {
9292+ self.0
9393+ }
9494+9595+ /// Return relative humidity in 1 %RH
9696+ pub const fn as_1k_percent(&self) -> u8 {
9797+ (self.0 / 1000).unsigned_abs() as u8
9898+ }
9999+100100+ /// Return relative humidity in %RH.
101101+ pub const fn as_percent(&self) -> f32 {
102102+ self.0 as f32 / 1000.0
103103+ }
104104+}
105105+106106+/// Convert raw temperature measurement to milli-degrees celsius.
107107+///
108108+/// Formula (datasheet 5.11): -45 + 175 * (val / 2^16),
109109+/// optimized for fixed point math.
110110+#[inline]
111111+const fn convert_temperature(temp_raw: u16) -> i32 {
112112+ (((temp_raw as u32) * 21875) >> 13) as i32 - 45000
113113+}
114114+115115+/// Convert raw humidity measurement to relative humidity.
116116+///
117117+/// Formula (datasheet 5.11): 100 * (val / 2^16),
118118+/// optimized for fixed point math.
119119+#[inline]
120120+const fn convert_humidity(humi_raw: u16) -> i32 {
121121+ (((humi_raw as u32) * 12500) >> 13) as i32
122122+}
123123+124124+#[cfg(test)]
125125+mod tests {
126126+ use super::*;
127127+128128+ /// Test conversion of raw measurement results into °C.
129129+ #[test]
130130+ fn test_convert_temperature() {
131131+ let test_data = [
132132+ (0x0000, -45000),
133133+ // Datasheet setion 5.11 "Conversion of Sensor Output"
134134+ ((0b0110_0100_u16 << 8) | 0b1000_1011, 23730),
135135+ ];
136136+ for td in &test_data {
137137+ assert_eq!(convert_temperature(td.0), td.1);
138138+ }
139139+ }
140140+141141+ /// Test conversion of raw measurement results into %RH.
142142+ #[test]
143143+ fn test_convert_humidity() {
144144+ let test_data = [
145145+ (0x0000, 0),
146146+ // Datasheet setion 5.11 "Conversion of Sensor Output"
147147+ ((0b1010_0001_u16 << 8) | 0b0011_0011, 62968),
148148+ ];
149149+ for td in &test_data {
150150+ assert_eq!(convert_humidity(td.0), td.1);
151151+ }
152152+ }
153153+154154+ /// Test conversion of raw measurement results into °C and %RH.
155155+ #[test]
156156+ fn measurement_conversion() {
157157+ // Datasheet setion 5.11 "Conversion of Sensor Output"
158158+ let temperature = convert_temperature((0b0110_0100_u16 << 8) | 0b1000_1011);
159159+ let humidity = convert_humidity((0b1010_0001_u16 << 8) | 0b0011_0011);
160160+ assert_eq!(temperature, 23730);
161161+ assert_eq!(humidity, 62968);
162162+ }
163163+164164+ #[test]
165165+ fn temperature() {
166166+ let temp = Temperature(24123);
167167+ assert_eq!(temp.as_millidegrees_celsius(), 24123);
168168+ assert_eq!(temp.as_degrees_celsius(), 24.123);
169169+ }
170170+171171+ #[test]
172172+ fn humidity() {
173173+ let humi = Humidity(65432);
174174+ assert_eq!(humi.as_millipercent(), 65432);
175175+ assert_eq!(humi.as_percent(), 65.432);
176176+ }
177177+178178+ #[test]
179179+ fn measurement_from_into() {
180180+ // Datasheet setion 5.11 "Conversion of Sensor Output"
181181+ let raw = RawMeasurement {
182182+ temperature: (0b0110_0100_u16 << 8) | 0b1000_1011,
183183+ humidity: (0b1010_0001_u16 << 8) | 0b0011_0011,
184184+ };
185185+186186+ // std::convert::From
187187+ let measurement1 = Measurement::from(raw);
188188+ assert_eq!(measurement1.temperature.0, 23730);
189189+ assert_eq!(measurement1.humidity.0, 62968);
190190+191191+ // std::convert::Into
192192+ let measurement2: Measurement = raw.into();
193193+ assert_eq!(measurement2.temperature.0, 23730);
194194+ assert_eq!(measurement2.humidity.0, 62968);
195195+196196+ // std::cmp::PartialEq
197197+ assert_eq!(measurement1, measurement2);
198198+ }
199199+}