diff --git a/Cargo.toml b/Cargo.toml index fb84d20..0cb3b6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,31 +1,30 @@ [package] -name = "sht4x" -description = "Sensirion SHT4x Driver for Embedded HAL" -version = "0.1.0" +name = "sht4x-ng" +description = "Sensirion SHT4x Driver for Embedded HAL 1.0 with optional async support" +version = "0.2.2" edition = "2021" -authors = ["Christian Meusel <christian.meusel@posteo.de>"] +authors = [ + "Christian Meusel <christian.meusel@posteo.de>", + "Brian Bustin", + "Moritz Bitsch", +] license = "MIT OR Apache-2.0" categories = ["embedded", "hardware-support", "no-std"] keywords = ["driver", "embedded-hal-driver", "sensirion", "sht40"] -documentation = "https://docs.rs/sht4x" -repository = "https://github.com/sirhcel/sht4x" -readme = "README.md" - -exclude = [ - "/.github/", - ".gitignore", -] +exclude = ["/.github/", ".gitignore"] [dependencies] -defmt = { version = "0.3.2", optional = true } -embedded-hal = "0.2.7" -fixed = "1.20.0" -sensirion-i2c = "0.2" +defmt = { version = "0.3.8", optional = true } +embedded-hal = "1.0.0" +embedded-hal-async = { version = "1.0.0", optional = true } +fixed = "1.27.0" +sensirion-i2c = "0.3.0" [features] defmt = ["dep:defmt"] +async = ["dep:embedded-hal-async"] diff --git a/README.md b/README.md index b6e8f32..84277e4 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ +# Fork of https://github.com/sirhcel/sht4x/ +This is a fork of sirhcel's sht4x library. It brings async to the library and updates it to use embedded_hal 1.0.0. I based this code on [madmo's branch](https://github.com/madmo/sht4x/) as he already did the embedded-hal updating work in [his pull request](https://github.com/sirhcel/sht4x/pull/3). + +The fork is hopefully temporary. [I have tried to get these changes pulled into the sht4x repo without success](https://github.com/sirhcel/sht4x/pull/5). + # Sensirion SHT4x Driver for Embedded HAL A platform agnostic device driver for the Sensirion [SHT4x temperature and @@ -13,31 +18,31 @@ tested with the SHT40-AD1B so far. [](https://crates.io/crates/sht4x)  - ## Features - Blocking operation +- Async operation if the `async` feature is specified +- Uses the latest embedded_hal 1.0.0 - Supports all commands specified in the [datasheet](https://sensirion.com/media/documents/33FD6951/624C4357/Datasheet_SHT4x.pdf) -- Explicitly borrows `DelayMs` for command execution so that it could be shared +- Explicitly borrows `DelayNs` for command execution so that it could be shared (among multiple sensors) - Could be instantiated with the alternative I2C address for the SHT40-BD1B - Uses fixed-point arithmetics for converting raw sensor data into measurements in SI units - - Based on `I16F16` from the [`fixed`](https://gitlab.com/tspiteri/fixed) - crate - - Allows conversion to floating-point values, if needed - - Convenience methods for fixed-point conversions to milli degree Celsius - or milli percent relative humidity which are commonly used by drivers for - other humidity and temperature sensors from Sensirion + - Based on `I16F16` from the [`fixed`](https://gitlab.com/tspiteri/fixed) + crate + - Allows conversion to floating-point values, if needed + - Convenience methods for fixed-point conversions to milli degree Celsius + or milli percent relative humidity which are commonly used by drivers for + other humidity and temperature sensors from Sensirion - Optional support for [`defmt`](https://github.com/knurling-rs/defmt) - ## Example ```rust ignore -use embedded_hal::blocking::delay::DelayMs; -use sht4x::Sht4x; +use embedded_hal::delay::DelayNs; +use sht4x_ng::Sht4x; // Device-specific use declarations. let mut delay = // Device-specific initialization of delay. @@ -59,13 +64,11 @@ if let Ok(measurement) = measurement { } ``` - ## Related Work [sensor-temp-humidity-sht40](https://github.com/lc525/sensor-temp-humidity-sht40-rs) is another driver for this sensor family. - ## License Licensed under either of @@ -78,7 +81,6 @@ Licensed under either of at your discretion. - ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted diff --git a/src/commands.rs b/src/commands.rs index 132e12f..3d78617 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -31,7 +31,7 @@ impl Command { } } - pub(crate) fn duration_ms(&self) -> u16 { + pub(crate) fn duration_ms(&self) -> u32 { // Values rounded up from the maximum durations given in the datasheet // table 4, 'System timing specifications'. match self { diff --git a/src/error.rs b/src/error.rs index c4af875..edf2a0a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,4 @@ -use embedded_hal::blocking::i2c::{Read, Write}; -use sensirion_i2c::i2c; +use embedded_hal::i2c::I2c; /// Error conditions from accessing SHT4x sensors. #[cfg_attr(feature = "defmt", derive(defmt::Format))] @@ -12,16 +11,21 @@ pub enum Error<E> { Crc, } -impl<E, W, R> From<i2c::Error<W, R>> for Error<E> -where - W: Write<Error = E>, - R: Read<Error = E>, -{ - fn from(err: i2c::Error<W, R>) -> Self { +impl<I: I2c> From<sensirion_i2c::i2c::Error<I>> for Error<I::Error> { + fn from(err: sensirion_i2c::i2c::Error<I>) -> Self { match err { - i2c::Error::Crc => Error::Crc, - i2c::Error::I2cRead(e) => Error::I2c(e), - i2c::Error::I2cWrite(e) => Error::I2c(e), + sensirion_i2c::i2c::Error::Crc => Error::Crc, + sensirion_i2c::i2c::Error::I2cRead(e) => Error::I2c(e), + sensirion_i2c::i2c::Error::I2cWrite(e) => Error::I2c(e), + } + } +} + +#[cfg(feature = "async")] +impl<E> From<sensirion_i2c::crc8::Error> for Error<E> { + fn from(value: sensirion_i2c::crc8::Error) -> Self { + match value { + sensirion_i2c::crc8::Error::CrcError => Error::Crc, } } } diff --git a/src/sht4x.rs b/src/sht4x.rs index a49bf7d..5ae13b8 100644 --- a/src/sht4x.rs +++ b/src/sht4x.rs @@ -4,11 +4,12 @@ use crate::{ types::{Address, HeatingDuration, HeatingPower, Measurement, Precision, SensorData}, }; use core::marker::PhantomData; -use embedded_hal::blocking::{ - delay::DelayMs, - i2c::{Read, Write, WriteRead}, -}; -use sensirion_i2c::i2c; + +#[cfg(not(feature = "async"))] +use embedded_hal::{delay::DelayNs, i2c::I2c}; + +#[cfg(feature = "async")] +use embedded_hal_async::{delay::DelayNs, i2c::I2c}; const RESPONSE_LEN: usize = 6; @@ -46,10 +47,9 @@ impl From<Precision> for Command { } } -impl<I, D, E> Sht4x<I, D> +impl<I: I2c, D> Sht4x<I, D> where - I: Read<Error = E> + Write<Error = E> + WriteRead<Error = E>, - D: DelayMs<u16>, + D: DelayNs, { /// Creates a new driver instance using the given I2C bus. It configures the default I2C /// address 0x44 used by most family members. @@ -78,6 +78,7 @@ where self.i2c } + #[cfg(not(feature = "async"))] /// Activates the heater and performs a measurement returning measurands in SI units. /// /// **Note:** The heater is designed to be used up to 10 % of the sensor's lifetime. Please @@ -89,12 +90,31 @@ where power: HeatingPower, duration: HeatingDuration, delay: &mut D, - ) -> Result<Measurement, Error<E>> { + ) -> Result<Measurement, Error<I::Error>> { let raw = self.heat_and_measure_raw(power, duration, delay)?; Ok(Measurement::from(raw)) } + #[cfg(feature = "async")] + /// Activates the heater and performs a measurement returning measurands in SI units. + /// + /// **Note:** The heater is designed to be used up to 10 % of the sensor's lifetime. Please + /// check the + /// [datasheet](https://sensirion.com/media/documents/33FD6951/624C4357/Datasheet_SHT4x.pdf), + /// section 4.9 _Heater Operation_ for details. + pub async fn heat_and_measure( + &mut self, + power: HeatingPower, + duration: HeatingDuration, + delay: &mut D, + ) -> Result<Measurement, Error<I::Error>> { + let raw = self.heat_and_measure_raw(power, duration, delay).await?; + + Ok(Measurement::from(raw)) + } + + #[cfg(not(feature = "async"))] /// Activates the heater and performs a measurement returning raw sensor data. /// /// **Note:** The heater is designed to be used up to 10 % of the sensor's lifetime. Please @@ -106,7 +126,7 @@ where power: HeatingPower, duration: HeatingDuration, delay: &mut D, - ) -> Result<SensorData, Error<E>> { + ) -> Result<SensorData, Error<I::Error>> { let command = Command::from((power, duration)); self.write_command_and_delay_for_execution(command, delay)?; @@ -116,22 +136,58 @@ where Ok(raw) } + #[cfg(feature = "async")] + /// Activates the heater and performs a measurement returning raw sensor data. + /// + /// **Note:** The heater is designed to be used up to 10 % of the sensor's lifetime. Please + /// check the + /// [datasheet](https://sensirion.com/media/documents/33FD6951/624C4357/Datasheet_SHT4x.pdf), + /// section 4.9 _Heater Operation_ for details. + pub async fn heat_and_measure_raw( + &mut self, + power: HeatingPower, + duration: HeatingDuration, + delay: &mut D, + ) -> Result<SensorData, Error<I::Error>> { + let command = Command::from((power, duration)); + + self.write_command_and_delay_for_execution(command, delay) + .await?; + let response = self.read_response().await?; + let raw = self.sensor_data_from_response(&response); + + Ok(raw) + } + + #[cfg(not(feature = "async"))] /// Performs a measurement returning measurands in SI units. pub fn measure( &mut self, precision: Precision, delay: &mut D, - ) -> Result<Measurement, Error<E>> { + ) -> Result<Measurement, Error<I::Error>> { let raw = self.measure_raw(precision, delay)?; Ok(Measurement::from(raw)) } + #[cfg(feature = "async")] + /// Performs a measurement returning measurands in SI units. + pub async fn measure( + &mut self, + precision: Precision, + delay: &mut D, + ) -> Result<Measurement, Error<I::Error>> { + let raw = self.measure_raw(precision, delay).await?; + Ok(Measurement::from(raw)) + } + + #[cfg(not(feature = "async"))] /// Performs a measurement returning raw sensor data. pub fn measure_raw( &mut self, precision: Precision, delay: &mut D, - ) -> Result<SensorData, Error<E>> { + ) -> Result<SensorData, Error<I::Error>> { let command = Command::from(precision); self.write_command_and_delay_for_execution(command, delay)?; @@ -141,8 +197,26 @@ where Ok(raw) } + #[cfg(feature = "async")] + /// Performs a measurement returning raw sensor data. + pub async fn measure_raw( + &mut self, + precision: Precision, + delay: &mut D, + ) -> Result<SensorData, Error<I::Error>> { + let command = Command::from(precision); + + self.write_command_and_delay_for_execution(command, delay) + .await?; + let response = self.read_response().await?; + let raw = self.sensor_data_from_response(&response); + + Ok(raw) + } + + #[cfg(not(feature = "async"))] /// Reads the sensor's serial number. - pub fn serial_number(&mut self, delay: &mut D) -> Result<u32, Error<E>> { + pub fn serial_number(&mut self, delay: &mut D) -> Result<u32, Error<I::Error>> { self.write_command_and_delay_for_execution(Command::SerialNumber, delay)?; let response = self.read_response()?; @@ -154,15 +228,53 @@ where ])) } + #[cfg(feature = "async")] + /// Reads the sensor's serial number. + pub async fn serial_number(&mut self, delay: &mut D) -> Result<u32, Error<I::Error>> { + self.write_command_and_delay_for_execution(Command::SerialNumber, delay) + .await?; + let response = self.read_response().await?; + + Ok(u32::from_be_bytes([ + response[0], + response[1], + response[3], + response[4], + ])) + } + + #[cfg(not(feature = "async"))] + /// Performs a soft reset of the sensor. + pub fn soft_reset(&mut self, delay: &mut D) -> Result<(), Error<I::Error>> { + self.write_command_and_delay_for_execution(Command::SoftReset, delay) + } + + #[cfg(feature = "async")] /// Performs a soft reset of the sensor. - pub fn soft_reset(&mut self, delay: &mut D) -> Result<(), Error<E>> { + pub async fn soft_reset(&mut self, delay: &mut D) -> Result<(), Error<I::Error>> { self.write_command_and_delay_for_execution(Command::SoftReset, delay) + .await } - fn read_response(&mut self) -> Result<[u8; RESPONSE_LEN], Error<E>> { + #[cfg(not(feature = "async"))] + fn read_response(&mut self) -> Result<[u8; RESPONSE_LEN], Error<I::Error>> { let mut response = [0; RESPONSE_LEN]; - i2c::read_words_with_crc(&mut self.i2c, self.address.into(), &mut response)?; + sensirion_i2c::i2c::read_words_with_crc(&mut self.i2c, self.address.into(), &mut response)?; + + Ok(response) + } + + #[cfg(feature = "async")] + async fn read_response(&mut self) -> Result<[u8; RESPONSE_LEN], Error<I::Error>> { + let mut response = [0; RESPONSE_LEN]; + + self.i2c + .read(self.address.into(), &mut response) + .await + .map_err(Error::I2c)?; + + sensirion_i2c::crc8::validate(&response)?; Ok(response) } @@ -174,16 +286,36 @@ where } } + #[cfg(not(feature = "async"))] fn write_command_and_delay_for_execution( &mut self, command: Command, delay: &mut D, - ) -> Result<(), Error<E>> { + ) -> Result<(), Error<I::Error>> { let code = command.code(); - i2c::write_command_u8(&mut self.i2c, self.address.into(), code).map_err(Error::I2c)?; + sensirion_i2c::i2c::write_command_u8(&mut self.i2c, self.address.into(), code) + .map_err(Error::I2c)?; delay.delay_ms(command.duration_ms()); Ok(()) } + + #[cfg(feature = "async")] + async fn write_command_and_delay_for_execution( + &mut self, + command: Command, + delay: &mut D, + ) -> Result<(), Error<I::Error>> { + let code = command.code(); + + self.i2c + .write(self.address.into(), &code.to_be_bytes()) + .await + .map_err(Error::I2c)?; + + delay.delay_ms(command.duration_ms()).await; + + Ok(()) + } }