#![cfg_attr(not(feature = "std"), no_std)]
pub mod error;
use core::{
convert::TryFrom,
ops::{Deref, DerefMut},
};
#[cfg(not(feature = "async"))]
use embedded_hal::i2c::I2c;
#[cfg(feature = "async")]
use embedded_hal_async::i2c::I2c as AsyncI2c;
use bitfield::bitfield;
use error::AirQualityConvError;
const ENS160_PART_ID_REG: u8 = 0x00;
const ENS160_OPMODE_REG: u8 = 0x10;
const ENS160_CONFIG_REG: u8 = 0x11;
#[allow(dead_code)]
const ENS160_COMMAND_REG: u8 = 0x12;
const ENS160_TEMP_IN_REG: u8 = 0x13;
#[allow(dead_code)]
const ENS160_RH_IN_REG: u8 = 0x15;
const ENS160_DATA_STATUS_REG: u8 = 0x20;
const ENS160_DATA_AQI_REG: u8 = 0x21;
const ENS160_DATA_TVOC_REG: u8 = 0x22;
const ENS160_DATA_ECO2_REG: u8 = 0x24;
const ENS160_DATA_T_REG: u8 = 0x30;
#[allow(dead_code)]
const ENS160_DATA_RH_REG: u8 = 0x32;
#[allow(dead_code)]
const ENS160_DATA_MISR_REG: u8 = 0x38;
#[allow(dead_code)]
const ENS160_GPR_WRITE_REG: u8 = 0x40;
#[allow(dead_code)]
const ENS160_GPR_READ_REG: u8 = 0x48;
pub struct Ens160<I2C> {
i2c: I2C,
address: u8,
}
impl<I2C> Ens160<I2C> {
pub fn new(i2c: I2C, address: u8) -> Self {
Self { i2c, address }
}
pub fn release(self) -> I2C {
self.i2c
}
}
#[maybe_async_cfg::maybe(
sync(
cfg(not(feature = "async")),
self = "Ens160",
idents(AsyncI2c(sync = "I2c"))
),
async(feature = "async", keep_self)
)]
impl<I2C, E> Ens160<I2C>
where
I2C: AsyncI2c<Error = E>,
{
pub async fn reset(&mut self) -> Result<(), E> {
self.write_register([ENS160_OPMODE_REG, OperationMode::Reset as u8])
.await
}
pub async fn idle(&mut self) -> Result<(), E> {
self.write_register([ENS160_OPMODE_REG, OperationMode::Idle as u8])
.await
}
pub async fn deep_sleep(&mut self) -> Result<(), E> {
self.write_register([ENS160_OPMODE_REG, OperationMode::Sleep as u8])
.await
}
pub async fn operational(&mut self) -> Result<(), E> {
self.write_register([ENS160_OPMODE_REG, OperationMode::Standard as u8])
.await
}
pub async fn clear_command(&mut self) -> Result<(), E> {
self.write_register([ENS160_COMMAND_REG, Command::Nop as u8])
.await?;
self.write_register([ENS160_COMMAND_REG, Command::Clear as u8])
.await?;
Ok(())
}
pub async fn part_id(&mut self) -> Result<u16, E> {
self.read_register::<2>(ENS160_PART_ID_REG)
.await
.map(u16::from_le_bytes)
}
pub async fn firmware_version(&mut self) -> Result<(u8, u8, u8), E> {
self.write_register([ENS160_COMMAND_REG, Command::GetAppVersion as u8])
.await?;
let buffer = self.read_register::<3>(ENS160_GPR_READ_REG).await?;
Ok((buffer[0], buffer[1], buffer[2]))
}
pub async fn status(&mut self) -> Result<Status, E> {
self.read_register::<1>(ENS160_DATA_STATUS_REG)
.await
.map(|v| Status(v[0]))
}
pub async fn air_quality_index(&mut self) -> Result<AirQualityIndex, E> {
self.read_register::<1>(ENS160_DATA_AQI_REG)
.await
.map(|v| AirQualityIndex::from(v[0] & 0x07))
}
pub async fn tvoc(&mut self) -> Result<u16, E> {
self.read_register::<2>(ENS160_DATA_TVOC_REG)
.await
.map(u16::from_le_bytes)
}
pub async fn eco2(&mut self) -> Result<ECo2, E> {
self.read_register::<2>(ENS160_DATA_ECO2_REG)
.await
.map(u16::from_le_bytes)
.map(ECo2::from)
}
pub async fn temp_and_hum(&mut self) -> Result<(i16, u16), E> {
let buffer = self.read_register::<4>(ENS160_DATA_T_REG).await?;
let temp = u16::from_le_bytes([buffer[0], buffer[1]]);
let rh = u16::from_le_bytes([buffer[2], buffer[3]]);
let temp = temp as i32 * 100 / 64 - 27315;
let hum = rh as u32 * 100 / 512;
Ok((temp as i16, hum as u16))
}
pub async fn set_temp(&mut self, ambient_temp: i16) -> Result<(), E> {
let temp = ((ambient_temp as i32 + 27315) * 64 / 100) as u16;
let temp = temp.to_le_bytes();
let tbuffer = [ENS160_TEMP_IN_REG, temp[0], temp[1]];
self.write_register(tbuffer).await
}
pub async fn set_hum(&mut self, relative_humidity: u16) -> Result<(), E> {
let rh = (relative_humidity as u32 * 512 / 100) as u16;
let rh = rh.to_le_bytes();
let hbuffer = [ENS160_RH_IN_REG, rh[0], rh[1]];
self.write_register(hbuffer).await
}
pub async fn set_interrupt_config(&mut self, config: InterruptConfig) -> Result<(), E> {
self.write_register([ENS160_CONFIG_REG, config.finish().0])
.await
}
async fn read_register<const N: usize>(&mut self, register: u8) -> Result<[u8; N], E> {
let mut write_buffer = [0u8; 1];
write_buffer[0] = register;
let mut buffer = [0u8; N];
self.i2c
.write_read(self.address, &write_buffer, &mut buffer)
.await?;
Ok(buffer)
}
async fn write_register<const N: usize>(&mut self, buffer: [u8; N]) -> Result<(), E> {
self.i2c.write(self.address, &buffer).await
}
}
#[repr(u8)]
enum Command {
Nop = 0x00,
GetAppVersion = 0x0E,
Clear = 0xCC,
}
#[repr(u8)]
enum OperationMode {
Sleep = 0x00,
Idle = 0x01,
Standard = 0x02,
Reset = 0xF0,
}
bitfield! {
pub struct Status(u8);
impl Debug;
pub bool, running_normally, _: 7;
pub bool, error, _: 6;
pub into Validity, validity_flag, _: 3,2;
pub bool, data_is_ready, _: 1;
pub bool, new_data_in_gpr, _: 0;
}
#[derive(Debug, Clone, Copy)]
pub enum Validity {
NormalOperation,
WarmupPhase,
InitStartupPhase,
InvalidOutput,
}
impl From<u8> for Validity {
fn from(v: u8) -> Self {
match v {
0b00 => Self::NormalOperation,
0b01 => Self::WarmupPhase,
0b10 => Self::InitStartupPhase,
0b11 => Self::InvalidOutput,
_ => unreachable!(),
}
}
}
bitfield! {
#[derive(Default)]
struct InterruptRegister(u8);
impl Debug;
from into InterruptState, _, set_interrupt_state: 6, 6;
from into PinMode, ddfsdf, set_pin_mode: 5, 5;
bool, _, set_on_data_in_gpr_register: 3;
bool, _, set_on_data_in_data_register: 1;
bool, _, set_enabled: 0;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PinMode {
OpenDrain,
PushPull,
}
impl From<PinMode> for u8 {
fn from(m: PinMode) -> u8 {
match m {
PinMode::OpenDrain => 0x0,
PinMode::PushPull => 0x1,
}
}
}
impl From<u8> for PinMode {
fn from(b: u8) -> Self {
match b {
0x1 => Self::PushPull,
_ => Self::OpenDrain,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InterruptState {
ActiveLow,
ActiveHigh,
}
impl From<InterruptState> for u8 {
fn from(i: InterruptState) -> u8 {
match i {
InterruptState::ActiveHigh => 0x1,
InterruptState::ActiveLow => 0x0,
}
}
}
impl From<u8> for InterruptState {
fn from(b: u8) -> Self {
match b {
0x1 => Self::ActiveHigh,
_ => Self::ActiveLow,
}
}
}
#[derive(Debug, Default)]
pub struct InterruptConfig(InterruptRegister);
impl InterruptConfig {
pub fn set_pin_interrupt_state(mut self, state: InterruptState) -> Self {
self.0.set_interrupt_state(state);
self
}
pub fn enable_for_measure_data_is_ready(mut self) -> Self {
self.0.set_enabled(true);
self.0.set_on_data_in_data_register(true);
self
}
pub fn enable_for_data_in_read_register(mut self) -> Self {
self.0.set_enabled(true);
self.0.set_on_data_in_gpr_register(true);
self
}
pub fn set_pin_mode(mut self, mode: PinMode) -> Self {
self.0.set_pin_mode(mode);
self
}
fn finish(self) -> InterruptRegister {
self.0
}
}
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
pub enum AirQualityIndex {
Excellent = 1,
Good = 2,
Moderate = 3,
Poor = 4,
Unhealthy = 5,
}
impl From<u8> for AirQualityIndex {
fn from(i: u8) -> Self {
match i {
1 => Self::Excellent,
2 => Self::Good,
3 => Self::Moderate,
4 => Self::Poor,
5 => Self::Unhealthy,
_ => Self::Unhealthy,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct ECo2(u16);
impl From<u16> for ECo2 {
fn from(v: u16) -> Self {
Self(v)
}
}
impl TryFrom<ECo2> for AirQualityIndex {
type Error = AirQualityConvError;
fn try_from(e: ECo2) -> Result<Self, Self::Error> {
let value = e.0;
match value {
400..=599 => Ok(Self::Excellent),
600..=799 => Ok(Self::Good),
800..=999 => Ok(Self::Moderate),
1000..=1499 => Ok(Self::Poor),
1500..=u16::MAX => Ok(Self::Unhealthy),
_ => Err(AirQualityConvError(value)),
}
}
}
impl Deref for ECo2 {
type Target = u16;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ECo2 {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
#[cfg(test)]
mod test {
use crate::{InterruptConfig, PinMode, Status, Validity};
#[test]
fn test_status_register_layout() {
let status = Status(0b00000001);
assert!(status.new_data_in_gpr());
let status = Status(0b10000100);
assert!(status.running_normally());
assert!(matches!(status.validity_flag(), Validity::WarmupPhase));
let status = Status(0b00001110);
assert!(status.data_is_ready());
assert!(matches!(status.validity_flag(), Validity::InvalidOutput))
}
#[test]
fn test_interrupt_config() {
let config = InterruptConfig::default()
.enable_for_measure_data_is_ready()
.set_pin_mode(PinMode::PushPull)
.finish();
assert_eq!(config.0, 0b00100011)
}
#[test]
fn test_byte_order() {
let b: u16 = 0x10;
assert_eq!(b.to_be_bytes(), [0x0, 0x10])
}
}