#![deny(unsafe_code, missing_docs)]
#![cfg_attr(not(test), no_std)]
mod crc;
mod types;
use core::marker::PhantomData;
use embedded_hal::{
delay::DelayNs,
i2c::{self, I2c, SevenBitAddress},
};
use crc::crc8;
pub use types::*;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum MeasurementOrder {
TemperatureFirst,
HumidityFirst,
}
use MeasurementOrder::*;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum PowerMode {
NormalMode,
LowPower,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Error<E: i2c::Error> {
I2c(E),
Crc,
}
impl<E> From<E> for Error<E>
where
E: i2c::Error,
{
fn from(e: E) -> Self {
Error::I2c(e)
}
}
#[derive(Debug, Copy, Clone)]
enum Command {
Sleep,
WakeUp,
Measure {
power_mode: PowerMode,
order: MeasurementOrder,
},
SoftwareReset,
ReadIdRegister,
}
impl Command {
fn as_bytes(self) -> [u8; 2] {
match self {
Command::Sleep => [0xB0, 0x98],
Command::WakeUp => [0x35, 0x17],
Command::Measure {
power_mode: PowerMode::NormalMode,
order: TemperatureFirst,
} => [0x78, 0x66],
Command::Measure {
power_mode: PowerMode::NormalMode,
order: HumidityFirst,
} => [0x58, 0xE0],
Command::Measure {
power_mode: PowerMode::LowPower,
order: TemperatureFirst,
} => [0x60, 0x9C],
Command::Measure {
power_mode: PowerMode::LowPower,
order: HumidityFirst,
} => [0x40, 0x1A],
Command::ReadIdRegister => [0xEF, 0xC8],
Command::SoftwareReset => [0x80, 0x5D],
}
}
}
pub trait MeasurementDuration {
fn max_measurement_duration(mode: PowerMode) -> u32;
}
pub mod sensor_class {
pub struct Sht1Gen;
pub struct Sht2Gen;
pub struct ShtGeneric;
}
pub trait ShtSensor {}
impl ShtSensor for sensor_class::Sht1Gen {}
impl ShtSensor for sensor_class::Sht2Gen {}
impl ShtSensor for sensor_class::ShtGeneric {}
#[derive(Debug, Default)]
pub struct ShtCx<S: ShtSensor, I2C> {
sensor: PhantomData<S>,
i2c: I2C,
address: u8,
}
pub type ShtC1<I2C> = ShtCx<sensor_class::Sht1Gen, I2C>;
pub fn shtc1<I2C>(i2c: I2C) -> ShtC1<I2C> {
ShtCx {
sensor: PhantomData,
i2c,
address: 0x70,
}
}
pub type ShtC3<I2C> = ShtCx<sensor_class::Sht2Gen, I2C>;
pub fn shtc3<I2C>(i2c: I2C) -> ShtC3<I2C> {
ShtCx {
sensor: PhantomData,
i2c,
address: 0x70,
}
}
pub type ShtW2<I2C> = ShtCx<sensor_class::Sht1Gen, I2C>;
pub fn shtw2<I2C>(i2c: I2C, address: u8) -> ShtW2<I2C> {
ShtCx {
sensor: PhantomData,
i2c,
address,
}
}
pub fn generic<I2C>(i2c: I2C, address: u8) -> ShtCx<sensor_class::ShtGeneric, I2C> {
ShtCx {
sensor: PhantomData,
i2c,
address,
}
}
impl MeasurementDuration for sensor_class::Sht1Gen {
fn max_measurement_duration(mode: PowerMode) -> u32 {
match mode {
PowerMode::NormalMode => 14400,
PowerMode::LowPower => 940,
}
}
}
impl MeasurementDuration for sensor_class::Sht2Gen {
fn max_measurement_duration(mode: PowerMode) -> u32 {
match mode {
PowerMode::NormalMode => 12100,
PowerMode::LowPower => 800,
}
}
}
impl MeasurementDuration for sensor_class::ShtGeneric {
fn max_measurement_duration(mode: PowerMode) -> u32 {
match mode {
PowerMode::NormalMode => 14400,
PowerMode::LowPower => 940,
}
}
}
#[inline(always)]
pub fn max_measurement_duration<S, I2C>(_: &ShtCx<S, I2C>, mode: PowerMode) -> u32
where
S: ShtSensor + MeasurementDuration,
{
S::max_measurement_duration(mode)
}
impl<S, I2C> ShtCx<S, I2C>
where
S: ShtSensor,
I2C: I2c<SevenBitAddress>,
{
pub fn destroy(self) -> I2C {
self.i2c
}
fn send_command(&mut self, command: Command) -> Result<(), Error<I2C::Error>> {
self.i2c
.write(self.address, &command.as_bytes())
.map_err(Error::I2c)
}
fn validate_crc(&self, buf: &[u8]) -> Result<(), Error<I2C::Error>> {
for chunk in buf.chunks(3) {
if chunk.len() == 3 && crc8(&[chunk[0], chunk[1]]) != chunk[2] {
return Err(Error::Crc);
}
}
Ok(())
}
fn read_with_crc(&mut self, buf: &mut [u8]) -> Result<(), Error<I2C::Error>> {
self.i2c.read(self.address, buf).map_err(Error::I2c)?;
self.validate_crc(buf)
}
pub fn raw_id_register(&mut self) -> Result<u16, Error<I2C::Error>> {
self.send_command(Command::ReadIdRegister)?;
let mut buf = [0; 3];
self.read_with_crc(&mut buf)?;
Ok(u16::from_be_bytes([buf[0], buf[1]]))
}
pub fn device_identifier(&mut self) -> Result<u8, Error<I2C::Error>> {
let ident = self.raw_id_register()?;
let lsb = (ident & 0b0011_1111) as u8;
let msb = ((ident & 0b0000_1000_0000_0000) >> 5) as u8;
Ok(lsb | msb)
}
pub fn reset(&mut self, delay: &mut impl DelayNs) -> Result<(), Error<I2C::Error>> {
self.send_command(Command::SoftwareReset)?;
delay.delay_us(240_000);
Ok(())
}
}
impl<S, I2C> ShtCx<S, I2C>
where
S: ShtSensor,
I2C: I2c<SevenBitAddress>,
{
fn start_measure_partial(
&mut self,
power_mode: PowerMode,
order: MeasurementOrder,
) -> Result<(), Error<I2C::Error>> {
self.send_command(Command::Measure { power_mode, order })
}
pub fn start_measurement(&mut self, mode: PowerMode) -> Result<(), Error<I2C::Error>> {
self.start_measure_partial(mode, MeasurementOrder::TemperatureFirst)
}
pub fn start_temperature_measurement(
&mut self,
mode: PowerMode,
) -> Result<(), Error<I2C::Error>> {
self.start_measure_partial(mode, MeasurementOrder::TemperatureFirst)
}
pub fn start_humidity_measurement(&mut self, mode: PowerMode) -> Result<(), Error<I2C::Error>> {
self.start_measure_partial(mode, MeasurementOrder::HumidityFirst)
}
pub fn get_measurement_result(&mut self) -> Result<Measurement, Error<I2C::Error>> {
let raw = self.get_raw_measurement_result()?;
Ok(raw.into())
}
pub fn get_temperature_measurement_result(&mut self) -> Result<Temperature, Error<I2C::Error>> {
let raw = self.get_raw_partial_measurement_result()?;
Ok(Temperature::from_raw(raw))
}
pub fn get_humidity_measurement_result(&mut self) -> Result<Humidity, Error<I2C::Error>> {
let raw = self.get_raw_partial_measurement_result()?;
Ok(Humidity::from_raw(raw))
}
pub fn get_raw_measurement_result(&mut self) -> Result<RawMeasurement, Error<I2C::Error>> {
let mut buf = [0; 6];
self.read_with_crc(&mut buf)?;
Ok(RawMeasurement {
temperature: u16::from_be_bytes([buf[0], buf[1]]),
humidity: u16::from_be_bytes([buf[3], buf[4]]),
})
}
pub fn get_raw_partial_measurement_result(&mut self) -> Result<u16, Error<I2C::Error>> {
let mut buf = [0; 3];
self.read_with_crc(&mut buf)?;
Ok(u16::from_be_bytes([buf[0], buf[1]]))
}
}
impl<S, I2C> ShtCx<S, I2C>
where
S: ShtSensor + MeasurementDuration,
I2C: I2c<SevenBitAddress>,
{
pub fn wait_for_measurement(&mut self, mode: PowerMode, delay: &mut impl DelayNs) {
delay.delay_us(S::max_measurement_duration(mode));
}
pub fn measure(
&mut self,
mode: PowerMode,
delay: &mut impl DelayNs,
) -> Result<Measurement, Error<I2C::Error>> {
self.start_measurement(mode)?;
self.wait_for_measurement(mode, delay);
self.get_measurement_result()
}
pub fn measure_temperature(
&mut self,
mode: PowerMode,
delay: &mut impl DelayNs,
) -> Result<Temperature, Error<I2C::Error>> {
self.start_temperature_measurement(mode)?;
self.wait_for_measurement(mode, delay);
self.get_temperature_measurement_result()
}
pub fn measure_humidity(
&mut self,
mode: PowerMode,
delay: &mut impl DelayNs,
) -> Result<Humidity, Error<I2C::Error>> {
self.start_humidity_measurement(mode)?;
self.wait_for_measurement(mode, delay);
self.get_humidity_measurement_result()
}
}
pub trait LowPower<E: i2c::Error> {
const WAKEUP_TIME_US: u32;
fn sleep(&mut self) -> Result<(), Error<E>>;
fn start_wakeup(&mut self) -> Result<(), Error<E>>;
fn wakeup(&mut self, delay: &mut impl DelayNs) -> Result<(), Error<E>>;
}
macro_rules! impl_low_power {
($target:ty) => {
impl<I2C> LowPower<I2C::Error> for ShtCx<$target, I2C>
where
I2C: I2c<SevenBitAddress>,
I2C::Error: Into<Error<I2C::Error>>,
{
const WAKEUP_TIME_US: u32 = 240_u32;
fn sleep(&mut self) -> Result<(), Error<I2C::Error>> {
self.send_command(Command::Sleep)
}
fn start_wakeup(&mut self) -> Result<(), Error<I2C::Error>> {
self.send_command(Command::WakeUp)
}
fn wakeup(&mut self, delay: &mut impl DelayNs) -> Result<(), Error<I2C::Error>> {
self.start_wakeup()?;
delay.delay_us(Self::WAKEUP_TIME_US);
Ok(())
}
}
};
}
impl_low_power!(sensor_class::Sht2Gen);
impl_low_power!(sensor_class::ShtGeneric);
#[cfg(test)]
mod tests {
use super::*;
use embedded_hal::i2c::ErrorKind;
use embedded_hal_mock::eh1::{
delay::NoopDelay,
i2c::{Mock as I2cMock, Transaction},
};
const SHT_ADDR: u8 = 0x70;
mod core {
use super::*;
#[test]
fn send_command_error() {
let expectations =
[Transaction::write(SHT_ADDR, vec![0xef, 0xc8]).with_error(ErrorKind::Other)];
let mock = I2cMock::new(&expectations);
let mut sht = shtc1(mock);
let err = sht.send_command(Command::ReadIdRegister).unwrap_err();
assert_eq!(err, Error::I2c(ErrorKind::Other));
sht.destroy().done();
}
#[test]
fn validate_crc() {
let mock = I2cMock::new(&[]);
let sht = shtc3(mock);
sht.validate_crc(&[]).unwrap();
sht.validate_crc(&[0xbe]).unwrap();
sht.validate_crc(&[0xbe, 0xef]).unwrap();
sht.validate_crc(&[0xbe, 0xef, 0x92]).unwrap();
match sht.validate_crc(&[0xbe, 0xef, 0x91]) {
Err(Error::Crc) => {}
Err(_) => panic!("Invalid error: Must be Crc"),
Ok(_) => panic!("CRC check did not fail"),
}
sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0x92, 0x00, 0x00])
.unwrap();
match sht.validate_crc(&[0xbe, 0xef, 0x92, 0xbe, 0xef, 0xff, 0x00, 0x00]) {
Err(Error::Crc) => {}
Err(_) => panic!("Invalid error: Must be Crc"),
Ok(_) => panic!("CRC check did not fail"),
}
sht.destroy().done();
}
#[test]
fn read_with_crc() {
let mut buf = [0; 3];
let expectations = [Transaction::read(SHT_ADDR, vec![0xbe, 0xef, 0x92])];
let mock = I2cMock::new(&expectations);
let mut sht = shtc3(mock);
sht.read_with_crc(&mut buf).unwrap();
assert_eq!(buf, [0xbe, 0xef, 0x92]);
sht.destroy().done();
let expectations = [Transaction::read(SHT_ADDR, vec![0xbe, 0xef, 0x00])];
let mock = I2cMock::new(&expectations);
let mut sht = shtc3(mock);
match sht.read_with_crc(&mut buf) {
Err(Error::Crc) => {}
Err(_) => panic!("Invalid error: Must be Crc"),
Ok(_) => panic!("CRC check did not fail"),
}
assert_eq!(buf, [0xbe, 0xef, 0x00]); sht.destroy().done();
}
}
mod factory_functions {
use super::*;
#[test]
fn new_shtc1() {
let mock = I2cMock::new(&[]);
let sht = shtc1(mock);
assert_eq!(sht.address, 0x70);
sht.destroy().done();
}
#[test]
fn new_shtc3() {
let mock = I2cMock::new(&[]);
let sht = shtc3(mock);
assert_eq!(sht.address, 0x70);
sht.destroy().done();
}
#[test]
fn new_shtw2() {
let mock = I2cMock::new(&[]);
let sht = shtw2(mock, 0x42);
assert_eq!(sht.address, 0x42);
sht.destroy().done();
}
#[test]
fn new_generic() {
let mock = I2cMock::new(&[]);
let sht = generic(mock, 0x23);
assert_eq!(sht.address, 0x23);
sht.destroy().done();
}
}
mod device_info {
use super::*;
#[test]
fn raw_id_register() {
let msb = 0b00001000;
let lsb = 0b00000111;
let crc = crc8(&[msb, lsb]);
let expectations = [
Transaction::write(SHT_ADDR, vec![0xef, 0xc8]),
Transaction::read(SHT_ADDR, vec![msb, lsb, crc]),
];
let mock = I2cMock::new(&expectations);
let mut sht = shtc3(mock);
let val = sht.raw_id_register().unwrap();
assert_eq!(val, (msb as u16) << 8 | (lsb as u16));
sht.destroy().done();
}
#[test]
fn device_identifier() {
let msb = 0b00001000;
let lsb = 0b00000111;
let crc = crc8(&[msb, lsb]);
let expectations = [
Transaction::write(SHT_ADDR, vec![0xef, 0xc8]),
Transaction::read(SHT_ADDR, vec![msb, lsb, crc]),
];
let mock = I2cMock::new(&expectations);
let mut sht = shtc3(mock);
let ident = sht.device_identifier().unwrap();
assert_eq!(ident, 0b01000111);
sht.destroy().done();
}
}
mod measurements {
use super::*;
#[test]
fn measure_normal() {
let expectations = [
Transaction::write(SHT_ADDR, vec![0x78, 0x66]),
Transaction::read(
SHT_ADDR,
vec![
0b0110_0100,
0b1000_1011,
0b1100_0111,
0b1010_0001,
0b0011_0011,
0b0001_1100,
],
),
];
let mock = I2cMock::new(&expectations);
let mut sht = shtc1(mock);
let mut delay = NoopDelay;
let measurement = sht.measure(PowerMode::NormalMode, &mut delay).unwrap();
assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); assert_eq!(measurement.humidity.as_millipercent(), 62_968); sht.destroy().done();
}
#[test]
fn measure_low_power() {
let expectations = [
Transaction::write(SHT_ADDR, vec![0x60, 0x9C]),
Transaction::read(
SHT_ADDR,
vec![
0b0110_0100,
0b1000_1011,
0b1100_0111,
0b1010_0001,
0b0011_0011,
0b0001_1100,
],
),
];
let mock = I2cMock::new(&expectations);
let mut sht = shtc3(mock);
let mut delay = NoopDelay;
let measurement = sht.measure(PowerMode::LowPower, &mut delay).unwrap();
assert_eq!(measurement.temperature.as_millidegrees_celsius(), 23_730); assert_eq!(measurement.humidity.as_millipercent(), 62_968); sht.destroy().done();
}
#[test]
fn measure_temperature_only() {
let expectations = [
Transaction::write(SHT_ADDR, vec![0x78, 0x66]),
Transaction::read(SHT_ADDR, vec![0b0110_0100, 0b1000_1011, 0b1100_0111]),
];
let mock = I2cMock::new(&expectations);
let mut sht = shtc3(mock);
let mut delay = NoopDelay;
let temperature = sht
.measure_temperature(PowerMode::NormalMode, &mut delay)
.unwrap();
assert_eq!(temperature.as_millidegrees_celsius(), 23_730); sht.destroy().done();
}
#[test]
fn measure_humidity_only() {
let expectations = [
Transaction::write(SHT_ADDR, vec![0x58, 0xE0]),
Transaction::read(SHT_ADDR, vec![0b1010_0001, 0b0011_0011, 0b0001_1100]),
];
let mock = I2cMock::new(&expectations);
let mut sht = shtc3(mock);
let mut delay = NoopDelay;
let humidity = sht
.measure_humidity(PowerMode::NormalMode, &mut delay)
.unwrap();
assert_eq!(humidity.as_millipercent(), 62_968); sht.destroy().done();
}
#[test]
fn measure_write_error() {
let expectations =
[Transaction::write(SHT_ADDR, vec![0x60, 0x9C]).with_error(ErrorKind::Other)];
let mock = I2cMock::new(&expectations);
let mut sht = shtc3(mock);
let err = sht
.measure(PowerMode::LowPower, &mut NoopDelay)
.unwrap_err();
assert_eq!(err, Error::I2c(ErrorKind::Other));
sht.destroy().done();
}
}
mod power_management {
use super::*;
#[test]
fn sleep() {
let expectations = [Transaction::write(SHT_ADDR, vec![0xB0, 0x98])];
let mock = I2cMock::new(&expectations);
let mut sht = shtc3(mock);
sht.sleep().unwrap();
sht.destroy().done();
}
#[test]
fn wakeup() {
let expectations = [Transaction::write(SHT_ADDR, vec![0x35, 0x17])];
let mock = I2cMock::new(&expectations);
let mut sht = shtc3(mock);
sht.wakeup(&mut NoopDelay).unwrap();
sht.destroy().done();
}
#[test]
fn reset() {
let expectations = [Transaction::write(SHT_ADDR, vec![0x80, 0x5D])];
let mock = I2cMock::new(&expectations);
let mut sht = shtc3(mock);
sht.reset(&mut NoopDelay).unwrap();
sht.destroy().done();
}
}
mod max_measurement_duration {
use super::*;
#[test]
fn shortcut_function() {
let c1 = shtc1(I2cMock::new(&[]));
let c3 = shtc3(I2cMock::new(&[]));
assert_eq!(max_measurement_duration(&c1, PowerMode::NormalMode), 14400);
assert_eq!(max_measurement_duration(&c1, PowerMode::LowPower), 940);
assert_eq!(max_measurement_duration(&c3, PowerMode::NormalMode), 12100);
assert_eq!(max_measurement_duration(&c3, PowerMode::LowPower), 800);
c1.destroy().done();
c3.destroy().done();
}
}
}