#![no_std]
use core::fmt::Debug;
use core::marker::PhantomData;
use embedded_hal::delay::DelayNs;
pub mod device;
use device::*;
pub mod base;
use base::*;
pub struct Scd30<Conn, Delay, Err> {
conn: Conn,
delay: Delay,
_err: PhantomData<Err>,
}
#[derive(Debug)]
pub enum Error<ConnErr> {
Conn(ConnErr),
Crc(u8, u8),
NoDevice,
}
impl<ConnErr> From<ConnErr> for Error<ConnErr> {
fn from(conn_err: ConnErr) -> Self {
Error::Conn(conn_err)
}
}
#[derive(PartialEq, Clone, Debug)]
pub struct Measurement {
pub co2: f32,
pub temp: f32,
pub rh: f32,
}
impl<Conn, Delay, Err> Scd30<Conn, Delay, Err>
where
Conn: Base<Err>,
Delay: DelayNs,
Err: Debug,
{
pub fn new(conn: Conn, delay: Delay) -> Result<Self, Error<Err>> {
let mut s = Scd30 {
conn,
delay,
_err: PhantomData,
};
let v = s.firmware_version()?;
if v == 0x00 || v == 0xFF {
return Err(Error::NoDevice);
}
Ok(s)
}
pub fn start_continuous(&mut self, pressure_compensation: u16) -> Result<(), Error<Err>> {
self.conn
.write_command(Command::StartContinuousMode, Some(pressure_compensation))
}
pub fn stop_continuous(&mut self) -> Result<(), Error<Err>> {
self.conn.write_command(Command::StopContinuousMode, None)
}
pub fn set_measurement_interval(&mut self, interval: u16) -> Result<(), Error<Err>> {
self.conn
.write_command(Command::SetMeasurementInterval, Some(interval))
}
pub fn set_afc(&mut self, enabled: bool) -> Result<(), Error<Err>> {
let v = match enabled {
true => 1,
false => 0,
};
self.conn.write_command(Command::SetAfc, Some(v))
}
pub fn set_frc(&mut self, cal_ppm: u16) -> Result<(), Error<Err>> {
self.conn.write_command(Command::SetFrc, Some(cal_ppm))
}
pub fn set_temp_offset(&mut self, temperature: f32) -> Result<(), Error<Err>> {
let temperature = (temperature as u16) * 100;
self.conn
.write_command(Command::SetTempOffset, Some(temperature))
}
pub fn set_alt_offset(&mut self, altitude: u16) -> Result<(), Error<Err>> {
self.conn.write_command(Command::SetAltComp, Some(altitude))
}
pub fn soft_reset(&mut self) -> Result<(), Error<Err>> {
self.conn.write_command(Command::SoftReset, None)
}
pub fn firmware_version(&mut self) -> Result<u16, Error<Err>> {
let mut buff = [0u8; 3];
self.conn
.read_command(Command::GetFirmwareVersion, &mut buff)?;
let crc = crc8(&buff[..2]);
if crc != buff[2] {
return Err(Error::Crc(crc, buff[2]));
}
let v: u16 = (buff[0] as u16) << 8 | (buff[1] as u16);
Ok(v)
}
pub fn data_ready(&mut self) -> Result<bool, Error<Err>> {
let mut buff = [0u8; 3];
self.conn.read_command(Command::GetDataReady, &mut buff)?;
let crc = crc8(&buff[..2]);
if crc != buff[2] {
return Err(Error::Crc(crc, buff[2]));
}
Ok(buff[1] != 0)
}
pub fn read_data(&mut self) -> Result<Measurement, Error<Err>> {
let mut buff = [0u8; 18];
self.conn
.read_command(Command::ReadMeasurement, &mut buff)?;
let co2 = convert(&buff[0..6])?;
let temp = convert(&buff[6..12])?;
let rh = convert(&buff[12..18])?;
Ok(Measurement { co2, temp, rh })
}
}
fn convert<E>(line: &[u8]) -> Result<f32, Error<E>> {
assert_eq!(line.len(), 6);
let crc1 = crc8(&line[0..2]);
if crc1 != line[2] {
return Err(Error::Crc(crc1, line[2]));
}
let crc2 = crc8(&line[3..5]);
if crc2 != line[5] {
return Err(Error::Crc(crc2, line[5]));
}
let u: u32 = ((line[0] as u32) << 24)
| ((line[1] as u32) << 16)
| ((line[3] as u32) << 8)
| ((line[4] as u32) << 0);
let v = f32::from_bits(u);
Ok(v)
}
#[cfg(test)]
mod test {
extern crate std;
use std::vec;
use embedded_hal_mock::eh1::delay::NoopDelay;
use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
use embedded_hal_mock::eh1::MockError;
use assert_approx_eq::assert_approx_eq;
use super::*;
#[test]
fn test_start_continuous() {
let expectations = [I2cTransaction::write(
DEFAULT_ADDRESS,
vec![0x00, 0x10, 0x00, 0x00, 0x81],
)];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Scd30 {
conn: i2c.clone(),
delay: NoopDelay {},
_err: PhantomData,
};
sensor.start_continuous(0).unwrap();
i2c.done();
}
#[test]
fn test_stop_continuous() {
let expectations = [I2cTransaction::write(DEFAULT_ADDRESS, vec![0x01, 0x04])];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Scd30 {
conn: i2c.clone(),
delay: NoopDelay {},
_err: PhantomData,
};
sensor.stop_continuous().unwrap();
i2c.done();
}
#[test]
fn test_set_measurement_interval() {
let expectations = [I2cTransaction::write(
DEFAULT_ADDRESS,
vec![0x46, 0x00, 0x00, 0x02, 0xE3],
)];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Scd30 {
conn: i2c.clone(),
delay: NoopDelay {},
_err: PhantomData,
};
sensor.set_measurement_interval(2).unwrap();
i2c.done();
}
#[test]
fn test_set_frc() {
let expectations = [I2cTransaction::write(
DEFAULT_ADDRESS,
vec![0x52, 0x04, 0x01, 0xc2, 0x50],
)];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Scd30 {
conn: i2c.clone(),
delay: NoopDelay {},
_err: PhantomData,
};
sensor.set_frc(450).unwrap();
i2c.done();
}
#[test]
fn set_temp_offset() {
let expectations = [I2cTransaction::write(
DEFAULT_ADDRESS,
vec![0x54, 0x03, 0x01, 0xF4, 0x33],
)];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Scd30 {
conn: i2c.clone(),
delay: NoopDelay {},
_err: PhantomData,
};
sensor.set_temp_offset(5.0).unwrap();
i2c.done();
}
#[test]
fn set_alt_offset() {
let expectations = [I2cTransaction::write(
DEFAULT_ADDRESS,
vec![0x51, 0x02, 0x03, 0xE8, 0xD4],
)];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Scd30 {
conn: i2c.clone(),
delay: NoopDelay {},
_err: PhantomData,
};
sensor.set_alt_offset(1000).unwrap();
i2c.done();
}
#[test]
fn test_soft_reset() {
let expectations = [I2cTransaction::write(DEFAULT_ADDRESS, vec![0xD3, 0x04])];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Scd30 {
conn: i2c.clone(),
delay: NoopDelay {},
_err: PhantomData,
};
sensor.soft_reset().unwrap();
i2c.done();
}
#[test]
fn test_read_data_ready() {
let expectations = [
I2cTransaction::write(DEFAULT_ADDRESS, vec![0x02, 0x02]),
I2cTransaction::read(DEFAULT_ADDRESS | I2C_READ_FLAG, vec![0x00, 0x01, 0xB0]),
];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Scd30 {
conn: i2c.clone(),
delay: NoopDelay {},
_err: PhantomData,
};
let ready = sensor.data_ready().unwrap();
assert!(ready);
i2c.done();
}
#[test]
fn test_read_measurement() {
let expectations = [
I2cTransaction::write(DEFAULT_ADDRESS, vec![0x03, 0x00]),
I2cTransaction::read(
DEFAULT_ADDRESS | I2C_READ_FLAG,
vec![
0x43, 0xDB, 0xCB, 0x8C, 0x2E, 0x8F, 0x41, 0xD9, 0x70, 0xE7, 0xFF, 0xF5, 0x42, 0x43, 0xBF, 0x3A, 0x1B, 0x74, ],
),
];
let mut i2c = I2cMock::new(&expectations);
let mut sensor = Scd30 {
conn: i2c.clone(),
delay: NoopDelay {},
_err: PhantomData,
};
let m = sensor.read_data().unwrap();
assert_approx_eq!(m.co2, 439.0, 0.1);
assert_approx_eq!(m.temp, 27.2, 0.1);
assert_approx_eq!(m.rh, 48.8, 0.1);
i2c.done();
}
#[test]
fn test_convert() {
let tests = &[
([0x43, 0xDB, 0xCB, 0x8C, 0x2E, 0x8F], 439.0),
([0x41, 0xD9, 0x70, 0xE7, 0xFF, 0xF5], 27.2),
([0x42, 0x43, 0xBF, 0x3A, 0x1B, 0x74], 48.8),
];
for t in tests {
let v = convert::<()>(&t.0).unwrap();
assert_approx_eq!(v, t.1, 0.1);
}
}
}