1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
#![cfg_attr(not(feature = "std"), no_std)]
#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"),"/README.md"))]

#[cfg(feature = "critical-section")]
use critical_section::with;
#[cfg(not(feature = "critical-section"))]
fn with<R>(f: impl FnOnce(()) -> R) -> R {
    f(())
}

#[derive(Debug)]
pub enum DhtError<DeviceError> {
    /// Initial handshake with the sensor was unsuccessful. Make sure all physical connections are working, individual reads of the sensor are seperated by at least 2 seconds and the pin state is high while idle
    Handshake,
    /// Timeout while waiting for the sensor to respond
    Timeout(Microseconds),
    /// The checksum of the read data does not match with the provided checksum
    Checksum { correct: u8, actual: u8 },
    /// While setting the pin state the DeviceError occured
    DeviceError(DeviceError),
}

impl<DeviceError> core::fmt::Display for DhtError<DeviceError>
where
    DeviceError: core::fmt::Display,
{
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            DhtError::Handshake => write!(f, "Inital handshake failed!"),
            DhtError::Timeout(us) => write!(
                f,
                "Timeout while waiting for level change after {} microseconds",
                us.0
            ),
            DhtError::Checksum { correct, actual } => write!(
                f,
                "Checksum validation failed. Correct: {correct}, Actual: {actual}"
            ),
            DhtError::DeviceError(device_error) => write!(f, "DeviceError: {device_error}"),
        }
    }
}

#[cfg(feature = "std")]
impl<DeviceError> std::error::Error for DhtError<DeviceError> where DeviceError: std::error::Error {}

impl<DeviceError> From<DeviceError> for DhtError<DeviceError> {
    fn from(value: DeviceError) -> Self {
        Self::DeviceError(value)
    }
}

/// Represents a GPIO pin capable of reading and setting the voltage level
pub trait IOPin {
    type DeviceError;
    fn set_low(&mut self) -> Result<(), Self::DeviceError>;
    fn set_high(&mut self) -> Result<(), Self::DeviceError>;
    fn is_low(&self) -> bool;
    fn is_high(&self) -> bool;
}

/// Represents a number of microseconds.
/// Simple Newtype to attach meaning to the contained primitive.
/// The std::duration::Duration which could also be used here is a much larger type in order to accomodate much
/// bigger time spans, which may impact performance, code size and stack usage.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Microseconds(pub u32);

/// Represents a timer with microsecond resolution
pub trait MicroTimer {
    /// Return an instance in time as a number of microseconds since some fixed point (normally boot or start of the timer).
    /// The implementation is allowed to wrap during a read from the sensor.
    fn now(&self) -> Microseconds;
}

/// Represents a DHT22 sensor connected to a pin.
pub struct Dht22<Pin, Timer>
where
    Pin: IOPin,
    Timer: MicroTimer,
{
    pin: Pin,
    timer: Timer,
}

/// A valid reading from the DHT22 sensor
pub struct SensorReading {
    pub humidity: f32,
    pub temperature: f32,
}

impl<Pin, Timer> Dht22<Pin, Timer>
where
    Pin: IOPin,
    <Pin as IOPin>::DeviceError: core::fmt::Debug,
    Timer: MicroTimer,
{
    /// Construct a new representation of the DHT22 sensor.
    /// Construction is cheap as long as the pin and clock are cheap to move.
    pub fn new(pin: Pin, clock: Timer) -> Self {
        Self { pin, timer: clock }
    }
    /// Attempt one read from the DHT22 sensor.
    /// Between subsequent reads from the same sensor at least 2 seconds should pass to avoid erratic readings.
    /// Reading to early after startup may also result in failure to read.
    pub fn read(&mut self) -> Result<SensorReading, DhtError<Pin::DeviceError>> {
        const RESPONSE_BITS: usize = 40;
        // Each bit is indicated by the two edges of the HIGH level (up, down).
        // In addition the initial down edge from the get-ready HIGH state is recorded.
        let mut cycles: [u32; 2 * RESPONSE_BITS + 1] = [0; 2 * RESPONSE_BITS + 1];
        let waiter = Waiter { timer: &self.timer };
        // Disable interrupts while interacting with the sensor so they don't mess up the timings
        with(|_guard| {
            // Initial handshake
            self.pin.set_low()?;
            let _ = waiter.wait_for(|| false, 1200);
            self.pin.set_high()?;

            // Wait for DHT22 to acknowledge the handshake with low
            if waiter.wait_for(|| self.pin.is_low(), 100).is_err() {
                return Err(DhtError::Handshake);
            }

            // Wait for low to end
            if waiter.wait_for(|| self.pin.is_high(), 100).is_err() {
                return Err(DhtError::Handshake);
            }
            // Data transfer started. Each bit starts with 50us low and than ~27us high for a 0 or ~70us high for a 1.
            // The pin should stay high for about 80us before transmission starts, we don't actually care about the precise timing of this duration.
            // To be precise just record time of edges and process later.
            let mut is_high = true;
            for duration in &mut cycles {
                *duration = waiter
                    .wait_for(|| is_high != self.pin.is_high(), 100)
                    .map(|microsecond| microsecond.0)
                    .map_err(DhtError::Timeout)?;

                is_high = !is_high;
            }
            // Data transfer ended
            Ok(())
        })
        .map_err(|err| {
            // Reset pin to high, which is the idle state of the dht22
            // Ignore any error that might occur here so the user sees the first error that occured
            let _ = self.pin.set_high();
            err
        })?;

        let mut bytes: [u8; 5] = [0; 5];
        // Ignore first element, because the time until data transmission starts is not important
        for (idx, _) in cycles[1..]
            // Group the durations of the low and high voltage for each bit
            .chunks_exact(2)
            // Map the duration of the high voltage to a 0 or 1
            .map(|pair| {
                let cycles_low = pair[0];
                let cycles_high = pair[1];
                // use the low duration as a reference to be robust against jitter
                cycles_low < cycles_high
            })
            // Count with index to know where to shift the bit
            .enumerate()
            // Ignore 0-bits as that is already their initial value
            .filter(|(_, bit)| *bit)
        {
            let byte_idx = idx / 8;
            let bit_idx = idx % 8;
            bytes[byte_idx] |= 1 << (7 - bit_idx);
        }
        // Verify the checksum in the last byte
        let correct = bytes[4];
        let actual = bytes[0]
            .wrapping_add(bytes[1])
            .wrapping_add(bytes[2])
            .wrapping_add(bytes[3]);
        if actual != correct {
            return Err(DhtError::Checksum { actual, correct });
        }
        let humidity = (((bytes[0] as u32) << 8 | bytes[1] as u32) as f32) / 10.;
        // The MSB of the 16 temperature bits indicates negative temperatures
        let is_negative = (bytes[2] >> 7) != 0;
        bytes[2] &= 0b0111_1111;
        let temperature = (((bytes[2] as u32) << 8 | bytes[3] as u32) as f32) / 10.;
        let temperature = if is_negative {
            -1. * temperature
        } else {
            temperature
        };
        Ok(SensorReading {
            humidity,
            temperature,
        })
    }
}

struct Waiter<'timer, Timer>
where
    Timer: MicroTimer,
{
    timer: &'timer Timer,
}
impl<'timer, Timer> Waiter<'timer, Timer>
where
    Timer: MicroTimer,
{
    #[inline(always)]
    fn wait_for(
        &self,
        condition: impl Fn() -> bool,
        timeout: u32,
    ) -> Result<Microseconds, Microseconds> {
        let start = self.timer.now();
        loop {
            // Using wrapping arithmetic on unsigned integers, overflow of the timer can be
            // exploited to count over the whole representable range of the integer type regardless of initial value.
            // For example for a u8:
            // 10 - 230 = 36
            let since_start = self.timer.now().0.wrapping_sub(start.0);
            if condition() {
                return Ok(Microseconds(since_start));
            }
            if since_start >= timeout {
                return Err(Microseconds(since_start));
            }
        }
    }
}