Skip to content

Commit ff01f7e

Browse files
committed
Add asynchronous sensor API
1 parent 0a9aa18 commit ff01f7e

File tree

4 files changed

+178
-0
lines changed

4 files changed

+178
-0
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ defmt = { version = "0.3.2", optional = true }
2626
embedded-hal = "0.2.7"
2727
fixed = "1.20.0"
2828
sensirion-i2c = "0.2"
29+
embedded-hal-async = { version = "1.0.0-rc.1", optional = true }
2930

3031
[features]
32+
default = ["blocking"]
3133
defmt = ["dep:defmt"]
34+
blocking = []
35+
async = ["dep:embedded-hal-async"]

src/error.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use embedded_hal::blocking::i2c::{Read, Write};
2+
use sensirion_i2c::crc8::Error as CrcError;
23
use sensirion_i2c::i2c;
34

45
/// Error conditions from accessing SHT4x sensors.
@@ -25,3 +26,11 @@ where
2526
}
2627
}
2728
}
29+
30+
impl<E> From<CrcError> for Error<E> {
31+
fn from(value: CrcError) -> Self {
32+
match value {
33+
CrcError::CrcError => Error::Crc,
34+
}
35+
}
36+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ mod types;
1010
pub use crate::error::*;
1111
pub use crate::sht4x::*;
1212
pub use crate::types::*;
13+
14+
#[cfg(all(feature = "blocking", feature = "async"))]
15+
compile_error!("Cannot enable both `blocking` and `async` features");

src/sht4x.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,17 @@ use crate::{
44
types::{Address, HeatingDuration, HeatingPower, Measurement, Precision, SensorData},
55
};
66
use core::marker::PhantomData;
7+
#[cfg(feature = "blocking")]
78
use embedded_hal::blocking::{
89
delay::DelayMs,
910
i2c::{Read, Write, WriteRead},
1011
};
12+
#[cfg(feature = "async")]
13+
use embedded_hal_async::{
14+
delay::DelayUs,
15+
i2c::{I2c, SevenBitAddress},
16+
};
17+
#[cfg(feature = "blocking")]
1118
use sensirion_i2c::i2c;
1219

1320
const RESPONSE_LEN: usize = 6;
@@ -46,6 +53,7 @@ impl From<Precision> for Command {
4653
}
4754
}
4855

56+
#[cfg(feature = "blocking")]
4957
impl<I, D, E> Sht4x<I, D>
5058
where
5159
I: Read<Error = E> + Write<Error = E> + WriteRead<Error = E>,
@@ -187,3 +195,157 @@ where
187195
Ok(())
188196
}
189197
}
198+
199+
#[cfg(feature = "async")]
200+
impl<I, D, E> Sht4x<I, D>
201+
where
202+
I: I2c<SevenBitAddress, Error = E>,
203+
D: DelayUs,
204+
{
205+
/// Creates a new driver instance using the given I2C bus. It configures the default I2C
206+
/// address 0x44 used by most family members.
207+
///
208+
/// For operating multiple devices on the same bus,
209+
/// [`shared-bus`](https://github.com/Rahix/shared-bus) might come in handy.
210+
pub fn new(i2c: I) -> Self {
211+
Self::new_with_address(i2c, Address::Address0x44)
212+
}
213+
214+
/// Crates a new driver instance using the given I2C bus and address. This constructor allows
215+
/// to instantiate the driver for the SHT40-BD1B which uses the non-default I2C address 0x45.
216+
///
217+
/// For operating multiple devices on the same bus,
218+
/// [`shared-bus`](https://github.com/Rahix/shared-bus) might come in handy.
219+
pub fn new_with_address(i2c: I, address: Address) -> Self {
220+
Sht4x {
221+
i2c,
222+
address,
223+
_delay: PhantomData,
224+
}
225+
}
226+
227+
/// Destroys the driver and returns the used I2C bus.
228+
pub fn destroy(self) -> I {
229+
self.i2c
230+
}
231+
232+
/// Activates the heater and performs a measurement returning measurands in SI units.
233+
///
234+
/// **Note:** The heater is designed to be used up to 10 % of the sensor's lifetime. Please
235+
/// check the
236+
/// [datasheet](https://sensirion.com/media/documents/33FD6951/624C4357/Datasheet_SHT4x.pdf),
237+
/// section 4.9 _Heater Operation_ for details.
238+
pub async fn heat_and_measure(
239+
&mut self,
240+
power: HeatingPower,
241+
duration: HeatingDuration,
242+
delay: &mut D,
243+
) -> Result<Measurement, Error<E>> {
244+
let raw = self.heat_and_measure_raw(power, duration, delay).await?;
245+
246+
Ok(Measurement::from(raw))
247+
}
248+
249+
/// Activates the heater and performs a measurement returning raw sensor data.
250+
///
251+
/// **Note:** The heater is designed to be used up to 10 % of the sensor's lifetime. Please
252+
/// check the
253+
/// [datasheet](https://sensirion.com/media/documents/33FD6951/624C4357/Datasheet_SHT4x.pdf),
254+
/// section 4.9 _Heater Operation_ for details.
255+
pub async fn heat_and_measure_raw(
256+
&mut self,
257+
power: HeatingPower,
258+
duration: HeatingDuration,
259+
delay: &mut D,
260+
) -> Result<SensorData, Error<E>> {
261+
let command = Command::from((power, duration));
262+
263+
self.write_command_and_delay_for_execution(command, delay)
264+
.await?;
265+
let response = self.read_response().await?;
266+
let raw = self.sensor_data_from_response(&response);
267+
268+
Ok(raw)
269+
}
270+
271+
/// Performs a measurement returning measurands in SI units.
272+
pub async fn measure(
273+
&mut self,
274+
precision: Precision,
275+
delay: &mut D,
276+
) -> Result<Measurement, Error<E>> {
277+
let raw = self.measure_raw(precision, delay).await?;
278+
Ok(Measurement::from(raw))
279+
}
280+
281+
/// Performs a measurement returning raw sensor data.
282+
pub async fn measure_raw(
283+
&mut self,
284+
precision: Precision,
285+
delay: &mut D,
286+
) -> Result<SensorData, Error<E>> {
287+
let command = Command::from(precision);
288+
289+
self.write_command_and_delay_for_execution(command, delay)
290+
.await?;
291+
let response = self.read_response().await?;
292+
let raw = self.sensor_data_from_response(&response);
293+
294+
Ok(raw)
295+
}
296+
297+
/// Reads the sensor's serial number.
298+
pub async fn serial_number(&mut self, delay: &mut D) -> Result<u32, Error<E>> {
299+
self.write_command_and_delay_for_execution(Command::SerialNumber, delay)
300+
.await?;
301+
let response = self.read_response().await?;
302+
303+
Ok(u32::from_be_bytes([
304+
response[0],
305+
response[1],
306+
response[3],
307+
response[4],
308+
]))
309+
}
310+
311+
/// Performs a soft reset of the sensor.
312+
pub async fn soft_reset(&mut self, delay: &mut D) -> Result<(), Error<E>> {
313+
self.write_command_and_delay_for_execution(Command::SoftReset, delay)
314+
.await
315+
}
316+
317+
async fn read_response(&mut self) -> Result<[u8; RESPONSE_LEN], Error<E>> {
318+
let mut response = [0; RESPONSE_LEN];
319+
320+
self.i2c
321+
.read(self.address.into(), &mut response)
322+
.await
323+
.map_err(Error::I2c)?;
324+
sensirion_i2c::crc8::validate(&response)?;
325+
326+
Ok(response)
327+
}
328+
329+
fn sensor_data_from_response(&self, response: &[u8; RESPONSE_LEN]) -> SensorData {
330+
SensorData {
331+
temperature: u16::from_be_bytes([response[0], response[1]]),
332+
humidity: u16::from_be_bytes([response[3], response[4]]),
333+
}
334+
}
335+
336+
async fn write_command_and_delay_for_execution(
337+
&mut self,
338+
command: Command,
339+
delay: &mut D,
340+
) -> Result<(), Error<E>> {
341+
let code = command.code();
342+
343+
self.i2c
344+
.write(self.address.into(), &code.to_be_bytes())
345+
.await
346+
.map_err(Error::I2c)?;
347+
delay.delay_ms(command.duration_ms() as u32).await;
348+
349+
Ok(())
350+
}
351+
}

0 commit comments

Comments
 (0)