1use core::result::Result::{self, Err, Ok};
22
23use embedded_hal::delay::DelayNs as SyncDelay;
24use embedded_hal::digital::{InputPin, OutputPin, PinState};
25
26use embedded_hal_async::delay::DelayNs as AsyncDelay;
27
28const START_SIGNAL_LOW_MS: u32 = 18; const START_SIGNAL_HIGH_US: u32 = 40; const BIT_SAMPLE_DELAY_US: u32 = 35; const POLL_DELAY_US: u32 = 1; const MAX_ATTEMPTS: usize = 100; #[derive(Debug, Clone, Copy)]
37pub struct Measurement {
38 pub humidity: f32,
40 pub temperature: f32,
42}
43
44#[derive(Debug)]
46pub enum Dht22Error<E> {
47 Pin(E),
49 ChecksumMismatch,
51 Timeout,
53}
54
55impl<E> From<E> for Dht22Error<E> {
56 fn from(e: E) -> Self {
57 Dht22Error::Pin(e)
58 }
59}
60
61pub struct Dht22<P, D>
63where
64 P: InputPin + OutputPin,
65 D: SyncDelay + AsyncDelay,
66{
67 pin: P,
68 delay: D,
69}
70
71type RawData = (u8, u8, u8, u8, u8);
73
74impl<P, D> Dht22<P, D>
75where
76 P: InputPin + OutputPin,
77 D: SyncDelay + AsyncDelay,
78{
79 #[must_use]
81 pub fn new(pin: P, delay: D) -> Self {
82 Self { pin, delay }
83 }
84
85 pub fn read(&mut self) -> Result<Measurement, Dht22Error<P::Error>> {
94 self.send_start_signal()?;
96
97 self.wait_for_sensor_response()?;
99
100 let (hh, hl, th, tl, checksum) = self.read_raw_data()?;
102
103 Self::validate_checksum(hh, hl, th, tl, checksum)?;
105
106 Ok(Measurement {
107 humidity: Self::decode_humidity(hh, hl),
108 temperature: Self::decode_temperature(th, tl),
109 })
110 }
111
112 fn send_start_signal(&mut self) -> Result<(), Dht22Error<P::Error>> {
113 self.pin.set_low()?;
115 SyncDelay::delay_ms(&mut self.delay, START_SIGNAL_LOW_MS);
116
117 self.pin.set_high()?;
119 SyncDelay::delay_us(&mut self.delay, START_SIGNAL_HIGH_US);
120
121 Ok(())
122 }
123
124 fn wait_for_sensor_response(&mut self) -> Result<(), Dht22Error<P::Error>> {
125 self.wait_until_state(PinState::Low)?;
127 self.wait_until_state(PinState::High)?;
128
129 Ok(())
130 }
131
132 fn read_raw_data(&mut self) -> Result<RawData, Dht22Error<P::Error>> {
133 Ok((
135 self.read_byte()?,
136 self.read_byte()?,
137 self.read_byte()?,
138 self.read_byte()?,
139 self.read_byte()?,
140 ))
141 }
142
143 #[inline]
144 fn validate_checksum(
145 hh: u8,
146 hl: u8,
147 th: u8,
148 tl: u8,
149 checksum: u8,
150 ) -> Result<(), Dht22Error<P::Error>> {
151 let sum = hh.wrapping_add(hl).wrapping_add(th).wrapping_add(tl);
153
154 if sum == checksum {
155 Ok(())
156 } else {
157 Err(Dht22Error::ChecksumMismatch)
158 }
159 }
160
161 #[inline]
162 fn decode_humidity(high: u8, low: u8) -> f32 {
163 f32::from((u16::from(high) << 8) | u16::from(low)) / 10.0
165 }
166
167 #[inline]
168 fn decode_temperature(high: u8, low: u8) -> f32 {
169 let raw = (u16::from(high & 0x7F) << 8) | u16::from(low);
171 let mut t = f32::from(raw) / 10.0;
172
173 if high & 0x80 != 0 {
175 t = -t;
176 }
177
178 t
179 }
180
181 fn wait_until_state(&mut self, state: PinState) -> Result<(), Dht22Error<P::Error>> {
182 for _ in 0..MAX_ATTEMPTS {
184 let reached = match state {
185 PinState::High => self.pin.is_high()?,
186 PinState::Low => self.pin.is_low()?,
187 };
188 if reached {
189 return Ok(());
190 }
191 SyncDelay::delay_us(&mut self.delay, POLL_DELAY_US);
192 }
193
194 Err(Dht22Error::Timeout)
195 }
196
197 fn read_byte(&mut self) -> Result<u8, Dht22Error<P::Error>> {
198 let mut byte = 0;
199
200 for i in 0..8 {
203 self.wait_until_state(PinState::Low)?; self.wait_until_state(PinState::High)?; SyncDelay::delay_us(&mut self.delay, BIT_SAMPLE_DELAY_US);
208
209 if self.pin.is_high()? {
211 byte |= 1 << (7 - i); }
213 }
214
215 Ok(byte)
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 extern crate std;
224 use std::vec;
225
226 use embedded_hal_mock::eh1::delay::NoopDelay;
227 use embedded_hal_mock::eh1::digital::{Mock as PinMock, State, Transaction as PinTransaction};
228
229 #[test]
230 fn test_send_start_signal() {
231 let expectations = [
232 PinTransaction::set(State::Low),
233 PinTransaction::set(State::High),
234 ];
235
236 let pin = PinMock::new(&expectations);
237 let delay = NoopDelay::new();
238 let mut dht22 = Dht22::new(pin, delay);
239
240 dht22.send_start_signal().unwrap();
241
242 dht22.pin.done();
243 }
244
245 #[test]
246 fn test_wait_for_sensor_response_success() {
247 let expectations = [
248 PinTransaction::get(State::Low),
249 PinTransaction::get(State::High),
250 ];
251
252 let pin = PinMock::new(&expectations);
253 let delay = NoopDelay::new();
254 let mut dht22 = Dht22::new(pin, delay);
255
256 dht22.wait_for_sensor_response().unwrap();
257
258 dht22.pin.done();
259 }
260
261 #[test]
262 fn test_wait_until_state_timeout() {
263 let expectations = vec![PinTransaction::get(State::High); MAX_ATTEMPTS];
265
266 let pin = PinMock::new(&expectations);
267 let delay = NoopDelay::new();
268 let mut dht22 = Dht22::new(pin, delay);
269
270 let result = dht22.wait_until_state(PinState::Low);
271 assert!(matches!(result, Err(Dht22Error::Timeout)));
272
273 dht22.pin.done();
274 }
275
276 #[test]
277 fn test_read_byte_all_zeros() {
278 let mut expectations = vec![];
279
280 for _ in 0..8 {
282 expectations.push(PinTransaction::get(State::Low)); expectations.push(PinTransaction::get(State::High)); expectations.push(PinTransaction::get(State::Low)); }
286
287 let pin = PinMock::new(&expectations);
288 let delay = NoopDelay::new();
289 let mut dht22 = Dht22::new(pin, delay);
290
291 let byte = dht22.read_byte().unwrap();
292 assert_eq!(byte, 0x00);
293
294 dht22.pin.done();
295 }
296
297 #[test]
298 fn test_read_byte_all_ones() {
299 let mut expectations = vec![];
300
301 for _ in 0..8 {
303 expectations.push(PinTransaction::get(State::Low)); expectations.push(PinTransaction::get(State::High)); expectations.push(PinTransaction::get(State::High)); }
307
308 let pin = PinMock::new(&expectations);
309 let delay = NoopDelay::new();
310 let mut dht22 = Dht22::new(pin, delay);
311
312 let byte = dht22.read_byte().unwrap();
313 assert_eq!(byte, 0xFF);
314
315 dht22.pin.done();
316 }
317
318 #[test]
319 fn test_decode_humidity_temperature() {
320 let humidity = Dht22::<PinMock, NoopDelay>::decode_humidity(0x02, 0x58); let temperature = Dht22::<PinMock, NoopDelay>::decode_temperature(0x00, 0xFA); let temperature_neg = Dht22::<PinMock, NoopDelay>::decode_temperature(0x80, 0xFA); assert!((humidity - 60.0).abs() < f32::EPSILON);
325 assert!((temperature - 25.0).abs() < f32::EPSILON);
326 assert!((temperature_neg + 25.0).abs() < f32::EPSILON);
327 }
328
329 #[test]
330 fn test_validate_checksum() {
331 let result_ok = Dht22::<PinMock, NoopDelay>::validate_checksum(1, 2, 3, 4, 10);
332 assert!(result_ok.is_ok());
333
334 let result_err = Dht22::<PinMock, NoopDelay>::validate_checksum(1, 2, 3, 4, 9);
335 assert!(matches!(result_err, Err(Dht22Error::ChecksumMismatch)));
336 }
337}