#![doc = include_str!("../README.md")]
#![cfg_attr(not(feature = "std"), no_std)]
use core::fmt;
use embedded_hal::{
delay::DelayUs,
digital::{InputPin, OutputPin, PinState},
};
#[derive(Debug, Clone, Copy)]
pub struct Reading {
humidity: f32,
temperature: f32,
}
impl Reading {
pub fn humidity(&self) -> f32 {
self.humidity
}
pub fn temperature(&self) -> f32 {
self.temperature
}
}
#[derive(Debug, Clone)]
pub enum DhtError<HE> {
NotPresent,
ChecksumMismatch(u8, u8),
InvalidData,
Timeout,
PinError(HE),
}
impl<HE> From<HE> for DhtError<HE> {
fn from(error: HE) -> Self {
DhtError::PinError(error)
}
}
impl<HE: fmt::Debug> fmt::Display for DhtError<HE> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use DhtError::*;
match self {
NotPresent => write!(f, "DHT device not found"),
ChecksumMismatch(expected, calculated) => write!(
f,
"Data read was corrupt (expected checksum {:x}, calculated {:x})",
expected, calculated
),
InvalidData => f.write_str("Received data is out of range"),
Timeout => f.write_str("Timed out waiting for a read"),
PinError(err) => write!(f, "HAL pin error: {:?}", err),
}
}
}
#[cfg(feature = "std")]
impl<HE: fmt::Debug> std::error::Error for DhtError<HE> {}
pub trait InterruptControl {
fn enable_interrupts(&mut self);
fn disable_interrupts(&mut self);
}
pub struct NoopInterruptControl;
impl InterruptControl for NoopInterruptControl {
fn enable_interrupts(&mut self) {}
fn disable_interrupts(&mut self) {}
}
pub trait DhtSensor<HE> {
fn read(&mut self) -> Result<Reading, DhtError<HE>>;
}
#[doc(hidden)]
pub struct Dht<
HE,
ID: InterruptControl,
D: DelayUs,
P: InputPin<Error = HE> + OutputPin<Error = HE>,
> {
interrupt_disabler: ID,
delay: D,
pin: P,
}
impl<HE, ID: InterruptControl, D: DelayUs, P: InputPin<Error = HE> + OutputPin<Error = HE>>
Dht<HE, ID, D, P>
{
fn new(interrupt_disabler: ID, delay: D, pin: P) -> Self {
Self {
interrupt_disabler,
delay,
pin,
}
}
fn read(&mut self, parse_data: fn(&[u8]) -> (f32, f32)) -> Result<Reading, DhtError<HE>> {
self.interrupt_disabler.disable_interrupts();
let res = self.read_uninterruptible(parse_data);
self.interrupt_disabler.enable_interrupts();
res
}
fn read_uninterruptible(
&mut self,
parse_data: fn(&[u8]) -> (f32, f32),
) -> Result<Reading, DhtError<HE>> {
let mut buf: [u8; 5] = [0; 5];
self.pin.set_low()?;
self.delay.delay_us(3000);
self.pin.set_high()?;
self.delay.delay_us(25);
self.wait_for_level(PinState::High, 85, DhtError::NotPresent)?;
self.wait_for_level(PinState::Low, 85, DhtError::NotPresent)?;
for bit in 0..40 {
self.wait_for_level(PinState::High, 55, DhtError::Timeout)?;
let elapsed = self.wait_for_level(PinState::Low, 70, DhtError::Timeout)?;
if elapsed > 30 {
let byte = bit / 8;
let shift = 7 - bit % 8;
buf[byte] |= 1 << shift;
}
}
let checksum = (buf[0..=3]
.iter()
.fold(0u16, |accum, next| accum + *next as u16)
& 0xff) as u8;
if buf[4] == checksum {
let (humidity, temperature) = parse_data(&buf);
if !(0.0..=100.0).contains(&humidity) {
Err(DhtError::InvalidData)
} else {
Ok(Reading {
humidity,
temperature,
})
}
} else {
Err(DhtError::ChecksumMismatch(buf[4], checksum))
}
}
fn wait_for_level(
&mut self,
level: PinState,
timeout_us: u32,
on_timeout: DhtError<HE>,
) -> Result<u32, DhtError<HE>> {
let tester = || match level {
PinState::High => self.pin.is_high(),
PinState::Low => self.pin.is_low(),
};
for elapsed in 0..=timeout_us {
if tester()? {
return Ok(elapsed);
}
self.delay.delay_us(1);
}
Err(on_timeout)
}
}
pub struct Dht11<
HE,
ID: InterruptControl,
D: DelayUs,
P: InputPin<Error = HE> + OutputPin<Error = HE>,
> {
dht: Dht<HE, ID, D, P>,
}
impl<HE, ID: InterruptControl, D: DelayUs, P: InputPin<Error = HE> + OutputPin<Error = HE>>
Dht11<HE, ID, D, P>
{
pub fn new(interrupt_disabler: ID, delay: D, pin: P) -> Self {
Self {
dht: Dht::new(interrupt_disabler, delay, pin),
}
}
fn parse_data(buf: &[u8]) -> (f32, f32) {
(buf[0] as f32, buf[2] as f32)
}
}
impl<HE, ID: InterruptControl, D: DelayUs, P: InputPin<Error = HE> + OutputPin<Error = HE>>
DhtSensor<HE> for Dht11<HE, ID, D, P>
{
fn read(&mut self) -> Result<Reading, DhtError<HE>> {
self.dht.read(Dht11::<HE, ID, D, P>::parse_data)
}
}
pub struct Dht22<
HE,
ID: InterruptControl,
D: DelayUs,
P: InputPin<Error = HE> + OutputPin<Error = HE>,
> {
dht: Dht<HE, ID, D, P>,
}
impl<HE, ID: InterruptControl, D: DelayUs, P: InputPin<Error = HE> + OutputPin<Error = HE>>
Dht22<HE, ID, D, P>
{
pub fn new(interrupt_disabler: ID, delay: D, pin: P) -> Self {
Self {
dht: Dht::new(interrupt_disabler, delay, pin),
}
}
fn parse_data(buf: &[u8]) -> (f32, f32) {
let humidity = (((buf[0] as u16) << 8) | buf[1] as u16) as f32 / 10.0;
let mut temperature = ((((buf[2] & 0x7f) as u16) << 8) | buf[3] as u16) as f32 / 10.0;
if buf[2] & 0x80 != 0 {
temperature = -temperature;
}
(humidity, temperature)
}
}
impl<HE, ID: InterruptControl, D: DelayUs, P: InputPin<Error = HE> + OutputPin<Error = HE>>
DhtSensor<HE> for Dht22<HE, ID, D, P>
{
fn read(&mut self) -> Result<Reading, DhtError<HE>> {
self.dht.read(Dht22::<HE, ID, D, P>::parse_data)
}
}