ccs811 0.1.2

A library to use the CCS811 air quality sensor on raspberry pi
Documentation
use rppal::i2c::I2c;
use rppal::gpio::{OutputPin};
use std::thread::sleep;
use std::cmp::min;
use super::constants::{*};
use std::result::Result::Err;

/// Bytes are calculated by taking the value without fraction and put it's 7 bits to the first byte.
/// The fraction is multiplied by 512 as described in the CCS811 specs. To ensure
/// The value can not be higher than 127 but humidity and temperature, this function is used for, will never
/// exceed this.
fn float_to_bytes(value: f32) -> [u8; 2] {
    let base = value.floor();
    // We only have 9 bits. 512 are already 10. So we ensure with min() that max 511 is used for fraction
    let fraction = min(((value - base) * 512.0 - 1.0) as u16, 511);
    // Take 7 bits of base and 1 bit of fraction
    let hi = ((base as u8 & 0b1111111) << 1) | ((&fraction & 0b100000000) >> 8) as u8;
    // Take 8 bits of fraction (the missing one is in the high byte
    let lo = (&fraction & 0xFF) as u8;

    [hi, lo]
}

pub struct Ccs811Data {
    pub t_voc: u16,
    pub e_co2: u16,
    pub raw: Vec<u8>
}

pub struct CCS811 {
    pub i2c: I2c,
    pub wake: Option<OutputPin>
}

impl CCS811 {

    fn reset(&mut self) -> Result<(), String> {
        self.i2c.block_write(CCS811_SW_RESET, &[0x11,0xE5,0x72,0x8A])
            .map_err(|error| format!("Couldn't write to I2C: {}", error))?;

        sleep(CCS811_WAIT_AFTER_RESET_US);

        Ok(())
    }

    fn app_start(&mut self) -> Result<(), String> {
        self.i2c.write(&[CCS811_APP_START])
            .map_err(|error| format!("Could not set App start: {}", error))?;

        sleep(CCS811_WAIT_AFTER_APPSTART_US);

        Ok(())
    }

    fn erase_app(&mut self) -> Result<(), String> {
        self.i2c.block_write(CCS811_APP_ERASE, &[0xE7, 0xA7, 0xE6, 0x09])
            .map_err(|error| format!("Could not erase app: {}", error))?;

        sleep(CCS811_WAIT_AFTER_APPERASE_MS);

        Ok(())
    }

    fn check_hw_id(&mut self) -> Result<(), String> {
        let hw_id = self.i2c.smbus_read_byte(CCS811_HW_ID)
            .map_err(|error| format!("Couldn't read HWID: {}", error))?;

        if hw_id != 0x81 {
            return Err(format!("HWID of chip is not 0x81 but {:x?}", hw_id));
        }

        Ok(())
    }

    fn check_status(&mut self, expected: u8) -> Result<(), String> {
        let status = self.i2c.smbus_read_byte(CCS811_STATUS)
            .map_err(|error| format!("Could not read chip status: {}", error))?;

        if (status & expected) == 0 {
            return Err(format!("Chip status is not {:#010b} but {:#010b}", expected, status));
        }

        Ok(())
    }

    fn awake(&mut self) {
        if let Some(pin) = &mut self.wake {
            pin.set_low();
            sleep(CCS811_WAIT_AFTER_WAKE_US);
        }
    }

    fn sleep(&mut self) {
        if let Some(pin) = &mut self.wake {
            pin.set_high();
        }
    }

    /// Initialize CCS811 chip with i2c bus
    /// Sequence: set i2c slave -> Wake to low -> reset chip -> check hardware id -> start chip -> check chip status -> Wake to high -> ready
    ///
    /// # Examples
    ///
    /// ```
    /// let mut ccs811 = ccs811::new(i2c, None);
    ///
    /// match ccs811.begin() {
    ///   Ok(()) => println!("Chip is ready"),
    ///   Err(error) => panic!("Could not init the chip: {}", error)
    /// }
    /// ```
    pub fn begin(&mut self) -> Result<(), String> {
        self.i2c.set_slave_address(CCS811_SLAVEADDR_0)
            .map_err(|error| format!("Could not set slave addr: {}", error))?;

        self.awake();

        self.reset()
            .and(self.check_hw_id())
            .and(self.app_start())
            .and(self.check_status(CCS811_STATUS_APP_MODE & CCS811_STATUS_APP_VERIFY))?;

        self.sleep();

        Ok(())
    }

    /// Put CCS811 chip into target mode. Be aware that the first sampled data will be available after
    /// the period of time the mode takes. For instance it will take at least 60 seconds data will be
    /// first available in the Sec60 mode. For the Sec10 mode it is at least 10 seconds etc.
    /// Also be aware that the documentation of the chip mentions to change the chip mode to a lower
    /// sampling rate like Sec1 to Sec60, the mode should be set to Idle for at least 10 minutes before
    /// the setting the new mode.
    ///
    /// # Examples
    ///
    /// ```
    /// let mut ccs811 = ccs811::new(i2c, None);
    ///
    /// match ccs811.begin() {
    ///   Ok(()) => match ccs811.start(ccs811::MODE::Sec1) {
    ///     Ok(()) => (),
    ///     Err(error) => panic!("Could not start: {}", error)
    ///   },
    ///   Err(error) => panic!("Could not init the chip: {}", error)
    /// }
    /// ```
    pub fn start(&mut self, mode: Ccs811Mode) -> Result<(), String> {
        self.awake();
        self.i2c.block_write(CCS811_MEAS_MODE, &[(mode as u8) << 4])
            .map_err(|error| format!("Could not set mode: {}", error))?;
        self.sleep();

        Ok(())
    }

    /// Version should be something like 0x1X
    pub fn hardware_version(&mut self) -> Result<u8, String> {
        self.i2c.smbus_read_byte(CCS811_HW_VERSION)
            .map_err(|error| format!("Could not read hardware version: {}", error))
    }

    /// Something like 0x10 0x0
    pub fn bootloader_version(&mut self) -> Result<[u8; 2], String> {
        let mut buffer = [0; 2];
        self.i2c.block_read(CCS811_FW_BOOT_VERSION, &mut buffer)
            .map_err(|error| format!("Could not read boot loader version: {}", error))?;

        Ok(buffer)
    }

    /// Something like 0x10 0x0 or higher. You can flash a newer firmware (2.0.0) using the flash method
    /// and a firmware binary. See examples for more details
    pub fn application_version(&mut self) -> Result<[u8; 2], String> {
        let mut buffer = [0; 2];
        self.i2c.block_read(CCS811_FW_APP_VERSION, &mut buffer)
            .map_err(|error| format!("Could not read application version: {}", error))?;

        Ok(buffer)
    }

    /// Get the currently used baseline
    pub fn get_baseline(&mut self) -> Result<u16, String> {
        self.i2c.smbus_read_word(CCS811_BASELINE)
            .map_err(|error| format!("Could not read baseline: {}", error))
    }

    /// The CCS811 chip has an automatic baseline correction based on a 24 hour interval but you still
    /// can set the baseline manually if you want.
    pub fn set_baseline(&mut self, baseline: u16) -> Result<(), String> {
        self.i2c.smbus_write_word(CCS811_BASELINE, baseline)
            .map_err(|error| format!("Could not set baseline: {}", error))
    }

    /// Set environmental data measured by external sensors to the chip to include those in
    /// calculations. E.g. humidity 48.5% and 23.3°C
    ///
    /// # Examples
    ///
    /// ```
    /// match ccs811.set_env_data(48.5, 23.3) {
    ///   Ok(()) => println!("Updated environmental data on chip"),
    ///   Err(error) => panic!("Failed to set environmental data on chip because {}", error)
    /// }
    /// ```
    pub fn set_env_data(&mut self, humidity: f32, temperature: f32) -> Result<(), String> {
        let data = [
            float_to_bytes(humidity),
            float_to_bytes(temperature)
        ].concat();

        self.i2c.block_write(CCS811_ENV_DATA, &data)
            .map_err(|error| format!("Could npt write env data: {}", error))?;

        Ok(())
    }

    /// Read last sampled eCO2, tVOC and the corresponding status, error and raw data from the
    /// chip register
    ///
    /// # Examples
    ///
    /// ```
    /// match ccs811.read() {
    ///   Ok(data) => {
    ///     println!("t_voc: {}, e_co2: {}, raw: {:x?}", data.t_voc, data.e_co2, data.raw);
    ///   },
    ///   Err(error) => println!("Could not read data: {}", error)
    /// };
    /// ```
    pub fn read(&mut self) -> Result<Ccs811Data, String> {
        let mut buffer = [0; 8];
        self.awake();

        self.i2c.block_read(CCS811_ALG_RESULT_DATA, &mut buffer)
            .map_err(|error| format!("Could not read chip data: {}", error))?;

        self.sleep();

        if buffer[5] != 0 {
            return Err(format!("Some error while reading data {:x?}", buffer[5]));
        }

        let data = Ccs811Data {
            e_co2: buffer[0] as u16 * 256 + buffer[1] as u16,
            t_voc: buffer[2] as u16 * 256 + buffer[3] as u16,
            raw: buffer.to_vec()
        };

        if data.t_voc > 1187 || data.e_co2 > 8192 {
            return Err(format!("The data is above max {}ppb, {}ppm", data.t_voc, data.e_co2));
        }

        Ok(data)
    }

    /// Flash another firmware to the CCS811 chip. The firmware can be found in the world wide web in
    /// form of an binary file which must be read and passed as byte array to this function.
    /// If flashing fails the chip still got a working boot loader which makes it possible to write
    /// another firmware to the chip and fix the issue.
    ///
    /// # Examples
    ///
    /// ```
    /// use std::fs::File;
    /// use std::io::Read;
    ///
    /// let mut ccs811 = ccs811::new(i2c, None);
    ///
    /// let mut file = File::open("./CCS811_FW_App_v2-0-1.bin")
    ///     .expect("No firmware found");
    /// let mut data = vec![];
    /// let read = file.read_to_end(&mut data)
    ///     .expect("Could not load firmware");
    ///
    /// println!("Firmware has size of {} bytes", read);
    ///
    /// ccs811.flash(data)
    /// .expect("Failed to flash firmware");
    ///
    /// println!("Flashed :)");
    /// ```
    pub fn flash(&mut self, data: Vec<u8>) -> Result<(), String> {
        self.i2c.set_slave_address(CCS811_SLAVEADDR_0)
            .map_err(|error| format!("Could not set slave addr: {}", error))?;

        self.reset()?;
        self.check_status(CCS811_STATUS_APP_VALID)
            .map_err(|error| format!("Not valid: {}", error))?; //status!=0x00 && status!=0x10
        self.erase_app()?;
        self.check_status(CCS811_STATUS_APP_ERASE)
            .map_err(|error| format!("Not erased: {}", error))?; // status!=0x40

        let mut i = 0;
        loop {
            println!("Flashing {} of {}\r", i, data.len());
            if i >= data.len() {
                break;
            }
            let end = match i + 8 {
                v if v > data.len() => data.len(),
                v => v
            };
            self.i2c.block_write(CCS811_APP_DATA, &data[i..end])
                .map_err(|error| format!("Could not write firmware: {}", error))?;

            i += 8;
        }
        sleep(CCS811_WAIT_AFTER_APPDATA_MS);

        self.i2c.write(&[CCS811_APP_VERIFY])
            .map_err(|error| format!("Could not reset verify bit: {}", error))?;
        sleep(CCS811_WAIT_AFTER_APPVERIFY_MS);

        self.check_status(CCS811_STATUS_APP_ERASE | CCS811_STATUS_APP_VERIFY | CCS811_STATUS_APP_VALID)
            .map_err(|error| format!("Not verified: {}", error))?;

        self.reset()?;

        self.check_status(CCS811_STATUS_APP_VALID)
            .map_err(|error| format!("Unexpected status after flashing: {}", error))
    }
}