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#[derive(Debug, Clone, Copy)]
12pub struct Reading {
13 humidity: f32,
14 temperature: f32,
15}
16
17impl Reading {
18 pub fn humidity(&self) -> f32 {
20 self.humidity
21 }
22
23 pub fn temperature(&self) -> f32 {
25 self.temperature
26 }
27}
28
29#[derive(Debug, Clone)]
31pub enum DhtError<HE> {
32 NotPresent,
34 ChecksumMismatch(u8, u8),
36 InvalidData,
38 Timeout,
40 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
70pub trait InterruptControl {
72 fn enable_interrupts(&mut self);
73 fn disable_interrupts(&mut self);
74}
75
76pub struct NoopInterruptControl;
78
79impl InterruptControl for NoopInterruptControl {
80 fn enable_interrupts(&mut self) {}
81 fn disable_interrupts(&mut self) {}
82}
83
84pub trait DhtSensor<HE> {
89 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 self.pin.set_low()?;
131 self.delay.delay_us(3000);
132
133 self.pin.set_high()?;
135 self.delay.delay_us(25);
136
137 self.wait_for_level(PinState::High, 85, DhtError::NotPresent)?;
139 self.wait_for_level(PinState::Low, 85, DhtError::NotPresent)?;
140
141 for bit in 0..40 {
143 self.wait_for_level(PinState::High, 55, DhtError::Timeout)?;
145
146 let elapsed = self.wait_for_level(PinState::Low, 70, DhtError::Timeout)?;
148 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
196pub 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
228pub 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}