embedded_aht20/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(unsafe_code, missing_docs)]
3#![no_std]
4
5use bitflags::bitflags;
6use crc::{Crc, CRC_8_NRSC_5};
7
8#[cfg(not(feature = "async"))]
9use embedded_hal as hal;
10#[cfg(feature = "async")]
11use embedded_hal_async as hal;
12
13use hal::delay::DelayNs;
14use hal::i2c::{I2c, SevenBitAddress};
15
16use weather_utils::{unit::Celsius, TemperatureAndRelativeHumidity};
17
18/// The default I2C address.
19pub const DEFAULT_I2C_ADDRESS: SevenBitAddress = 0x38;
20
21const CHECK_STATUS_COMMAND: &[u8] = &[0b0111_0001];
22const INITIALIZATION_COMMAND: &[u8] = &[0b1011_1110, 0x08, 0x00];
23const TRIGGER_MEASUREMENT_COMMAND: &[u8] = &[0b1010_1100, 0x33, 0x00];
24const SOFT_RESET_COMMAND: &[u8] = &[0b1011_1010];
25
26/// All possible errors generated when using the Aht20 struct.
27#[derive(Debug)]
28pub enum Error<I2cError>
29where
30    I2cError: hal::i2c::Error,
31{
32    /// I²C bus error.
33    I2c(I2cError),
34    /// The computed CRC and the one sent by the device mismatch.
35    InvalidCrc,
36    /// The device is busy at a time where it was not expected to.
37    UnexpectedBusy,
38}
39
40impl<I2cError> From<I2cError> for Error<I2cError>
41where
42    I2cError: hal::i2c::Error,
43{
44    fn from(value: I2cError) -> Self {
45        Error::I2c(value)
46    }
47}
48
49#[derive(Debug)]
50struct SensorMeasurement {
51    raw_humidity: u32,
52    raw_temperature: u32,
53}
54
55impl From<&[u8]> for SensorMeasurement {
56    fn from(data: &[u8]) -> Self {
57        let raw_humidity: u32 =
58            ((data[0] as u32) << 12) | ((data[1] as u32) << 4) | ((data[2] >> 4) as u32);
59        let raw_temperature: u32 =
60            (((data[2] & 0b0000_1111) as u32) << 16) | ((data[3] as u32) << 8) | (data[4] as u32);
61        SensorMeasurement {
62            raw_humidity,
63            raw_temperature,
64        }
65    }
66}
67
68impl SensorMeasurement {
69    /// The measured relative humidity (in %).
70    pub fn humidity(&self) -> f32 {
71        ((self.raw_humidity as f32) / ((1 << 20) as f32)) * 100.0
72    }
73
74    /// The measured temperature (in °C).
75    pub fn temperature(&self) -> f32 {
76        ((self.raw_temperature as f32) / ((1 << 20) as f32)) * 200.0 - 50.0
77    }
78}
79
80impl From<SensorMeasurement> for TemperatureAndRelativeHumidity<Celsius> {
81    fn from(value: SensorMeasurement) -> Self {
82        TemperatureAndRelativeHumidity::<Celsius>::new(value.temperature(), value.humidity())
83    }
84}
85
86bitflags! {
87    struct SensorStatus: u8 {
88        const BUSY = 0b1000_0000;
89        const CALIBRATED = 0b0000_1000;
90    }
91}
92
93impl SensorStatus {
94    fn is_calibrated(&self) -> bool {
95        self.contains(SensorStatus::CALIBRATED)
96    }
97
98    fn is_ready(&self) -> bool {
99        !self.contains(SensorStatus::BUSY)
100    }
101}
102
103/// AHT20 device driver.
104#[derive(Debug)]
105pub struct Aht20<I2C, D> {
106    i2c: I2C,
107    address: SevenBitAddress,
108    delay: D,
109}
110
111impl<I2C, D> Aht20<I2C, D>
112where
113    I2C: I2c,
114    D: DelayNs,
115{
116    /// Create a new instance of the AHT20 device.
117    #[maybe_async_cfg::maybe(
118        sync(not(feature = "async"), keep_self),
119        async(feature = "async", keep_self)
120    )]
121    pub async fn new(
122        i2c: I2C,
123        address: SevenBitAddress,
124        delay: D,
125    ) -> Result<Self, Error<I2C::Error>> {
126        let mut dev = Self {
127            i2c,
128            address,
129            delay,
130        };
131
132        while !dev.check_status().await?.is_calibrated() {
133            dev.send_initialize().await?;
134            dev.delay_ms(10).await;
135        }
136
137        Ok(dev)
138    }
139
140    /// Perform a measurement.
141    ///
142    /// The measurement takes at least 80 ms to be performed.
143    #[maybe_async_cfg::maybe(
144        sync(not(feature = "async"), keep_self),
145        async(feature = "async", keep_self)
146    )]
147    pub async fn measure(
148        &mut self,
149    ) -> Result<TemperatureAndRelativeHumidity<Celsius>, Error<I2C::Error>> {
150        self.send_trigger_measurement().await?;
151
152        // Wait for measurement to be ready
153        self.delay_ms(80).await;
154        while !self.check_status().await?.is_ready() {
155            self.delay_ms(1).await;
156        }
157
158        let mut buffer = [0u8; 7];
159        self.i2c
160            .read(self.address, &mut buffer)
161            .await
162            .map_err(Error::I2c)?;
163
164        let data = &buffer[..6];
165        let crc = buffer[6];
166        self.check_crc(data, crc)?;
167
168        let status = SensorStatus::from_bits_retain(buffer[0]);
169        if !status.is_ready() {
170            return Err(Error::UnexpectedBusy);
171        }
172
173        let measurement = SensorMeasurement::from(&data[1..6]);
174        Ok(measurement.into())
175    }
176
177    /// Perform a soft reset to force the device into a well-defined state
178    /// without removing the power supply.
179    #[maybe_async_cfg::maybe(
180        sync(not(feature = "async"), keep_self),
181        async(feature = "async", keep_self)
182    )]
183    pub async fn soft_reset(&mut self) -> Result<(), Error<I2C::Error>> {
184        self.i2c
185            .write(self.address, SOFT_RESET_COMMAND)
186            .await
187            .map_err(Error::I2c)?;
188        self.delay_ms(20).await;
189        Ok(())
190    }
191
192    fn check_crc(&self, data: &[u8], crc_value: u8) -> Result<(), Error<I2C::Error>> {
193        let crc = Crc::<u8>::new(&CRC_8_NRSC_5);
194        let mut digest = crc.digest();
195        digest.update(data);
196        if digest.finalize() != crc_value {
197            return Err(Error::InvalidCrc);
198        }
199        Ok(())
200    }
201
202    #[maybe_async_cfg::maybe(
203        sync(not(feature = "async"), keep_self),
204        async(feature = "async", keep_self)
205    )]
206    async fn check_status(&mut self) -> Result<SensorStatus, Error<I2C::Error>> {
207        let mut buffer = [0];
208        self.i2c
209            .write_read(self.address, CHECK_STATUS_COMMAND, &mut buffer)
210            .await
211            .map_err(Error::I2c)?;
212        Ok(SensorStatus::from_bits_retain(buffer[0]))
213    }
214
215    #[maybe_async_cfg::maybe(
216        sync(not(feature = "async"), keep_self),
217        async(feature = "async", keep_self)
218    )]
219    async fn delay_ms(&mut self, duration: u32) {
220        self.delay.delay_ms(duration).await;
221    }
222
223    #[maybe_async_cfg::maybe(
224        sync(not(feature = "async"), keep_self),
225        async(feature = "async", keep_self)
226    )]
227    async fn send_initialize(&mut self) -> Result<(), Error<I2C::Error>> {
228        self.i2c
229            .write(self.address, INITIALIZATION_COMMAND)
230            .await
231            .map_err(Error::I2c)?;
232        Ok(())
233    }
234
235    #[maybe_async_cfg::maybe(
236        sync(not(feature = "async"), keep_self),
237        async(feature = "async", keep_self)
238    )]
239    async fn send_trigger_measurement(&mut self) -> Result<(), Error<I2C::Error>> {
240        self.i2c
241            .write(self.address, TRIGGER_MEASUREMENT_COMMAND)
242            .await
243            .map_err(Error::I2c)?;
244        Ok(())
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use crate::*;
251    use approx::assert_relative_eq;
252    use embedded_hal::i2c::ErrorKind;
253    use embedded_hal_mock::eh1::delay::StdSleep as Delay;
254    use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
255
256    #[test]
257    fn test_i2c_error() {
258        let error: Error<hal::i2c::ErrorKind> = hal::i2c::ErrorKind::Other.into();
259        assert!(matches!(error, Error::I2c(_)));
260    }
261
262    #[test]
263    fn test_sensor_measurement() {
264        let measurement: SensorMeasurement = [0x7b, 0xb3, 0x05, 0x9d, 0x49].as_slice().into();
265        assert_eq!(measurement.raw_humidity, 0x0007bb30);
266        assert_eq!(measurement.raw_temperature, 0x00059d49);
267        assert_relative_eq!(measurement.humidity(), 48.32, epsilon = 0.01);
268        assert_relative_eq!(measurement.temperature(), 20.18, epsilon = 0.01);
269    }
270
271    #[test]
272    fn test_aht20_creation_with_busy() {
273        let expectations = [
274            I2cTransaction::write_read(
275                DEFAULT_I2C_ADDRESS,
276                CHECK_STATUS_COMMAND.to_vec(),
277                [SensorStatus::BUSY.bits()].to_vec(),
278            ),
279            I2cTransaction::write(DEFAULT_I2C_ADDRESS, INITIALIZATION_COMMAND.to_vec()),
280            I2cTransaction::write_read(
281                DEFAULT_I2C_ADDRESS,
282                CHECK_STATUS_COMMAND.to_vec(),
283                [SensorStatus::CALIBRATED.bits()].to_vec(),
284            ),
285        ];
286        let mut i2c = I2cMock::new(&expectations);
287        let _device = Aht20::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {}).unwrap();
288        i2c.done();
289    }
290
291    #[test]
292    fn test_aht20_creation_with_check_status_error() {
293        let expectations = [I2cTransaction::write_read(
294            DEFAULT_I2C_ADDRESS,
295            CHECK_STATUS_COMMAND.to_vec(),
296            [SensorStatus::BUSY.bits()].to_vec(),
297        )
298        .with_error(ErrorKind::Bus)];
299        let mut i2c = I2cMock::new(&expectations);
300        let err = Aht20::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {});
301        assert!(matches!(err, Err(Error::I2c(ErrorKind::Bus))));
302        i2c.done();
303    }
304
305    #[test]
306    fn test_aht20_creation_with_initialization_error() {
307        let expectations = [
308            I2cTransaction::write_read(
309                DEFAULT_I2C_ADDRESS,
310                CHECK_STATUS_COMMAND.to_vec(),
311                [SensorStatus::BUSY.bits()].to_vec(),
312            ),
313            I2cTransaction::write(DEFAULT_I2C_ADDRESS, INITIALIZATION_COMMAND.to_vec())
314                .with_error(ErrorKind::Other),
315        ];
316        let mut i2c = I2cMock::new(&expectations);
317        let err = Aht20::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {});
318        assert!(matches!(err, Err(Error::I2c(ErrorKind::Other))));
319        i2c.done();
320    }
321
322    #[test]
323    fn test_measure() {
324        let expectations = [
325            I2cTransaction::write_read(
326                DEFAULT_I2C_ADDRESS,
327                CHECK_STATUS_COMMAND.to_vec(),
328                [SensorStatus::CALIBRATED.bits()].to_vec(),
329            ),
330            I2cTransaction::write(DEFAULT_I2C_ADDRESS, TRIGGER_MEASUREMENT_COMMAND.to_vec()),
331            I2cTransaction::write_read(
332                DEFAULT_I2C_ADDRESS,
333                CHECK_STATUS_COMMAND.to_vec(),
334                [SensorStatus::CALIBRATED.bits()].to_vec(),
335            ),
336            I2cTransaction::read(
337                DEFAULT_I2C_ADDRESS,
338                [
339                    SensorStatus::CALIBRATED.bits(),
340                    0x7b,
341                    0xb3,
342                    0x05,
343                    0x9d,
344                    0x49,
345                    0x7d,
346                ]
347                .to_vec(),
348            ),
349        ];
350        let mut i2c = I2cMock::new(&expectations);
351        let mut device = Aht20::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {}).unwrap();
352        let measurement = device.measure().unwrap();
353        assert_relative_eq!(measurement.temperature.celsius(), 20.18, epsilon = 0.01);
354        assert_relative_eq!(measurement.relative_humidity, 48.32, epsilon = 0.01);
355        i2c.done();
356    }
357
358    #[test]
359    fn test_measure_with_trigger_measurement_error() {
360        let expectations = [
361            I2cTransaction::write_read(
362                DEFAULT_I2C_ADDRESS,
363                CHECK_STATUS_COMMAND.to_vec(),
364                [SensorStatus::CALIBRATED.bits()].to_vec(),
365            ),
366            I2cTransaction::write(DEFAULT_I2C_ADDRESS, TRIGGER_MEASUREMENT_COMMAND.to_vec())
367                .with_error(ErrorKind::ArbitrationLoss),
368        ];
369        let mut i2c = I2cMock::new(&expectations);
370        let mut device = Aht20::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {}).unwrap();
371        let err = device.measure().expect_err("Arbitration loss");
372        assert!(matches!(err, Error::I2c(ErrorKind::ArbitrationLoss)));
373        i2c.done();
374    }
375
376    #[test]
377    fn test_measure_with_measure_read_error() {
378        let expectations = [
379            I2cTransaction::write_read(
380                DEFAULT_I2C_ADDRESS,
381                CHECK_STATUS_COMMAND.to_vec(),
382                [SensorStatus::CALIBRATED.bits()].to_vec(),
383            ),
384            I2cTransaction::write(DEFAULT_I2C_ADDRESS, TRIGGER_MEASUREMENT_COMMAND.to_vec()),
385            I2cTransaction::write_read(
386                DEFAULT_I2C_ADDRESS,
387                CHECK_STATUS_COMMAND.to_vec(),
388                [SensorStatus::CALIBRATED.bits()].to_vec(),
389            ),
390            I2cTransaction::read(
391                DEFAULT_I2C_ADDRESS,
392                [
393                    SensorStatus::CALIBRATED.bits(),
394                    0x7b,
395                    0xb3,
396                    0x05,
397                    0x9d,
398                    0x49,
399                    0x7d,
400                ]
401                .to_vec(),
402            )
403            .with_error(ErrorKind::ArbitrationLoss),
404        ];
405        let mut i2c = I2cMock::new(&expectations);
406        let mut device = Aht20::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {}).unwrap();
407        let err = device.measure().expect_err("Arbitration loss");
408        assert!(matches!(err, Error::I2c(ErrorKind::ArbitrationLoss)));
409        i2c.done();
410    }
411
412    #[test]
413    fn test_measure_with_busy_and_unexpected_busy_error() {
414        let expectations = [
415            I2cTransaction::write_read(
416                DEFAULT_I2C_ADDRESS,
417                CHECK_STATUS_COMMAND.to_vec(),
418                [SensorStatus::CALIBRATED.bits()].to_vec(),
419            ),
420            I2cTransaction::write(DEFAULT_I2C_ADDRESS, TRIGGER_MEASUREMENT_COMMAND.to_vec()),
421            I2cTransaction::write_read(
422                DEFAULT_I2C_ADDRESS,
423                CHECK_STATUS_COMMAND.to_vec(),
424                [(SensorStatus::CALIBRATED | SensorStatus::BUSY).bits()].to_vec(),
425            ),
426            I2cTransaction::write_read(
427                DEFAULT_I2C_ADDRESS,
428                CHECK_STATUS_COMMAND.to_vec(),
429                [SensorStatus::CALIBRATED.bits()].to_vec(),
430            ),
431            I2cTransaction::read(
432                DEFAULT_I2C_ADDRESS,
433                [
434                    (SensorStatus::CALIBRATED | SensorStatus::BUSY).bits(),
435                    0x7b,
436                    0xb3,
437                    0x05,
438                    0x9d,
439                    0x49,
440                    0x91,
441                ]
442                .to_vec(),
443            ),
444        ];
445        let mut i2c = I2cMock::new(&expectations);
446        let mut device = Aht20::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {}).unwrap();
447        let err = device.measure().expect_err("Unexpected Busy");
448        assert!(matches!(err, Error::UnexpectedBusy));
449        i2c.done();
450    }
451
452    #[test]
453    fn test_soft_reset() {
454        let expectations = [
455            I2cTransaction::write_read(
456                DEFAULT_I2C_ADDRESS,
457                CHECK_STATUS_COMMAND.to_vec(),
458                [SensorStatus::CALIBRATED.bits()].to_vec(),
459            ),
460            I2cTransaction::write(DEFAULT_I2C_ADDRESS, SOFT_RESET_COMMAND.to_vec()),
461        ];
462        let mut i2c = I2cMock::new(&expectations);
463        let mut device = Aht20::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {}).unwrap();
464        device.soft_reset().unwrap();
465        i2c.done();
466    }
467
468    #[test]
469    fn test_soft_reset_with_error() {
470        let expectations = [
471            I2cTransaction::write_read(
472                DEFAULT_I2C_ADDRESS,
473                CHECK_STATUS_COMMAND.to_vec(),
474                [SensorStatus::CALIBRATED.bits()].to_vec(),
475            ),
476            I2cTransaction::write(DEFAULT_I2C_ADDRESS, SOFT_RESET_COMMAND.to_vec())
477                .with_error(ErrorKind::Overrun),
478        ];
479        let mut i2c = I2cMock::new(&expectations);
480        let mut device = Aht20::new(&mut i2c, DEFAULT_I2C_ADDRESS, Delay {}).unwrap();
481        let err = device.soft_reset().expect_err("Overrun");
482        assert!(matches!(err, Error::I2c(ErrorKind::Overrun)));
483        i2c.done();
484    }
485}