embedded_hal_aht2x/
lib.rs

1#![no_std]
2#![feature(int_roundings)]
3use core::fmt::{Debug, Display};
4use core::marker::PhantomData;
5#[cfg(feature = "crc")]
6use crc::{Crc, CRC_8_NRSC_5};
7use embedded_hal_async::delay::DelayNs;
8use embedded_hal_async::i2c::I2c;
9
10pub const I2C_ADDRESS: u8 = 0x38;
11
12pub const I2C_TRIGGER_MEASURE: u8 = 0xAC;
13
14pub const MEASURE_TIME_MS: u32 = 80;
15
16#[derive(Debug, Copy, Clone, Eq, PartialEq)]
17#[cfg_attr(feature = "defmt", derive(defmt::Format))]
18pub struct HumidityData(u32);
19
20impl HumidityData {
21    pub fn from_bytes(bytes: [u8; 3]) -> Self {
22        // | x2 | x1 | half byte x0 |
23        let mut me = Self(0);
24        me.0 = bytes[0] as u32;
25        me.0 <<= 8;
26        me.0 |= bytes[1] as u32;
27        me.0 <<= 8;
28        me.0 |= (bytes[2] & 0b11110000u8) as u32;
29        me.0 >>= 4;
30        me
31    }
32    pub fn relative_mille(&self) -> u32 {
33        ((self.0 as u64 * 1000) / (2 << 20)) as _
34    }
35    pub fn relative(&self) -> (u16, u16) {
36        let mille = self.relative_mille();
37        let div = 10;
38        let percent = mille.div_floor(div);
39        let frac = mille % div;
40        (percent as _, frac as _)
41    }
42}
43
44impl Display for HumidityData {
45    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
46        let (percent, frac) = self.relative();
47        write!(f, "{}.{}%", percent, frac)
48    }
49}
50
51#[derive(Debug, Copy, Clone, Eq, PartialEq)]
52#[cfg_attr(feature = "defmt", derive(defmt::Format))]
53pub struct TemperatureData(u32);
54impl TemperatureData {
55    pub fn from_bytes(bytes: [u8; 3]) -> Self {
56        // | half byte x2  | x1 | x0 |
57        let mut me = Self(0);
58        me.0 = (bytes[0] & 0b00001111u8) as u32;
59        me.0 <<= 8;
60        me.0 |= bytes[1] as u32;
61        me.0 <<= 8;
62        me.0 |= bytes[2] as u32;
63        me
64    }
65    pub fn milli_celsius(&self) -> i32 {
66        ((self.0 as i64 * 200 * 1000) / ((1u64 << 20) as i64) - 50 * 1000) as _
67    }
68    pub fn celsius(&self) -> (i16, u16) {
69        let milli = self.milli_celsius();
70        let int = milli.div_floor(1000);
71        let frac = milli % 1000;
72        (int as _, frac as _)
73    }
74}
75
76impl Display for TemperatureData {
77    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
78        let (int, frac) = self.celsius();
79        write!(f, "{}.{}°C", int, frac)
80    }
81}
82
83#[repr(u8)]
84enum StatusBits {
85    Busy = 0x01,
86    Calibrated = 0x10,
87    CalibrationOn = 0x08,
88    FifoOn = 0x04,
89    FifoFull = 0x02,
90}
91
92#[derive(Copy, Clone, Eq, PartialEq)]
93#[cfg_attr(feature = "defmt", derive(defmt::Format))]
94pub struct Status(u8);
95
96impl Status {
97    pub fn is_busy(&self) -> bool {
98        self.0 & (StatusBits::Busy as u8) > 0
99    }
100    pub fn is_calibrated(&self) -> bool {
101        self.0 & (StatusBits::Calibrated as u8) > 0
102    }
103    pub fn is_calibration_on(&self) -> bool {
104        self.0 & (StatusBits::CalibrationOn as u8) > 0
105    }
106    pub fn is_fifo_on(&self) -> bool {
107        self.0 & (StatusBits::FifoOn as u8) > 0
108    }
109    pub fn is_fido_full(&self) -> bool {
110        self.0 & (StatusBits::FifoFull as u8) > 0
111    }
112}
113
114impl Debug for Status {
115    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
116        f.debug_struct("Status")
117            .field("busy", &self.is_busy())
118            .field("calibrated", &self.is_calibrated())
119            .field("calibration on", &self.is_calibration_on())
120            .field("fifo on", &self.is_fifo_on())
121            .field("fifo full", &self.is_fido_full())
122            .finish()
123    }
124}
125
126#[derive(Debug, Copy, Clone)]
127#[repr(u8)]
128pub enum Aht2xCommand {
129    Init = 0xBE,
130    Status = 0x71,
131    Measure = 0xAC,
132    Reset = 0xBA,
133}
134
135pub struct Aht2X<T> {
136    _marker: PhantomData<T>,
137}
138
139impl<T: I2c> Aht2X<T> {
140    async fn write(
141        &mut self,
142        interface: &mut T,
143        cmd: Aht2xCommand,
144        bytes: &[u8],
145    ) -> Result<(), T::Error> {
146        let mut payload = [0u8; 12];
147        payload[0] = cmd as _;
148        payload[1..(bytes.len() + 1)].copy_from_slice(bytes);
149        #[cfg(feature = "defmt")]
150        defmt::debug!("cmd: {:?} {:?}", cmd, bytes);
151        let res = interface
152            .write(I2C_ADDRESS, &payload[..(bytes.len() + 1)])
153            .await;
154        res
155    }
156
157    async fn read(&mut self, interface: &mut T, bytes: &mut [u8]) -> Result<(), T::Error> {
158        let res = interface.read(I2C_ADDRESS, bytes).await;
159        #[cfg(feature = "defmt")]
160        defmt::debug!("read {:?}", bytes);
161        res
162    }
163
164    pub async fn setup<'a>(interface: &mut T, delay: &mut impl DelayNs) -> Result<Self, T::Error> {
165        let mut me = Self {
166            _marker: Default::default(),
167        };
168        delay.delay_ms(100).await;
169        me.write(interface, Aht2xCommand::Reset, &[]).await?;
170        delay.delay_ms(100).await;
171        me.write(interface, Aht2xCommand::Init, &[0b10101100, 0x00])
172            .await?;
173        while me.status(interface).await?.is_busy() {
174            delay.delay_ms(100).await;
175        }
176        let status = me.status(interface).await?;
177        #[cfg(feature = "defmt")]
178        defmt::debug!("setup status: {:?}", status);
179        if !status.is_calibrated() {
180            #[cfg(feature = "defmt")]
181            defmt::warn!("calibration failed!");
182        }
183        Ok(me)
184    }
185
186    pub async fn status<'a>(&mut self, interface: &mut T) -> Result<Status, T::Error> {
187        self.write(interface, Aht2xCommand::Status, &[]).await?;
188        let mut status = Status(0);
189        self.read(interface, core::slice::from_mut(&mut status.0))
190            .await?;
191        Ok(status)
192    }
193    pub async fn measure<'a>(
194        &mut self,
195        interface: &mut T,
196        delay: &mut impl DelayNs,
197    ) -> Result<(HumidityData, TemperatureData), T::Error> {
198        delay.delay_ms(100).await;
199        let mut resp = [0u8; if cfg!(feature = "crc") { 7 } else { 6 }];
200        self.write(interface, Aht2xCommand::Measure, &[0x33, 0x00])
201            .await?;
202        loop {
203            delay.delay_ms(MEASURE_TIME_MS).await;
204            self.read(interface, &mut resp).await?;
205            let full_resp = &resp[..];
206            let resp = full_resp;
207            let state = Status(resp[0]);
208            let resp = &resp[1..];
209            #[cfg(feature = "defmt")]
210            defmt::debug!("{:?} {}", state, state.is_busy());
211            if state.is_busy() {
212                continue;
213            }
214            let humidity = HumidityData::from_bytes(resp[..3].try_into().unwrap());
215            let resp = &resp[2..];
216            let temperature = TemperatureData::from_bytes(resp[..3].try_into().unwrap());
217            #[allow(unused)]
218            let resp = &resp[3..];
219            #[cfg(feature = "crc")]
220            let _ = {
221                let digest = calc_crc(&full_resp[..6]);
222                match digest {
223                    digest if digest == resp[0] => {}
224                    #[allow(unused)]
225                    digest => {
226                        #[cfg(feature = "defmt")]
227                        defmt::warn!("crc mismatch, expected {}, got {}", resp[0], digest);
228                        continue;
229                    }
230                }
231                &resp[1..]
232            };
233            #[cfg(feature = "defmt")]
234            defmt::debug!("temperature: {:?}, humidity: {:?}", temperature, humidity);
235            break Ok((humidity, temperature));
236        }
237    }
238}
239
240#[cfg(feature = "crc")]
241fn calc_crc(resp: &[u8]) -> u8 {
242    let crc = Crc::<u8>::new(&CRC_8_NRSC_5);
243    let mut digest = crc.digest_with_initial(0xFF);
244    digest.update(resp);
245    digest.finalize()
246}
247
248#[cfg(test)]
249extern crate std;
250
251#[cfg(test)]
252mod tests {
253    use super::*;
254
255    #[test]
256    fn status() {
257        assert!(Status(158).is_busy())
258    }
259
260    #[test]
261    fn parse_measurement() {
262        let resp: [u8; 5] = [111, 65, 133, 170, 130];
263        let hum = HumidityData::from_bytes(resp[..3].try_into().unwrap());
264        let temp = TemperatureData::from_bytes(resp[3..].try_into().unwrap());
265    }
266}