#![no_std]
use core::fmt::Debug;
use core::marker::PhantomData;
extern crate embedded_hal;
#[macro_use] extern crate log;
pub mod device;
use device::*;
pub mod base;
use base::*;
pub struct Scd30<Conn, Err> {
conn: Conn,
_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, Err> Scd30 <Conn, Err> where
Conn: Base<Err>,
Err: Debug,
{
pub fn new(conn: Conn) -> Result<Self, Error<Err>> {
let mut s = Scd30{ conn, _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 = Self::convert(&buff[0..6])?;
let temp = Self::convert(&buff[6..12])?;
let rh = Self::convert(&buff[12..18])?;
Ok(Measurement{co2, temp, rh})
}
fn convert(line: &[u8]) -> Result<f32, Error<Err>> {
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;
extern crate embedded_hal_mock;
use embedded_hal_mock::MockError;
use embedded_hal_mock::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
extern crate assert_approx_eq;
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(), _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(), _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(), _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(), _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(), _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(), _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(), _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(), _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(), _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 = Scd30::<I2cMock, MockError>::convert(&t.0).unwrap();
assert_approx_eq!(v, t.1, 0.1);
}
}
}