embedded-dht-rs 0.5.0

A driver for interfacing with DHT11, DHT20 (AHT20), DHT22 (AM2302) temperature and humidity sensors, designed for embedded systems.
Documentation
use embedded_hal::{
    delay::DelayNs,
    digital::{InputPin, OutputPin, PinState},
};

use crate::SensorError;

#[cfg(test)]
const DEFAULT_MAX_ATTEMPTS: usize = 10;
#[cfg(not(test))]
const DEFAULT_MAX_ATTEMPTS: usize = 10_000;

/// Common base struct for DHT11, DHT22 sensors.
pub struct Dht<P: InputPin + OutputPin, D: DelayNs> {
    pub pin: P,
    pub delay: D,
}

impl<P: InputPin + OutputPin, D: DelayNs> Dht<P, D> {
    pub fn new(pin: P, delay: D) -> Self {
        Self { pin, delay }
    }

    /// Reads a byte (8 bits) from the sensor.
    ///
    /// This method reads 8 bits sequentially from the sensor to construct a byte.
    /// It follows the communication protocol of the DHT11/DHT22 sensors:
    ///
    /// For each bit:
    /// - Waits for the pin to go **high** (start of bit transmission).
    /// - Delays for **30 microseconds** to sample the bit value.
    ///   - If the pin is **high** after the delay, the bit is interpreted as **'1'**.
    ///   - If the pin is **low**, the bit is interpreted as **'0'**.
    /// - Waits for the pin to go **low** (end of bit transmission).
    ///
    /// The bits are assembled into a byte, starting from the most significant bit (MSB).
    ///
    /// # Returns
    ///
    /// - `Ok(u8)`: The byte read from the sensor.
    /// - `Err(SensorError<P::Error>)`: If a pin error occurs.
    pub fn read_byte(&mut self) -> Result<u8, SensorError> {
        let mut byte: u8 = 0;
        for n in 0..8 {
            match self.wait_until_state(PinState::High) {
                Ok(_) => {}
                Err(err) => return Err(err),
            };

            self.delay.delay_us(30);
            let is_bit_1 = self.pin.is_high();
            if is_bit_1.unwrap() {
                let bit_mask = 1 << (7 - (n % 8));
                byte |= bit_mask;
                match self.wait_until_state(PinState::Low) {
                    Ok(_) => {}
                    Err(err) => return Err(err),
                };
            }
        }
        Ok(byte)
    }

    /// Waits until the pin reaches the specified state.
    ///
    /// This helper function continuously polls the pin until it reaches the desired `PinState`.
    /// It introduces a **1-microsecond delay** between each poll to prevent excessive CPU usage.
    ///
    /// # Arguments
    ///
    /// - `state`: The target `PinState` to wait for (`PinState::High` or `PinState::Low`).
    ///
    /// # Returns
    ///
    /// - `Ok(())`: When the pin reaches the desired state.
    /// - `Err(SensorError::Timeout)`: If the desired state is not reached in time.
    /// - `Err(SensorError::Io(...))`: If a pin error occurs while reading the pin state.
    ///
    pub fn wait_until_state(&mut self, state: PinState) -> Result<(), SensorError> {
        for _ in 0..DEFAULT_MAX_ATTEMPTS {
            let is_state = match state {
                PinState::Low => self.pin.is_low(),
                PinState::High => self.pin.is_high(),
            };

            match is_state {
                Ok(true) => return Ok(()),
                Ok(false) => self.delay.delay_us(1),
                Err(_) => return Err(SensorError::PinError),
            }
        }

        Err(SensorError::Timeout)
    }
}

#[cfg(test)]
mod tests {
    extern crate std;
    use std::io::ErrorKind;

    use super::*;
    use embedded_hal_mock::eh1::delay::NoopDelay as MockNoop;
    use embedded_hal_mock::eh1::digital::{Mock, State, Transaction as PinTransaction};
    use embedded_hal_mock::eh1::MockError;

    #[test]
    fn test_read_byte() {
        // Set up the pin transactions to mock the behavior of the sensor during the reading of a byte.
        // Each bit read from the sensor starts with a High state that lasts long enough
        // to signify the bit, followed by reading whether it stays High (bit 1) or goes Low (bit 0).
        let expectations = [
            // Bit 1 - 0
            PinTransaction::get(State::High),
            PinTransaction::get(State::Low),
            // Bit 2 - 1
            PinTransaction::get(State::High),
            PinTransaction::get(State::High),
            PinTransaction::get(State::Low),
            // Bit 3 - 0
            PinTransaction::get(State::High),
            PinTransaction::get(State::Low),
            // Bit 4 - 1
            PinTransaction::get(State::High),
            PinTransaction::get(State::High),
            PinTransaction::get(State::Low),
            // Bit 5 - 0
            PinTransaction::get(State::High),
            PinTransaction::get(State::Low),
            // Bit 6 - 1
            PinTransaction::get(State::High),
            PinTransaction::get(State::High),
            PinTransaction::get(State::Low),
            // Bit 7 - 1
            PinTransaction::get(State::High),
            PinTransaction::get(State::High),
            PinTransaction::get(State::Low),
            // Bit 8 - 1
            PinTransaction::get(State::High),
            PinTransaction::get(State::High),
            PinTransaction::get(State::Low),
        ];

        let mock_pin = Mock::new(&expectations);
        let mock_delay = MockNoop::new();

        let mut dht = Dht::new(mock_pin, mock_delay);

        let result = dht.read_byte().unwrap();
        assert_eq!(result, 0b01010111);

        dht.pin.done();
    }

    #[test]
    fn test_wait_until_state() {
        let expectations = [
            PinTransaction::get(State::Low),
            PinTransaction::get(State::Low),
            PinTransaction::get(State::High),
        ];

        let mock_pin = Mock::new(&expectations);
        let mock_delay = MockNoop::new();

        let mut dht = Dht::new(mock_pin, mock_delay);

        let result = dht.wait_until_state(PinState::High);
        assert!(result.is_ok());

        dht.pin.done();
    }

    #[test]
    fn test_wait_until_state_timeout_error() {
        let expectations: [PinTransaction; DEFAULT_MAX_ATTEMPTS] = [
            PinTransaction::get(State::Low),
            PinTransaction::get(State::Low),
            PinTransaction::get(State::Low),
            PinTransaction::get(State::Low),
            PinTransaction::get(State::Low),
            PinTransaction::get(State::Low),
            PinTransaction::get(State::Low),
            PinTransaction::get(State::Low),
            PinTransaction::get(State::Low),
            PinTransaction::get(State::Low),
        ];

        let mock_pin = Mock::new(&expectations);
        let mock_delay = MockNoop::new();

        let mut dht = Dht::new(mock_pin, mock_delay);

        let result = dht.wait_until_state(PinState::High);
        assert!(matches!(result, Err(SensorError::Timeout)));

        dht.pin.done();
    }

    #[test]
    fn test_wait_until_state_pin_error() {
        let err = MockError::Io(ErrorKind::NotConnected);
        let expectations = [PinTransaction::get(State::High).with_error(err)];
        let mock_pin = Mock::new(&expectations);
        let mock_delay = MockNoop::new();

        let mut dht = Dht::new(mock_pin, mock_delay);

        let result = dht.wait_until_state(PinState::High);
        assert!(matches!(result, Err(SensorError::PinError)));

        dht.pin.done();
    }
}