#![cfg_attr(not(test), no_std)]
use embedded_hal as hal;
use hal::blocking::delay::DelayMs;
use hal::blocking::i2c::{Read, Write, WriteRead};
use sensirion_i2c::{crc8, i2c};
const SGPC3_PRODUCT_TYPE: u8 = 1;
const SGPC3_CMD_MEASURE_TEST_OK: u16 = 0xd400;
#[derive(Debug)]
pub enum Error<E> {
I2c(E),
Crc,
SelfTest,
}
impl<E, I2cWrite, I2cRead> From<i2c::Error<I2cWrite, I2cRead>> for Error<E>
where
I2cWrite: Write<Error = E>,
I2cRead: Read<Error = E>,
{
fn from(err: i2c::Error<I2cWrite, I2cRead>) -> Self {
match err {
i2c::Error::Crc => Error::Crc,
i2c::Error::I2cWrite(e) => Error::I2c(e),
i2c::Error::I2cRead(e) => Error::I2c(e),
}
}
}
#[derive(Debug, Copy, Clone)]
enum Command {
GetSerial,
GetFeatureSet,
SelfTest,
InitAirQuality0,
InitAirQuality64,
InitAirQualityContinuous,
MeasureAirQuality,
MeasureRaw,
GetAirQualityBaseline,
GetAirQualityInceptiveBaseline,
MeasureAirQualityRaw,
SetBaseline,
SetHumidity,
SetPowerMode,
}
impl Command {
fn as_tuple(self) -> (u16, u32) {
match self {
Command::GetSerial => (0x3682, 1), Command::GetFeatureSet => (0x202f, 1),
Command::SelfTest => (0x2032, 220),
Command::InitAirQuality0 => (0x2089, 10),
Command::InitAirQuality64 => (0x2003, 10),
Command::InitAirQualityContinuous => (0x20ae, 10),
Command::MeasureAirQuality => (0x2008, 50),
Command::MeasureRaw => (0x204d, 50),
Command::GetAirQualityBaseline => (0x2015, 10),
Command::GetAirQualityInceptiveBaseline => (0x20b3, 10),
Command::MeasureAirQualityRaw => (0x2046, 50),
Command::SetBaseline => (0x201e, 10),
Command::SetHumidity => (0x2061, 10),
Command::SetPowerMode => (0x209f, 10),
}
}
}
#[derive(Debug)]
pub struct FeatureSet {
pub product_type: u8,
pub product_featureset: u8,
}
fn calculate_absolute_humidity(t_rh: i32, t_mc: i32) -> u32 {
type FP = fixed::types::I16F16;
let t = FP::from_num(t_mc / 10) / 100; let rh = FP::from_num(t_rh / 10);
let prefix_constants = FP::from_bits(0x21e8);
let k = FP::from_bits(0x1112666); let m = FP::from_bits(0x119eb8); let t_n = FP::from_bits(0xf335c2);
let temp_components = cordic::exp(m * t / (t_n + t));
let abs_hum = prefix_constants * rh * temp_components / (k + t);
(abs_hum * 1000).to_num::<u32>()
}
#[derive(Debug, Default)]
pub struct Sgpc3<I2C, D> {
i2c: I2C,
address: u8,
delay: D,
}
impl<I2C, D, E> Sgpc3<I2C, D>
where
I2C: Read<Error = E> + Write<Error = E> + WriteRead<Error = E>,
D: DelayMs<u32>,
{
pub fn new(i2c: I2C, address: u8, delay: D) -> Self {
Sgpc3 {
i2c,
address,
delay,
}
}
pub fn serial(&mut self) -> Result<u64, Error<E>> {
let mut serial = [0; 9];
self.delayed_read_cmd(Command::GetSerial, &mut serial)?;
let serial = u64::from(serial[0]) << 40
| u64::from(serial[1]) << 32
| u64::from(serial[3]) << 24
| u64::from(serial[4]) << 16
| u64::from(serial[6]) << 8
| u64::from(serial[7]);
Ok(serial)
}
pub fn get_feature_set(&mut self) -> Result<FeatureSet, Error<E>> {
let mut data = [0; 6];
self.delayed_read_cmd(Command::GetFeatureSet, &mut data)?;
let product_type = data[0] >> 4;
assert!(product_type == SGPC3_PRODUCT_TYPE);
Ok(FeatureSet {
product_type,
product_featureset: data[1],
})
}
#[inline]
pub fn set_ultra_power_mode(&mut self) -> Result<(), Error<E>> {
let power_mode: [u8; 2] = [0; 2];
self.write_command_with_args(Command::SetPowerMode, &power_mode)
}
pub fn self_test(&mut self) -> Result<&mut Self, Error<E>> {
let mut data = [0; 3];
self.delayed_read_cmd(Command::SelfTest, &mut data)?;
let result = u16::from_be_bytes([data[0], data[1]]);
if result != SGPC3_CMD_MEASURE_TEST_OK {
Err(Error::SelfTest)
} else {
Ok(self)
}
}
fn delayed_read_cmd(&mut self, cmd: Command, data: &mut [u8]) -> Result<(), Error<E>> {
self.write_command(cmd)?;
i2c::read_words_with_crc(&mut self.i2c, self.address, data)?;
Ok(())
}
fn write_command_with_args(&mut self, cmd: Command, data: &[u8]) -> Result<(), Error<E>> {
const MAX_TX_BUFFER: usize = 8;
let mut transfer_buffer = [0; MAX_TX_BUFFER];
let size = data.len();
assert!(size < 2 + size + size / 2);
let (command, delay) = cmd.as_tuple();
transfer_buffer[0..2].copy_from_slice(&command.to_be_bytes());
let slice = &data[..2];
transfer_buffer[2..4].copy_from_slice(slice);
transfer_buffer[4] = crc8::calculate(slice);
let transfer_buffer = if size > 2 {
let slice = &data[2..4];
transfer_buffer[5..7].copy_from_slice(slice);
transfer_buffer[7] = crc8::calculate(slice);
&transfer_buffer[..]
} else {
&transfer_buffer[0..5]
};
self.i2c
.write(self.address, transfer_buffer)
.map_err(Error::I2c)?;
self.delay.delay_ms(delay);
Ok(())
}
fn write_command(&mut self, cmd: Command) -> Result<(), Error<E>> {
let (command, delay) = cmd.as_tuple();
i2c::write_command(&mut self.i2c, self.address, command).map_err(Error::I2c)?;
self.delay.delay_ms(delay);
Ok(())
}
#[inline]
pub fn init_no_preheat(&mut self) -> Result<&mut Self, Error<E>> {
self.write_command(Command::InitAirQuality0)?;
Ok(self)
}
#[inline]
pub fn init_preheat(&mut self) -> Result<(), Error<E>> {
self.write_command(Command::InitAirQualityContinuous)
}
#[inline]
pub fn init_preheat_64s_fs5(&mut self) -> Result<(), Error<E>> {
self.write_command(Command::InitAirQuality64)
}
#[inline]
pub fn set_absolute_humidity(&mut self, abs_hum: u32) -> Result<&mut Self, Error<E>> {
assert!(abs_hum <= 256000);
let scaled = ((abs_hum * 16777) >> 16) as u16;
self.write_command_with_args(Command::SetHumidity, &scaled.to_be_bytes())?;
Ok(self)
}
#[inline]
pub fn set_relative_humidity(&mut self, rh: i32, t_mc: i32) -> Result<&mut Self, Error<E>> {
let abs_hum = calculate_absolute_humidity(rh, t_mc);
self.set_absolute_humidity(abs_hum as u32)
}
pub fn measure_tvoc_and_raw(&mut self) -> Result<(u16, u16), Error<E>> {
let mut buffer = [0; 6];
self.delayed_read_cmd(Command::MeasureAirQualityRaw, &mut buffer)?;
let raw_signal = u16::from_be_bytes([buffer[0], buffer[1]]);
let tvoc_ppb = u16::from_be_bytes([buffer[3], buffer[4]]);
Ok((tvoc_ppb, raw_signal))
}
pub fn measure_tvoc(&mut self) -> Result<u16, Error<E>> {
let mut buffer = [0; 3];
self.delayed_read_cmd(Command::MeasureAirQuality, &mut buffer)?;
let tvoc_ppb = u16::from_be_bytes([buffer[0], buffer[1]]);
Ok(tvoc_ppb)
}
pub fn measure_raw(&mut self) -> Result<u16, Error<E>> {
let mut buffer = [0; 3];
self.delayed_read_cmd(Command::MeasureRaw, &mut buffer)?;
let raw = u16::from_be_bytes([buffer[0], buffer[1]]);
Ok(raw)
}
pub fn get_baseline(&mut self) -> Result<u16, Error<E>> {
let mut buffer = [0; 3];
self.delayed_read_cmd(Command::GetAirQualityBaseline, &mut buffer)?;
let baseline = u16::from_be_bytes([buffer[0], buffer[1]]);
Ok(baseline)
}
pub fn get_inceptive_baseline(&mut self) -> Result<u16, Error<E>> {
let mut buffer = [0; 3];
self.delayed_read_cmd(Command::GetAirQualityInceptiveBaseline, &mut buffer)?;
let baseline = u16::from_be_bytes([buffer[0], buffer[1]]);
Ok(baseline)
}
#[inline]
pub fn set_baseline(&mut self, baseline: u16) -> Result<&mut Self, Error<E>> {
self.write_command_with_args(Command::SetBaseline, &baseline.to_be_bytes())?;
Ok(self)
}
pub fn initialize(
&mut self,
baseline: u16,
baseline_age_s: u32,
ultra_power_save: bool,
) -> Result<&mut Self, Error<E>> {
if ultra_power_save {
self.set_ultra_power_mode()?;
}
self.init_preheat()?;
let sleep_time = if baseline_age_s == 0 || baseline_age_s > 7 * 24 * 60 * 60 {
184 * 1000
} else {
self.set_baseline(baseline)?;
if baseline_age_s > 0 && baseline_age_s <= 30 * 60 {
0
} else if baseline_age_s > 30 * 60 && baseline_age_s <= 6 * 60 * 60 {
16 * 1000
} else {
184 * 1000
}
};
self.delay.delay_ms(sleep_time);
self.measure_tvoc()?;
self.delay.delay_ms(20 * 1000);
Ok(self)
}
}
#[cfg(test)]
mod tests {
use embedded_hal_mock as hal;
use self::hal::delay::MockNoop as DelayMock;
use self::hal::i2c::{Mock as I2cMock, Transaction};
use super::*;
#[test]
fn serial() {
let (cmd, _) = Command::GetSerial.as_tuple();
let expectations = [
Transaction::write(0x58, cmd.to_be_bytes().to_vec()),
Transaction::read(
0x58,
vec![0xde, 0xad, 0x98, 0xbe, 0xef, 0x92, 0xde, 0xad, 0x98],
),
];
let mock = I2cMock::new(&expectations);
let mut sensor = Sgpc3::new(mock, 0x58, DelayMock);
let serial = sensor.serial().unwrap();
assert_eq!(serial, 0x00deadbeefdead);
}
#[test]
fn selftest_ok() {
let (cmd, _) = Command::SelfTest.as_tuple();
let expectations = [
Transaction::write(0x58, cmd.to_be_bytes().to_vec()),
Transaction::read(0x58, vec![0xD4, 0x00, 0xC6]),
];
let mock = I2cMock::new(&expectations);
let mut sensor = Sgpc3::new(mock, 0x58, DelayMock);
assert!(sensor.self_test().is_ok());
}
#[test]
fn selftest_failed() {
let (cmd, _) = Command::SelfTest.as_tuple();
let expectations = [
Transaction::write(0x58, cmd.to_be_bytes().to_vec()),
Transaction::read(0x58, vec![0xde, 0xad, 0x98]),
];
let mock = I2cMock::new(&expectations);
let mut sensor = Sgpc3::new(mock, 0x58, DelayMock);
assert!(!sensor.self_test().is_ok());
}
#[test]
fn test_crc_error() {
let (cmd, _) = Command::SelfTest.as_tuple();
let expectations = [
Transaction::write(0x58, cmd.to_be_bytes().to_vec()),
Transaction::read(0x58, vec![0xD4, 0x00, 0x00]),
];
let mock = I2cMock::new(&expectations);
let mut sensor = Sgpc3::new(mock, 0x58, DelayMock);
match sensor.self_test() {
Err(Error::Crc) => {}
Err(_) => panic!("Unexpected error in CRC test"),
Ok(_) => panic!("Unexpected success in CRC test"),
}
}
#[test]
fn measure_tvoc_and_raw() {
let (cmd, _) = Command::MeasureAirQualityRaw.as_tuple();
let expectations = [
Transaction::write(0x58, cmd.to_be_bytes().to_vec()),
Transaction::read(0x58, vec![0x12, 0x34, 0x37, 0xbe, 0xef, 0x92]),
];
let mock = I2cMock::new(&expectations);
let mut sensor = Sgpc3::new(mock, 0x58, DelayMock);
let (tvoc, raw) = sensor.measure_tvoc_and_raw().unwrap();
assert_eq!(tvoc, 0xbeef);
assert_eq!(raw, 0x1234);
}
#[test]
fn absolute_humidity() {
let humidity = vec![
(10_000, 25_000, 2359),
(10_000, 50_000, 4717),
(25_000, 25_000, 5782),
(25_000, 50_000, 11565),
(25_000, 75_000, 17348),
];
for (i, (t, rh, abs_hum)) in humidity.iter().enumerate() {
let calc_abs_hum = calculate_absolute_humidity(*rh, *t) as i32;
let delta = if calc_abs_hum > *abs_hum {
calc_abs_hum - abs_hum
} else {
abs_hum - calc_abs_hum
};
assert!(
delta < 200,
"Calculated value = {}, Reference value = {} in index {}",
calc_abs_hum,
abs_hum,
i
);
}
}
}