#![allow(unused)]
#![deny(unsafe_code)]
#![cfg_attr(not(test), no_std)]
use embedded_hal::blocking::{delay::DelayMs,
i2c::{Read, Write}};
mod constants
{
pub const I2C_ADDR: u8 = 0x40;
pub const I2C_COMMAND_TEMPERATURE: u8 = 0xf3;
pub const I2C_COMMAND_HUMIDITY: u8 = 0xf5;
pub const SOFT_RESET: u8 = 0xfe;
pub const TEMP_DELAY_MS: u16 = 50;
pub const HUMI_DELAY_MS: u16 = 16;
pub const RESET_DELAY_MS: u16 = 15;
pub const POWERUP_DELAY_MS: u16 = 15;
pub const CRC8_POLY: u32 = 0b100110001;
}
#[derive(Debug)]
pub struct Sensor<I2C>
{
i2c: I2C,
addr: u8,
}
#[derive(Copy, Clone, Debug)]
#[repr(u8)]
enum Command
{
Reset = constants::SOFT_RESET,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct RawMeasurement
{
lo: u8,
hi: u8,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Measurement<Output, const D: u16, const C: u8>(Output);
pub type Humidity = Measurement<
f32,
{ constants::HUMI_DELAY_MS },
{ constants::I2C_COMMAND_HUMIDITY },
>;
pub type Temperature = Measurement<
f32,
{ constants::TEMP_DELAY_MS },
{ constants::I2C_COMMAND_TEMPERATURE },
>;
impl<Output: Copy, const D: u16, const C: u8> Measurement<Output, D, C>
{
const fn i2n_command(&self) -> u8 { C }
const fn delay_ms(&self) -> u16 { D }
pub const fn value(&self) -> Output { self.0 }
}
impl From<RawMeasurement> for u16
{
fn from(RawMeasurement { lo, hi }: RawMeasurement) -> Self
{
(hi as u16) << 8 | (lo as u16 & 0xfc_u16)
}
}
impl From<RawMeasurement> for Temperature
{
fn from(raw: RawMeasurement) -> Self
{
let sigout: u16 = raw.into();
Self(0.002681274_f32 * (sigout as f32) - 46.85_f32)
}
}
impl From<RawMeasurement> for Humidity
{
fn from(raw: RawMeasurement) -> Self
{
let sigout: u16 = raw.into();
Self(0.001907349_f32 * (sigout as f32) - 6.0_f32)
}
}
impl From<Command> for u8
{
fn from(cmd: Command) -> Self
{
match cmd {
Command::Reset => constants::SOFT_RESET,
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum Error<I2cError>
{
I2c(I2cError),
Crc,
}
impl<I2C, E> Sensor<I2C>
where I2C: Read<Error = E> + Write<Error = E>
{
pub fn new(
i2c: I2C,
delay: Option<&mut impl DelayMs<u16>>,
) -> Result<Self, Error<E>>
{
let mut htu = Self { i2c, addr: constants::I2C_ADDR };
if let Some(delay) = delay {
delay.delay_ms(constants::POWERUP_DELAY_MS);
htu.reset(delay)?;
}
Ok(htu)
}
pub fn with_address(
i2c: I2C,
delay: Option<&mut impl DelayMs<u16>>,
addr: u8,
) -> Result<Self, Error<E>>
{
let mut htu = Self { i2c, addr };
if let Some(delay) = delay {
delay.delay_ms(constants::POWERUP_DELAY_MS);
htu.reset(delay)?;
}
Ok(htu)
}
fn send_command<C: Into<u8>>(&mut self, cmd: C) -> Result<(), Error<E>>
{
let cmd: u8 = cmd.into();
self.i2c.write(self.addr, [cmd].as_slice()).map_err(Error::I2c)
}
fn start_measurement<const C: u8>(&mut self) -> Result<(), Error<E>>
{
self.send_command(C)
}
fn measurement_result<M>(&mut self) -> Result<M, Error<E>>
where M: From<RawMeasurement>
{
let raw = self.raw_measurement_result()?;
Ok(raw.into())
}
fn raw_measurement_result(&mut self) -> Result<RawMeasurement, Error<E>>
{
self.read_value_checked().map(|(hi, lo)| RawMeasurement { hi, lo })
}
fn read_value_checked(&mut self) -> Result<(u8, u8), Error<E>>
{
let mut buf = [0; 3];
self.i2c.read(self.addr, &mut buf).map_err(Error::I2c)?;
if calc_crc(buf[0], buf[1]) != buf[2] {
Err(Error::Crc)
} else {
Ok((buf[0], buf[1]))
}
}
pub fn measure_temperature(
&mut self,
delay: &mut impl DelayMs<u16>,
) -> Result<Temperature, Error<E>>
{
self.measure::<f32, { constants::TEMP_DELAY_MS }, {constants::I2C_COMMAND_TEMPERATURE}>(delay)
}
pub fn measure_humidity(
&mut self,
delay: &mut impl DelayMs<u16>,
) -> Result<Humidity, Error<E>>
{
self.measure::<f32, { constants::HUMI_DELAY_MS }, {constants::I2C_COMMAND_HUMIDITY}>(delay)
}
pub fn reset(
&mut self,
delay: &mut impl DelayMs<u16>,
) -> Result<(), Error<E>>
{
self.send_command(Command::Reset)?;
delay.delay_ms(constants::RESET_DELAY_MS);
Ok(())
}
#[inline]
fn measure<Output, const D: u16, const C: u8>(
&mut self,
delay: &mut impl DelayMs<u16>,
) -> Result<Measurement<Output, D, C>, Error<E>>
where
Measurement<Output, D, C>: From<RawMeasurement>,
{
self.start_measurement::<C>()?;
delay.delay_ms(D);
self.measurement_result::<Measurement<Output, D, C>>()
}
}
#[inline]
const fn calc_crc(hi: u8, lo: u8) -> u8
{
let mut crc: u32 = (hi as u32) << 16 | (lo as u32) << 8;
let mut div: u32 = constants::CRC8_POLY << 15;
let mut b = 24usize;
while b != 8 {
b -= 1;
if ((crc & (1 << b)) != 0) {
crc ^= div;
}
div >>= 1;
}
(crc & 0xff) as u8
}
#[cfg(test)]
mod tests
{
use super::*;
#[test]
fn crc()
{
assert_eq!(calc_crc(0b00000000u8, 0b11011100u8), 0b01111001u8);
assert_eq!(calc_crc(0b01101000u8, 0b00111010u8), 0b01111100u8);
assert_eq!(calc_crc(0b01001110u8, 0b10000101u8), 0b01101011u8);
}
#[test]
fn humidity()
{
use RawMeasurement as RM;
assert_eq!(
Humidity::from(RM { lo: 0x80, hi: 0x7c }),
Measurement(54.791027)
);
assert_eq!(
Humidity::from(RM { lo: 0x85, hi: 0x4e }),
Measurement(32.337715)
);
}
#[test]
fn temp()
{
use RawMeasurement as RM;
assert_eq!(
Temperature::from(RM { lo: 0x3a, hi: 0x68 }),
Measurement(24.686394)
);
}
}