Skip to main content

dht22_sensor/
dht22.rs

1use embedded_hal::{
2    delay::DelayNs,
3    digital::{InputPin, OutputPin},
4};
5
6use crate::error::DhtError;
7
8/// Maximum time to wait (in microseconds) for the pin to change state.
9///
10/// Used to detect timeouts when waiting for the DHT22 to respond.
11const TIMEOUT_US: u8 = 100;
12
13/// Driver for the DHT22 temperature and humidity sensor.
14pub struct Dht22<PIN, D> {
15    pin: PIN,
16    delay: D,
17}
18
19/// Reading returned by the DHT22 sensor.
20#[cfg_attr(feature = "defmt", derive(defmt::Format))]
21#[derive(Clone, Copy, Debug, PartialEq)]
22pub struct Reading {
23    /// Temperature in degrees Celsius.
24    pub temperature: f32,
25    /// Relative humidity in percent.
26    pub relative_humidity: f32,
27}
28
29impl<PIN, DELAY, E> Dht22<PIN, DELAY>
30where
31    PIN: InputPin<Error = E> + OutputPin<Error = E>,
32    DELAY: DelayNs,
33{
34    /// Creates a new instance of the DHT22 driver.
35    ///
36    /// # Arguments
37    ///
38    /// * `pin` - The GPIO pin connected to the DHT22 data line. Must support both input and output.
39    /// * `delay` - A delay provider implementing the `DelayNs` trait.
40    pub fn new(pin: PIN, delay: DELAY) -> Self {
41        Dht22 { pin, delay }
42    }
43
44    /// Reads a temperature and humidity measurement from the DHT22 sensor.
45    ///
46    /// This method performs the complete DHT22 communication sequence:
47    /// sending a start signal, waiting for the sensor's response,
48    /// reading 5 bytes, validating the checksum, and decoding the result.
49    ///
50    /// # Returns
51    ///
52    /// * `Ok(Reading)` if the read is successful and the checksum is valid.
53    /// * `Err(DhtError)` if a communication or checksum error occurs.
54    pub fn read(&mut self) -> Result<Reading, DhtError<E>> {
55        self.start()?;
56
57        let mut data = [0; 4];
58
59        for b in data.iter_mut() {
60            *b = self.read_byte()?;
61        }
62
63        let checksum = self.read_byte()?;
64        if data.iter().fold(0u8, |sum, v| sum.wrapping_add(*v)) != checksum {
65            Err(DhtError::ChecksumMismatch)
66        } else {
67            Ok(self.parse_data(data))
68        }
69    }
70
71    /// Converts the 4-byte data into a `Reading` struct.
72    fn parse_data(&self, data: [u8; 4]) -> Reading {
73        let [hum_hi, hum_lo, temp_hi, temp_lo] = data;
74
75        let joined_humidity = u16::from_be_bytes([hum_hi, hum_lo]);
76        let relative_humidity = joined_humidity as f32 / 10.0;
77
78        let is_temp_negative = (temp_hi >> 7) != 0;
79        let temp_hi = temp_hi & 0b0111_1111;
80        let joined_temp = u16::from_be_bytes([temp_hi, temp_lo]);
81        let mut temperature = joined_temp as f32 / 10.0;
82        if is_temp_negative {
83            temperature = -temperature;
84        }
85
86        Reading {
87            temperature,
88            relative_humidity,
89        }
90    }
91
92    /// Sends the start signal to the DHT22 and waits for its response.
93    ///
94    /// This includes pulling the line low for at least 1 ms,
95    /// then high, followed by waiting for the sensor's 80us low
96    /// and 80us high response.
97    fn start(&mut self) -> Result<(), DhtError<E>> {
98        // MCU sends start request
99        self.pin.set_low()?;
100        self.delay.delay_ms(1);
101        self.pin.set_high()?;
102        self.delay.delay_us(40);
103
104        // Waiting for DHT22 Response
105        self.wait_for_low()?; // 80us
106        self.wait_for_high()?; // 80us
107        Ok(())
108    }
109
110    /// Reads one byte (8 bits) from the sensor.
111    ///
112    /// # Returns
113    ///
114    /// * `Ok(u8)` with the read byte
115    /// * `Err(DhtError)` on communication failure
116    fn read_byte(&mut self) -> Result<u8, DhtError<E>> {
117        let mut byte: u8 = 0;
118
119        for i in 0..8 {
120            let bit_mask = 1 << (7 - i);
121            if self.read_bit()? {
122                byte |= bit_mask;
123            }
124        }
125
126        Ok(byte)
127    }
128
129    /// Reads a single bit from the sensor.
130    ///
131    /// The bit is determined by the duration of the high signal
132    /// after the DHT22 pulls the line low.
133    fn read_bit(&mut self) -> Result<bool, DhtError<E>> {
134        // Wait for DHT pulls line low
135        self.wait_for_low()?; // ~50us
136
137        // Step 2: DHT pulls line high
138        self.wait_for_high()?;
139
140        // Step 3: Delay ~35us, then sample pin
141        self.delay.delay_us(35);
142
143        // If it is still High, then the bit value is 1
144        let bit_is_one = self.pin.is_high()?;
145        self.wait_for_low()?;
146
147        Ok(bit_is_one)
148    }
149
150    /// Waits until the data line goes high or times out.
151    fn wait_for_high(&mut self) -> Result<(), DhtError<E>> {
152        Self::wait_for_state(&mut self.delay, || self.pin.is_high())
153    }
154
155    /// Waits until the data line goes low or times out.
156    fn wait_for_low(&mut self) -> Result<(), DhtError<E>> {
157        Self::wait_for_state(&mut self.delay, || self.pin.is_low())
158    }
159
160    /// Generic wait loop that checks a pin condition until true or timeout.
161    ///
162    /// # Arguments
163    ///
164    /// * `delay` - Delay provider
165    /// * `condition` - Closure that returns true when the expected condition is met
166    ///
167    /// # Errors
168    ///
169    /// Returns `DhtError::Timeout` if the timeout is exceeded
170    fn wait_for_state<F>(delay: &mut DELAY, mut condition: F) -> Result<(), DhtError<E>>
171    where
172        F: FnMut() -> Result<bool, E>,
173    {
174        for _ in 0..TIMEOUT_US {
175            if condition()? {
176                return Ok(());
177            }
178            delay.delay_us(1);
179        }
180        Err(DhtError::Timeout)
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187    use embedded_hal_mock::eh1::delay::CheckedDelay;
188    use embedded_hal_mock::eh1::delay::NoopDelay;
189    // use embedded_hal_mock::eh1::delay::NoopDelay;
190    use embedded_hal_mock::eh1::delay::Transaction as DelayTx;
191    use embedded_hal_mock::eh1::digital::{
192        Mock as PinMock, State as PinState, Transaction as PinTx,
193    };
194
195    fn start_sequence() -> Vec<PinTx> {
196        vec![
197            PinTx::set(PinState::High), // Initial High
198            // MCU initiates communication by pulling the data line low, then releasing it (pulling it high)
199            PinTx::set(PinState::Low),
200            PinTx::set(PinState::High),
201            // Sensor responds
202            PinTx::get(PinState::Low),
203            PinTx::get(PinState::High),
204        ]
205    }
206
207    // Helper to encode one byte into 8 bits (MSB first)
208    fn encode_byte(byte: u8) -> Vec<PinTx> {
209        (0..8)
210            .flat_map(|i| {
211                // Extract bit (MSB first: bit 7 to bit 0)
212                let bit = (byte >> (7 - i)) & 1;
213                vec![
214                    PinTx::get(PinState::Low),  // wait_for_low
215                    PinTx::get(PinState::High), // wait_for_high
216                    PinTx::get(if bit == 1 {
217                        // sample
218                        PinState::High
219                    } else {
220                        PinState::Low
221                    }),
222                    PinTx::get(PinState::Low), // end of bit
223                ]
224            })
225            .collect()
226    }
227
228    #[test]
229    fn test_start_sequence() {
230        let mut expect = vec![];
231        expect.extend_from_slice(&start_sequence());
232
233        let mut pin = PinMock::new(&expect);
234        pin.set_high().unwrap();
235
236        let delay_transactions = vec![DelayTx::delay_ms(1), DelayTx::delay_us(40)];
237        let mut delay = CheckedDelay::new(&delay_transactions);
238
239        let mut dht = Dht22::new(pin.clone(), &mut delay);
240        dht.start().unwrap();
241
242        pin.done();
243        delay.done();
244    }
245
246    #[test]
247    fn test_wait_for_state() {
248        let mut expect = vec![];
249
250        expect.extend_from_slice(&[
251            // pin setting high
252            PinTx::set(PinState::High),
253            // wait_for_high
254            PinTx::get(PinState::Low), // Triggers Delay 1us
255            PinTx::get(PinState::Low), // Triggers Delay 1us
256            PinTx::get(PinState::High),
257            // wait_for_low
258            PinTx::get(PinState::Low),
259        ]);
260
261        let mut pin = PinMock::new(&expect);
262        pin.set_high().unwrap();
263
264        let delay_transactions = vec![DelayTx::delay_us(1), DelayTx::delay_us(1)];
265        let mut delay = CheckedDelay::new(&delay_transactions);
266
267        let mut dht = Dht22::new(pin.clone(), &mut delay);
268        dht.wait_for_high().unwrap();
269        dht.wait_for_low().unwrap();
270
271        pin.done();
272        delay.done();
273    }
274
275    #[test]
276    fn test_read_bit_one() {
277        let mut pin = PinMock::new(&[
278            // wait_for_low
279            PinTx::get(PinState::Low), // Mimicks DHT pulling low to signal start of data bit
280            // wait_for_high
281            PinTx::get(PinState::High), // Then pulls high - duration determines bit value
282            // delay_us(35) -> handled in delay
283            // Sample pin after delay
284            PinTx::get(PinState::High), // is it still High? (High -> 1)
285            // Final wait_for_low
286            PinTx::get(PinState::Low), // End of bit
287        ]);
288
289        let delay_transactions = vec![
290            // wait_for_low
291            DelayTx::delay_us(35),
292        ];
293        let mut delay = CheckedDelay::new(&delay_transactions);
294
295        let mut dht = Dht22::new(pin.clone(), &mut delay);
296
297        let bit = dht.read_bit().unwrap();
298        assert!(bit);
299
300        pin.done();
301        delay.done();
302    }
303
304    #[test]
305    fn test_read_bit_zero() {
306        let mut pin = PinMock::new(&[
307            // wait_for_low
308            PinTx::get(PinState::High), // To trigger Delay of 1 us, we keep it High first
309            PinTx::get(PinState::Low),
310            // wait_for_high
311            PinTx::get(PinState::Low), // To trigger Delay of 1 us, we keep it Low first
312            PinTx::get(PinState::High), // now high
313            // sample bit after delay (35us)
314            PinTx::get(PinState::Low), // We will set it Low to indicate bit value is "0"
315            // final wait_for_low
316            PinTx::get(PinState::High), // To trigger Delay of 1 us, we keep it High first
317            PinTx::get(PinState::Low),  // now low
318        ]);
319
320        let delay_transactions = vec![
321            DelayTx::delay_us(1),  // after 1st pin high during wait_for_low
322            DelayTx::delay_us(1),  // after 1st pin low during wait_for_high
323            DelayTx::delay_us(35), // sampling delay
324            DelayTx::delay_us(1),  // after 1st high in final wait_for_low
325        ];
326        let mut delay = CheckedDelay::new(&delay_transactions);
327
328        let mut dht = Dht22::new(pin.clone(), &mut delay);
329
330        let bit = dht.read_bit().unwrap();
331        assert!(!bit);
332
333        pin.done();
334        delay.done();
335    }
336
337    #[test]
338    fn test_read_timeout() {
339        let pin_expects: Vec<PinTx> = (0..100).map(|_| PinTx::get(PinState::High)).collect();
340        let mut pin = PinMock::new(&pin_expects);
341
342        let delay_expects: Vec<DelayTx> = (0..100).map(|_| DelayTx::delay_us(1)).collect();
343
344        let mut delay = CheckedDelay::new(&delay_expects);
345
346        let mut dht = Dht22::new(pin.clone(), &mut delay);
347
348        assert_eq!(dht.read_bit().unwrap_err(), DhtError::Timeout);
349
350        pin.done();
351        delay.done();
352    }
353
354    #[test]
355    fn test_parse_data_positive_temp() {
356        let mut pin = PinMock::new(&[]);
357
358        let dht = Dht22::new(pin.clone(), NoopDelay);
359        // Humidity: 55.5% -> [0x02, 0x2B] => 555
360        // Temperature: 24.6C -> [0x00, 0xF6] => 246
361        let data = [0x02, 0x2B, 0x00, 0xF6];
362
363        let reading = dht.parse_data(data);
364
365        assert_eq!(
366            reading,
367            Reading {
368                relative_humidity: 55.5,
369                temperature: 24.6,
370            }
371        );
372        pin.done();
373    }
374
375    #[test]
376    fn test_parse_data_negative_temp() {
377        let mut pin = PinMock::new(&[]);
378
379        let dht = Dht22::new(pin.clone(), NoopDelay);
380
381        // Humidity: 40.0% -> [0x01, 0x90] => 400
382        // Temperature: -1.0C -> [0x80, 0x0A]
383        // Bit 7 of temp_hi is 1 => negative
384        // Clear sign bit: 0x80 & 0x7F = 0x00, so [0x00, 0x0A] = 10 => 1.0 then negated
385        let data = [0x01, 0x90, 0x80, 0x0A];
386
387        let reading = dht.parse_data(data);
388
389        assert_eq!(
390            reading,
391            Reading {
392                relative_humidity: 40.0,
393                temperature: -1.0,
394            }
395        );
396        pin.done();
397    }
398
399    #[test]
400    fn test_read_byte() {
401        let pin_states = encode_byte(0b10111010);
402
403        let mut pin = PinMock::new(&pin_states);
404        let delay_expects = vec![DelayTx::delay_us(35); 8];
405        let mut delay = CheckedDelay::new(&delay_expects);
406
407        let mut dht = Dht22::new(pin.clone(), &mut delay);
408        let byte = dht.read_byte().unwrap();
409        assert_eq!(byte, 0b10111010);
410
411        pin.done();
412        delay.done();
413    }
414
415    #[test]
416    fn test_read_valid() {
417        // Data to simulate: [0x01, 0x90, 0x00, 0xF6], checksum = 0x87
418
419        // Start sequence
420        let mut pin_states = start_sequence();
421
422        let data_bytes = [0x01, 0x90, 0x00, 0xF6];
423        let checksum = 0x87;
424
425        for byte in data_bytes.iter().chain(std::iter::once(&checksum)) {
426            pin_states.extend(encode_byte(*byte));
427        }
428
429        let mut pin = PinMock::new(&pin_states);
430        pin.set_high().unwrap();
431
432        // Delays: start = 1ms + 40us
433        let mut delay_transactions = vec![DelayTx::delay_ms(1), DelayTx::delay_us(40)];
434        // Delay for data bit transfer: 40 bits * 35us delay
435        delay_transactions.extend(std::iter::repeat_n(DelayTx::delay_us(35), 40));
436
437        let mut delay = CheckedDelay::new(&delay_transactions);
438
439        let mut dht = Dht22::new(pin.clone(), &mut delay);
440        let reading = dht.read().unwrap();
441
442        assert_eq!(
443            reading,
444            Reading {
445                relative_humidity: 40.0,
446                temperature: 24.6,
447            }
448        );
449
450        pin.done();
451        delay.done();
452    }
453
454    #[test]
455    fn test_read_invalid() {
456        // Data to simulate: [0x01, 0x90, 0x00, 0xF6], checksum = 0x87
457
458        // Start sequence
459        let mut pin_states = start_sequence();
460
461        let data_bytes = [0x01, 0x90, 0x00, 0xF6];
462        let checksum = 0x81; // Wrong checksum value
463
464        for byte in data_bytes.iter().chain(std::iter::once(&checksum)) {
465            pin_states.extend(encode_byte(*byte));
466        }
467
468        let mut pin = PinMock::new(&pin_states);
469        pin.set_high().unwrap();
470
471        // Delays: start = 1ms + 40us
472        let mut delay_transactions = vec![DelayTx::delay_ms(1), DelayTx::delay_us(40)];
473        // Delay for data bit transfer: 40 bits * 35us delay
474        delay_transactions.extend(std::iter::repeat_n(DelayTx::delay_us(35), 40));
475
476        let mut delay = CheckedDelay::new(&delay_transactions);
477
478        let mut dht = Dht22::new(pin.clone(), &mut delay);
479        assert_eq!(dht.read().unwrap_err(), DhtError::ChecksumMismatch);
480
481        pin.done();
482        delay.done();
483    }
484}