dht_embedded/
lib.rs

1#![doc = include_str!("../README.md")]
2#![cfg_attr(not(feature = "std"), no_std)]
3
4use core::fmt;
5use embedded_hal::{
6    delay::DelayNs,
7    digital::{InputPin, OutputPin, PinState},
8};
9
10/// A sensor reading
11#[derive(Debug, Clone, Copy)]
12pub struct Reading {
13    humidity: f32,
14    temperature: f32,
15}
16
17impl Reading {
18    /// Returns the ambient humidity, as a percentage value from 0.0 to 100.0
19    pub fn humidity(&self) -> f32 {
20        self.humidity
21    }
22
23    /// Returns the ambient temperature, in degrees Celsius
24    pub fn temperature(&self) -> f32 {
25        self.temperature
26    }
27}
28
29/// A type detailing various errors the DHT sensor can return
30#[derive(Debug, Clone)]
31pub enum DhtError<HE> {
32    /// The DHT sensor was not found on the specified GPIO
33    NotPresent,
34    /// The checksum provided in the DHT sensor data did not match the checksum of the data itself (expected, calculated)
35    ChecksumMismatch(u8, u8),
36    /// The seemingly-valid data has impossible values (e.g. a humidity value less than 0 or greater than 100)
37    InvalidData,
38    /// The read timed out
39    Timeout,
40    /// Received a low-level error from the HAL while reading or writing to pins
41    PinError(HE),
42}
43
44impl<HE> From<HE> for DhtError<HE> {
45    fn from(error: HE) -> Self {
46        DhtError::PinError(error)
47    }
48}
49
50impl<HE: fmt::Debug> fmt::Display for DhtError<HE> {
51    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52        use DhtError::*;
53        match self {
54            NotPresent => write!(f, "DHT device not found"),
55            ChecksumMismatch(expected, calculated) => write!(
56                f,
57                "Data read was corrupt (expected checksum {:x}, calculated {:x})",
58                expected, calculated
59            ),
60            InvalidData => f.write_str("Received data is out of range"),
61            Timeout => f.write_str("Timed out waiting for a read"),
62            PinError(err) => write!(f, "HAL pin error: {:?}", err),
63        }
64    }
65}
66
67#[cfg(feature = "std")]
68impl<HE: fmt::Debug> std::error::Error for DhtError<HE> {}
69
70/// Trait that allows us to disable interrupts when reading from the sensor
71pub trait InterruptControl {
72    fn enable_interrupts(&mut self);
73    fn disable_interrupts(&mut self);
74}
75
76/// A dummy implementation of InterruptControl that does nothing
77pub struct NoopInterruptControl;
78
79impl InterruptControl for NoopInterruptControl {
80    fn enable_interrupts(&mut self) {}
81    fn disable_interrupts(&mut self) {}
82}
83
84/// A trait for reading data from the sensor
85///
86/// This level of indirection is useful so you can write generic code that
87/// does not assume whether a DHT11 or DHT22 sensor is being used.
88pub trait DhtSensor<HE> {
89    /// Reads data from the sensor and returns a `Reading`
90    fn read(&mut self) -> Result<Reading, DhtError<HE>>;
91}
92
93#[doc(hidden)]
94pub struct Dht<
95    HE,
96    ID: InterruptControl,
97    D: DelayNs,
98    P: InputPin<Error = HE> + OutputPin<Error = HE>,
99> {
100    interrupt_disabler: ID,
101    delay: D,
102    pin: P,
103}
104
105impl<HE, ID: InterruptControl, D: DelayNs, P: InputPin<Error = HE> + OutputPin<Error = HE>>
106    Dht<HE, ID, D, P>
107{
108    fn new(interrupt_disabler: ID, delay: D, pin: P) -> Self {
109        Self {
110            interrupt_disabler,
111            delay,
112            pin,
113        }
114    }
115
116    fn read(&mut self, parse_data: fn(&[u8]) -> (f32, f32)) -> Result<Reading, DhtError<HE>> {
117        self.interrupt_disabler.disable_interrupts();
118        let res = self.read_uninterruptible(parse_data);
119        self.interrupt_disabler.enable_interrupts();
120        res
121    }
122
123    fn read_uninterruptible(
124        &mut self,
125        parse_data: fn(&[u8]) -> (f32, f32),
126    ) -> Result<Reading, DhtError<HE>> {
127        let mut buf: [u8; 5] = [0; 5];
128
129        // Wake up the sensor
130        self.pin.set_low()?;
131        self.delay.delay_us(3000);
132
133        // Ask for data
134        self.pin.set_high()?;
135        self.delay.delay_us(25);
136
137        // Wait for DHT to signal data is ready (~80us low followed by ~80us high)
138        self.wait_for_level(PinState::High, 85, DhtError::NotPresent)?;
139        self.wait_for_level(PinState::Low, 85, DhtError::NotPresent)?;
140
141        // Now read 40 data bits
142        for bit in 0..40 {
143            // Wait ~50us for high
144            self.wait_for_level(PinState::High, 55, DhtError::Timeout)?;
145
146            // See how long it takes to go low, with max of 70us
147            let elapsed = self.wait_for_level(PinState::Low, 70, DhtError::Timeout)?;
148            // If it took at least 30us to go low, it's a '1' bit
149            if elapsed > 30 {
150                let byte = bit / 8;
151                let shift = 7 - bit % 8;
152                buf[byte] |= 1 << shift;
153            }
154        }
155
156        let checksum = (buf[0..=3]
157            .iter()
158            .fold(0u16, |accum, next| accum + *next as u16)
159            & 0xff) as u8;
160        if buf[4] == checksum {
161            let (humidity, temperature) = parse_data(&buf);
162            if !(0.0..=100.0).contains(&humidity) {
163                Err(DhtError::InvalidData)
164            } else {
165                Ok(Reading {
166                    humidity,
167                    temperature,
168                })
169            }
170        } else {
171            Err(DhtError::ChecksumMismatch(buf[4], checksum))
172        }
173    }
174
175    fn wait_for_level(
176        &mut self,
177        level: PinState,
178        timeout_us: u32,
179        on_timeout: DhtError<HE>,
180    ) -> Result<u32, DhtError<HE>> {
181        for elapsed in 0..=timeout_us {
182            let is_ready = match level {
183                PinState::High => self.pin.is_high(),
184                PinState::Low => self.pin.is_low(),
185            }?;
186
187            if is_ready {
188                return Ok(elapsed);
189            }
190            self.delay.delay_us(1);
191        }
192        Err(on_timeout)
193    }
194}
195
196/// A DHT11 sensor
197pub struct Dht11<
198    HE,
199    ID: InterruptControl,
200    D: DelayNs,
201    P: InputPin<Error = HE> + OutputPin<Error = HE>,
202> {
203    dht: Dht<HE, ID, D, P>,
204}
205
206impl<HE, ID: InterruptControl, D: DelayNs, P: InputPin<Error = HE> + OutputPin<Error = HE>>
207    Dht11<HE, ID, D, P>
208{
209    pub fn new(interrupt_disabler: ID, delay: D, pin: P) -> Self {
210        Self {
211            dht: Dht::new(interrupt_disabler, delay, pin),
212        }
213    }
214
215    fn parse_data(buf: &[u8]) -> (f32, f32) {
216        (buf[0] as f32, buf[2] as f32)
217    }
218}
219
220impl<HE, ID: InterruptControl, D: DelayNs, P: InputPin<Error = HE> + OutputPin<Error = HE>>
221    DhtSensor<HE> for Dht11<HE, ID, D, P>
222{
223    fn read(&mut self) -> Result<Reading, DhtError<HE>> {
224        self.dht.read(Dht11::<HE, ID, D, P>::parse_data)
225    }
226}
227
228/// A DHT22 sensor
229pub struct Dht22<
230    HE,
231    ID: InterruptControl,
232    D: DelayNs,
233    P: InputPin<Error = HE> + OutputPin<Error = HE>,
234> {
235    dht: Dht<HE, ID, D, P>,
236}
237
238impl<HE, ID: InterruptControl, D: DelayNs, P: InputPin<Error = HE> + OutputPin<Error = HE>>
239    Dht22<HE, ID, D, P>
240{
241    pub fn new(interrupt_disabler: ID, delay: D, pin: P) -> Self {
242        Self {
243            dht: Dht::new(interrupt_disabler, delay, pin),
244        }
245    }
246
247    fn parse_data(buf: &[u8]) -> (f32, f32) {
248        let humidity = (((buf[0] as u16) << 8) | buf[1] as u16) as f32 / 10.0;
249        let mut temperature = ((((buf[2] & 0x7f) as u16) << 8) | buf[3] as u16) as f32 / 10.0;
250        if buf[2] & 0x80 != 0 {
251            temperature = -temperature;
252        }
253        (humidity, temperature)
254    }
255}
256
257impl<HE, ID: InterruptControl, D: DelayNs, P: InputPin<Error = HE> + OutputPin<Error = HE>>
258    DhtSensor<HE> for Dht22<HE, ID, D, P>
259{
260    fn read(&mut self) -> Result<Reading, DhtError<HE>> {
261        self.dht.read(Dht22::<HE, ID, D, P>::parse_data)
262    }
263}