#![doc = include_str!("../README.md")]
#![no_std]
#![deny(unsafe_code)]
#![deny(clippy::panic)]
#![deny(clippy::unwrap_used)]
use embedded_hal::{delay::DelayNs, i2c::I2c};
#[cfg(feature = "async")]
use embedded_hal_async::{delay::DelayNs as DelayNsAsync, i2c::I2c as I2cAsync};
fn crc8(data: &[u8]) -> u8 {
let polynomial = 0x31u8; let mut crc = 0xFFu8;
for &byte in data {
crc ^= byte;
for _ in 0..8 {
if crc & 0x80 != 0 {
crc = (crc << 1) ^ polynomial;
} else {
crc <<= 1;
}
}
}
crc
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub enum SensorError {
Io,
Timeout,
Checksum,
}
impl<E: embedded_hal::i2c::Error> From<E> for SensorError {
fn from(_value: E) -> Self {
SensorError::Io
}
}
#[maybe_async_cfg::maybe(
idents(
Aht10Async(sync = "Aht10", async),
I2cAsync(sync = "I2c", async),
DelayNsAsync(sync = "DelayNs", async)
),
sync(all()),
async(feature = "async")
)]
pub struct Aht10Async<I: I2cAsync, D: DelayNsAsync> {
addr: u8,
i2c: I,
delay: D,
}
#[maybe_async_cfg::maybe(
idents(
Aht10Async(sync = "Aht10", async),
I2cAsync(sync = "I2c", async),
DelayNsAsync(sync = "DelayNs", async)
),
sync(all()),
async(feature = "async")
)]
impl<I: I2cAsync, D: DelayNsAsync> Aht10Async<I, D> {
pub fn new(addr: u8, i2c: I, delay: D) -> Self {
Self { addr, i2c, delay }
}
pub async fn calibrate(&mut self) -> Result<(), SensorError> {
let mut status: [u8; 1] = [0; 1];
'done: {
for _ in 0..5 {
self.i2c.read(self.addr, &mut status).await?; if status[0] & 0b1000 == 0b1000 {
break 'done;
}
self.i2c.write(self.addr, &[0xE1, 0x08, 0x00]).await?;
self.delay.delay_ms(10).await;
}
return Err(SensorError::Timeout);
}
Ok(())
}
pub async fn soft_reset(&mut self) -> Result<(), SensorError> {
self.i2c.write(self.addr, &[0xBA]).await?;
Ok(())
}
pub async fn read(&mut self) -> Result<Aht10Measurement, SensorError> {
self.i2c.write(self.addr, &[0xAC, 0x33, 0x00]).await?;
self.delay.delay_ms(80).await;
let mut response: [u8; 6] = [0; 6]; 'done: {
for _ in 0..100 {
self.i2c.read(self.addr, &mut response[..1]).await?;
if response[0] & 0b1000_0000 == 0 {
break 'done;
}
self.delay.delay_ms(5).await;
}
return Err(SensorError::Timeout);
}
self.i2c.read(self.addr, &mut response).await?;
let humidity_raw =
(response[1] as u32) << 12 | (response[2] as u32) << 4 | (response[3] as u32) >> 4;
let temperature_raw =
((response[3] & 0x0f) as u32) << 16 | (response[4] as u32) << 8 | response[5] as u32;
Ok(Aht10Measurement {
humidity_raw,
temperature_raw,
})
}
}
pub struct Aht10Measurement {
pub humidity_raw: u32, pub temperature_raw: u32,
}
impl Aht10Measurement {
pub fn decode(&self) -> (f32, f32) {
let humidity = self.humidity_raw as f32 / ((1 << 20) as f32) * 100f32;
let temperature = self.temperature_raw as f32 / ((1 << 20) as f32) * 200f32 - 50f32;
(humidity, temperature)
}
}
pub const AHT10_DEFAULT_ADDR: u8 = 0x38;
#[maybe_async_cfg::maybe(
idents(
Aht20Async(sync = "Aht20", async),
I2cAsync(sync = "I2c", async),
DelayNsAsync(sync = "DelayNs", async)
),
sync(all()),
async(feature = "async")
)]
pub struct Aht20Async<I: I2cAsync, D: DelayNsAsync> {
addr: u8,
i2c: I,
delay: D,
}
#[maybe_async_cfg::maybe(
idents(
Aht20Async(sync = "Aht20", async),
I2cAsync(sync = "I2c", async),
DelayNsAsync(sync = "DelayNs", async)
),
sync(all()),
async(feature = "async")
)]
impl<I: I2cAsync, D: DelayNsAsync> Aht20Async<I, D> {
pub fn new(addr: u8, i2c: I, delay: D) -> Self {
Self { addr, i2c, delay }
}
pub async fn calibrate(&mut self) -> Result<(), SensorError> {
let mut status: [u8; 1] = [0; 1];
'done: {
for _ in 0..5 {
self.i2c.read(self.addr, &mut status).await?; if status[0] & 0x18 == 0x18 {
break 'done;
}
self.i2c.write(self.addr, &[0x1B, 0, 0]).await?;
self.i2c.write(self.addr, &[0x1C, 0, 0]).await?;
self.i2c.write(self.addr, &[0x1E, 0, 0]).await?;
self.delay.delay_ms(10).await;
}
return Err(SensorError::Timeout);
}
Ok(())
}
pub async fn soft_reset(&mut self) -> Result<(), SensorError> {
self.i2c.write(self.addr, &[0xBA]).await?;
Ok(())
}
pub async fn read(&mut self, checksum: bool) -> Result<Aht20Measurement, SensorError> {
self.i2c.write(self.addr, &[0xAC, 0x33, 0x00]).await?; self.delay.delay_ms(80).await;
let mut response: [u8; 7] = [0; 7]; 'done: {
for _ in 0..100 {
self.i2c.read(self.addr, &mut response[..1]).await?; if response[0] & 0b1000_0000 == 0 {
break 'done;
}
self.delay.delay_ms(5).await; }
return Err(SensorError::Timeout);
}
self.i2c.read(self.addr, &mut response).await?; if checksum {
if response[6] != crc8(&response[..6]) {
return Err(SensorError::Checksum);
}
}
let humidity_raw =
(response[1] as u32) << 12 | (response[2] as u32) << 4 | (response[3] as u32) >> 4;
let temperature_raw =
((response[3] & 0x0f) as u32) << 16 | (response[4] as u32) << 8 | response[5] as u32;
Ok(Aht20Measurement {
humidity_raw,
temperature_raw,
})
}
}
pub use Aht10Measurement as Aht20Measurement; pub const AHT20_DEFAULT_ADDR: u8 = 0x38;
#[maybe_async_cfg::maybe(
idents(
Aht40Async(sync = "Aht40", async),
I2cAsync(sync = "I2c", async),
DelayNsAsync(sync = "DelayNs", async)
),
sync(all()),
async(feature = "async")
)]
pub struct Aht40Async<I: I2cAsync, D: DelayNsAsync> {
addr: u8,
i2c: I,
delay: D,
}
#[maybe_async_cfg::maybe(
idents(
Aht40Async(sync = "Aht40", async),
I2cAsync(sync = "I2c", async),
DelayNsAsync(sync = "DelayNs", async)
),
sync(all()),
async(feature = "async")
)]
impl<I: I2cAsync, D: DelayNsAsync> Aht40Async<I, D> {
pub fn new(addr: u8, i2c: I, delay: D) -> Self {
Self { addr, i2c, delay }
}
pub async fn read(&mut self, checksum: bool) -> Result<Aht40Measurement, SensorError> {
self.i2c.write(self.addr, &[0xFD]).await?; self.delay.delay_ms(80).await;
let mut response: [u8; 6] = [0; 6]; self.i2c.read(self.addr, &mut response).await?; if checksum {
if response[2] != crc8(&response[0..2]) || response[5] != crc8(&response[3..5]) {
return Err(SensorError::Checksum);
}
}
let humidity_raw = (response[3] as u16) << 8 | (response[4] as u16);
let temperature_raw = (response[0] as u16) << 8 | (response[1] as u16);
Ok(Aht40Measurement {
humidity_raw,
temperature_raw,
})
}
}
pub struct Aht40Measurement {
pub humidity_raw: u16, pub temperature_raw: u16,
}
impl Aht40Measurement {
pub fn decode(&self) -> (f32, f32) {
let humidity = self.humidity_raw as f32 / 65535f32 * 125f32 - 6f32;
let temperature = self.temperature_raw as f32 / 65535f32 * 175f32 - 45f32;
(humidity, temperature)
}
}
pub const AHT40_DEFAULT_ADDR: u8 = 0x44;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = crc8(&[0x76, 0x54, 0x32, 0x10]); assert_eq!(result, 0x21);
}
}