#![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> {
Handshake,
Timeout(Microseconds),
Checksum { correct: u8, actual: u8 },
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)
}
}
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;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Microseconds(pub u32);
pub trait MicroTimer {
fn now(&self) -> Microseconds;
}
pub struct Dht22<Pin, Timer>
where
Pin: IOPin,
Timer: MicroTimer,
{
pin: Pin,
timer: Timer,
}
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,
{
pub fn new(pin: Pin, clock: Timer) -> Self {
Self { pin, timer: clock }
}
pub fn read(&mut self) -> Result<SensorReading, DhtError<Pin::DeviceError>> {
const RESPONSE_BITS: usize = 40;
let mut cycles: [u32; 2 * RESPONSE_BITS + 1] = [0; 2 * RESPONSE_BITS + 1];
let waiter = Waiter { timer: &self.timer };
with(|_guard| {
self.pin.set_low()?;
let _ = waiter.wait_for(|| false, 1200);
self.pin.set_high()?;
if waiter.wait_for(|| self.pin.is_low(), 100).is_err() {
return Err(DhtError::Handshake);
}
if waiter.wait_for(|| self.pin.is_high(), 100).is_err() {
return Err(DhtError::Handshake);
}
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;
}
Ok(())
})
.map_err(|err| {
let _ = self.pin.set_high();
err
})?;
let mut bytes: [u8; 5] = [0; 5];
for (idx, _) in cycles[1..]
.chunks_exact(2)
.map(|pair| {
let cycles_low = pair[0];
let cycles_high = pair[1];
cycles_low < cycles_high
})
.enumerate()
.filter(|(_, bit)| *bit)
{
let byte_idx = idx / 8;
let bit_idx = idx % 8;
bytes[byte_idx] |= 1 << (7 - bit_idx);
}
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.;
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 {
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));
}
}
}
}