1#![cfg_attr(not(feature = "std"), no_std)]
2#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/README.md"))]
3
4#[cfg(feature = "critical-section")]
5use critical_section::with;
6#[cfg(not(feature = "critical-section"))]
7fn with<R>(f: impl FnOnce(()) -> R) -> R {
8 f(())
9}
10
11#[derive(Debug)]
12pub enum DhtError<DeviceError> {
13 Handshake,
15 Timeout(Microseconds),
17 Checksum { correct: u8, actual: u8 },
19 DeviceError(DeviceError),
21}
22
23impl<DeviceError> core::fmt::Display for DhtError<DeviceError>
24where
25 DeviceError: core::fmt::Display,
26{
27 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
28 match self {
29 DhtError::Handshake => write!(f, "Inital handshake failed!"),
30 DhtError::Timeout(us) => write!(
31 f,
32 "Timeout while waiting for level change after {} microseconds",
33 us.0
34 ),
35 DhtError::Checksum { correct, actual } => write!(
36 f,
37 "Checksum validation failed. Correct: {correct}, Actual: {actual}"
38 ),
39 DhtError::DeviceError(device_error) => write!(f, "DeviceError: {device_error}"),
40 }
41 }
42}
43
44#[cfg(feature = "std")]
45impl<DeviceError> std::error::Error for DhtError<DeviceError> where DeviceError: std::error::Error {}
46
47impl<DeviceError> From<DeviceError> for DhtError<DeviceError> {
48 fn from(value: DeviceError) -> Self {
49 Self::DeviceError(value)
50 }
51}
52
53pub trait IOPin {
55 type DeviceError;
56 fn set_low(&mut self) -> Result<(), Self::DeviceError>;
57 fn set_high(&mut self) -> Result<(), Self::DeviceError>;
58 fn is_low(&self) -> bool;
59 fn is_high(&self) -> bool;
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
67pub struct Microseconds(pub u32);
68
69pub trait MicroTimer {
71 fn now(&self) -> Microseconds;
74}
75
76pub struct Dht22<Pin, Timer>
78where
79 Pin: IOPin,
80 Timer: MicroTimer,
81{
82 pin: Pin,
83 timer: Timer,
84}
85
86pub struct SensorReading {
88 pub humidity: f32,
89 pub temperature: f32,
90}
91
92impl<Pin, Timer> Dht22<Pin, Timer>
93where
94 Pin: IOPin,
95 <Pin as IOPin>::DeviceError: core::fmt::Debug,
96 Timer: MicroTimer,
97{
98 pub fn new(pin: Pin, clock: Timer) -> Self {
101 Self { pin, timer: clock }
102 }
103 pub fn read(&mut self) -> Result<SensorReading, DhtError<Pin::DeviceError>> {
107 const RESPONSE_BITS: usize = 40;
108 let mut cycles: [u32; 2 * RESPONSE_BITS + 1] = [0; 2 * RESPONSE_BITS + 1];
111 let waiter = Waiter { timer: &self.timer };
112 with(|_guard| {
114 self.pin.set_low()?;
116 let _ = waiter.wait_for(|| false, 1200);
117 self.pin.set_high()?;
118
119 if waiter.wait_for(|| self.pin.is_low(), 100).is_err() {
121 return Err(DhtError::Handshake);
122 }
123
124 if waiter.wait_for(|| self.pin.is_high(), 100).is_err() {
126 return Err(DhtError::Handshake);
127 }
128 let mut is_high = true;
132 for duration in &mut cycles {
133 *duration = waiter
134 .wait_for(|| is_high != self.pin.is_high(), 100)
135 .map(|microsecond| microsecond.0)
136 .map_err(DhtError::Timeout)?;
137
138 is_high = !is_high;
139 }
140 Ok(())
142 })
143 .map_err(|err| {
144 let _ = self.pin.set_high();
147 err
148 })?;
149
150 let mut bytes: [u8; 5] = [0; 5];
151 for (idx, _) in cycles[1..]
153 .chunks_exact(2)
155 .map(|pair| {
157 let cycles_low = pair[0];
158 let cycles_high = pair[1];
159 cycles_low < cycles_high
161 })
162 .enumerate()
164 .filter(|(_, bit)| *bit)
166 {
167 let byte_idx = idx / 8;
168 let bit_idx = idx % 8;
169 bytes[byte_idx] |= 1 << (7 - bit_idx);
170 }
171 let correct = bytes[4];
173 let actual = bytes[0]
174 .wrapping_add(bytes[1])
175 .wrapping_add(bytes[2])
176 .wrapping_add(bytes[3]);
177 if actual != correct {
178 return Err(DhtError::Checksum { actual, correct });
179 }
180 let humidity = (((bytes[0] as u32) << 8 | bytes[1] as u32) as f32) / 10.;
181 let is_negative = (bytes[2] >> 7) != 0;
183 bytes[2] &= 0b0111_1111;
184 let temperature = (((bytes[2] as u32) << 8 | bytes[3] as u32) as f32) / 10.;
185 let temperature = if is_negative {
186 -1. * temperature
187 } else {
188 temperature
189 };
190 Ok(SensorReading {
191 humidity,
192 temperature,
193 })
194 }
195}
196
197struct Waiter<'timer, Timer>
198where
199 Timer: MicroTimer,
200{
201 timer: &'timer Timer,
202}
203impl<'timer, Timer> Waiter<'timer, Timer>
204where
205 Timer: MicroTimer,
206{
207 #[inline(always)]
208 fn wait_for(
209 &self,
210 condition: impl Fn() -> bool,
211 timeout: u32,
212 ) -> Result<Microseconds, Microseconds> {
213 let start = self.timer.now();
214 loop {
215 let since_start = self.timer.now().0.wrapping_sub(start.0);
220 if condition() {
221 return Ok(Microseconds(since_start));
222 }
223 if since_start >= timeout {
224 return Err(Microseconds(since_start));
225 }
226 }
227 }
228}