embedded_ccs811/lib.rs
1//! This is a platform agnostic Rust driver for the CCS811 ultra-low power
2//! digital VOC sensor for monitoring indoor air quality (IAQ) using
3//! the [`embedded-hal`] traits.
4//!
5//! [`embedded-hal`]: https://github.com/rust-embedded/embedded-hal
6//!
7//! This driver allows you to:
8//! - In application mode:
9//! - Set the measurement mode. See: [`set_mode()`].
10//! - Check if there is new data ready. See: [`has_data_ready()`].
11//! - Get the algoritm and raw result data. See: [`data()`].
12//! - Get the raw data. See: [`raw_data()`].
13//! - Get the current baseline. See: [`baseline()`].
14//! - Set the baseline. See: [`set_baseline()`].
15//! - Set the environment temperature and relative humidity. See: [`set_environment()`].
16//! - Set the interrupt mode. See: [`set_interrupt_mode()`].
17//! - Set the eCO2 thresholds for interrupts. See: [`set_eco2_thresholds()`].
18//! - In boot mode:
19//! - Start application. See: [`start_application()`].
20//! - Reset, erase, download and verify new application. See: [`update_application()`].
21//! - Erase application. See: [`erase_application()`].
22//! - Verify application. See: [`verify_application()`].
23//! - Download application. See: [`download_application()`].
24//! - In either mode:
25//! - Get the firmware mode. See: [`firmware_mode()`].
26//! - Check whether a valid application is loaded. See: [`has_valid_app()`].
27//! - Get the hardware ID. See: [`hardware_id()`].
28//! - Get the hardware version. See: [`hardware_version()`].
29//! - Get the firmware bootloader version. See: [`firmware_bootloader_version()`].
30//! - Get the firmware application version. See: [`firmware_application_version()`].
31//! - Do a software reset. See: [`software_reset()`].
32//!
33//! [`set_mode()`]: trait.Ccs811AppMode.html#tymethod.set_mode
34//! [`has_data_ready()`]: trait.Ccs811AppMode.html#tymethod.has_data_ready
35//! [`data()`]: trait.Ccs811AppMode.html#tymethod.data
36//! [`raw_data()`]: trait.Ccs811AppMode.html#tymethod.raw_data
37//! [`baseline()`]: trait.Ccs811AppMode.html#tymethod.baseline
38//! [`set_baseline()`]: trait.Ccs811AppMode.html#tymethod.set_baseline
39//! [`set_environment()`]: trait.Ccs811AppMode.html#tymethod.set_environment
40//! [`set_interrupt_mode()`]: trait.Ccs811AppMode.html#tymethod.set_interrupt_mode
41//! [`set_eco2_thresholds()`]: trait.Ccs811AppMode.html#tymethod.set_eco2_thresholds
42//! [`start_application()`]: trait.Ccs811BootMode.html#tymethod.start_application
43//! [`update_application()`]: trait.Ccs811BootMode.html#tymethod.update_application
44//! [`erase_application()`]: trait.Ccs811BootMode.html#tymethod.erase_application
45//! [`verify_application()`]: trait.Ccs811BootMode.html#tymethod.verify_application
46//! [`download_application()`]: trait.Ccs811BootMode.html#tymethod.download_application
47//! [`firmware_mode()`]: trait.Ccs811Device.html#tymethod.firmware_mode
48//! [`has_valid_app()`]: trait.Ccs811Device.html#tymethod.has_valid_app
49//! [`hardware_id()`]: trait.Ccs811Device.html#tymethod.hardware_id
50//! [`hardware_version()`]: trait.Ccs811Device.html#tymethod.hardware_version
51//! [`firmware_bootloader_version()`]: trait.Ccs811Device.html#tymethod.firmware_bootloader_version
52//! [`firmware_application_version()`]: trait.Ccs811Device.html#tymethod.firmware_application_version
53//! [`software_reset()`]: trait.Ccs811Device.html#tymethod.software_reset
54//!
55//! [Introductory blog post](https://blog.eldruin.com/ccs811-indoor-air-quality-sensor-driver-in-rust)
56//!
57//! ## The device
58//!
59//! The CCS811 is an ultra-low power digital gas sensor solution which
60//! integrates a metal oxide (MOX) gas sensor to detect a wide range of
61//! Volatile Organic Compounds (VOCs) for indoor air quality monitoring
62//! with a microcontroller unit (MCU), which includes an Analog-to-Digital
63//! converter (ADC), and an I²C interface.
64//!
65//! CCS811 is based on ams unique micro-hotplate technology which enables a
66//! highly reliable solution for gas sensors, very fast cycle times and a
67//! significant reduction in average power consumption.
68//!
69//! The integrated MCU manages the sensor driver modes and measurements.
70//! The I²C digital interface significantly simplifies the hardware and
71//! software design, enabling a faster time to market.
72//!
73//! CCS811 supports intelligent algorithms to process raw sensor measurements
74//! to output equivalent total VOC (eTVOC) and equivalent CO2 (eCO2) values,
75//! where the main cause of VOCs is from humans.
76//!
77//! CCS811 supports multiple measurement modes that have been optimized for
78//! low-power consumption during an active sensor measurement and idle mode
79//! extending battery life in portable applications.
80//!
81//! Documentation:
82//! - [Datasheet](https://www.sciosense.com/wp-content/uploads/2020/01/CCS811-Datasheet.pdf)
83//! - [Programming and interfacing guide](https://www.sciosense.com/wp-content/uploads/2020/01/CCS811-Application-Note-Programming-and-interfacing-guide.pdf)
84//!
85//! ## Usage examples (see also examples folder)
86//!
87//! To use this driver, import this crate and an `embedded_hal` implementation,
88//! then instantiate the appropriate device.
89//!
90//! The CCS811 can be placed in sleep and woken up only for communication.
91//! This driver provides two structures: `Ccs811Awake` and `Ccs811` depeding
92//! on the waking state.
93//!
94//! The `Ccs811Awake` assumes an awake device and handles only the I2C communication.
95//! This can be used when the waking up and sleep of the device is handled
96//! manually.
97//! Additionally a wrapper `Ccs811` is provided, which handles waking up
98//! the device before each operation and putting it to sleep afterwards.
99//!
100//! Please find additional examples using hardware in this repository: [driver-examples]
101//!
102//! [driver-examples]: https://github.com/eldruin/driver-examples
103//!
104//! ### Start the application and take measurements
105//!
106//! ```no_run
107//! use linux_embedded_hal::{I2cdev, CdevPin, Delay};
108//! use linux_embedded_hal::gpio_cdev::{Chip, LineRequestFlags};
109//! use embedded_ccs811::{prelude::*, Ccs811, SlaveAddr, MeasurementMode};
110//! use nb::block;
111//!
112//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
113//! let mut chip = Chip::new("/dev/gpiochip0").unwrap();
114//! let handle = chip.get_line(17).unwrap()
115//! .request(LineRequestFlags::OUTPUT, 0, "output").unwrap();
116//! let nwake = CdevPin::new(handle).unwrap();
117//! let delay = Delay {};
118//! let address = SlaveAddr::default();
119//! let sensor = Ccs811::new(dev, address, nwake, delay);
120//! let mut sensor = sensor.start_application().ok().unwrap();
121//! sensor.set_mode(MeasurementMode::ConstantPower1s).unwrap();
122//! loop {
123//! let data = block!(sensor.data()).unwrap();
124//! println!("eCO2: {}, eTVOC: {}", data.eco2, data.etvoc);
125//! }
126//! ```
127//!
128//! ### Save and restore the baseline
129//!
130//! ```no_run
131//! use linux_embedded_hal::I2cdev;
132//! use embedded_ccs811::{prelude::*, Ccs811Awake, SlaveAddr};
133//!
134//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
135//! let address = SlaveAddr::default();
136//! let sensor = Ccs811Awake::new(dev, address);
137//! let mut sensor = sensor.start_application().ok().unwrap();
138//! let baseline = sensor.baseline().unwrap();
139//! // ...
140//! sensor.set_baseline(baseline).unwrap();
141//! ```
142//!
143//! ### Set the environment temperature and relative humidity
144//!
145//! ```no_run
146//! use linux_embedded_hal::I2cdev;
147//! use embedded_ccs811::{prelude::*, Ccs811Awake, SlaveAddr};
148//!
149//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
150//! let address = SlaveAddr::default();
151//! let sensor = Ccs811Awake::new(dev, address);
152//! let mut sensor = sensor.start_application().ok().unwrap();
153//! let temp_c = 25.0;
154//! let rel_humidity = 50.0;
155//! sensor.set_environment(rel_humidity, temp_c).unwrap();
156//! ```
157//!
158//! ### Set the eCO2 thresholds and configure interrupts
159//!
160//! Only generate an interrupt when the thresholds are crossed.
161//!
162//! ```no_run
163//! use linux_embedded_hal::I2cdev;
164//! use embedded_ccs811::{prelude::*, Ccs811Awake, SlaveAddr, InterruptMode, MeasurementMode};
165//!
166//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
167//! let address = SlaveAddr::default();
168//! let sensor = Ccs811Awake::new(dev, address);
169//! let mut sensor = sensor.start_application().ok().unwrap();
170//! sensor.set_eco2_thresholds(1500, 2500).unwrap();
171//! sensor.set_interrupt_mode(InterruptMode::OnThresholdCrossed).unwrap();
172//! sensor.set_mode(MeasurementMode::ConstantPower1s).unwrap();
173//! ```
174//!
175//! ### Get hardware and firmware information
176//!
177//! ```no_run
178//! use linux_embedded_hal::I2cdev;
179//! use embedded_ccs811::{prelude::*, Ccs811Awake, SlaveAddr};
180//!
181//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
182//! let address = SlaveAddr::default();
183//! let mut sensor = Ccs811Awake::new(dev, address);
184//! let hw_id = sensor.hardware_id().unwrap();
185//! let hw_ver = sensor.hardware_version().unwrap();
186//! let fw_boot_ver = sensor.firmware_bootloader_version().unwrap();
187//! let fw_app_ver = sensor.firmware_application_version().unwrap();
188//! println!(
189//! "HW ID: {}, HW version: {:#?}, FW bootloader version: {:#?}, FW app version: {:#?}",
190//! hw_id, hw_ver, fw_boot_ver, fw_app_ver
191//! );
192//! ```
193
194#![deny(unsafe_code, missing_docs)]
195#![no_std]
196
197extern crate embedded_hal as hal;
198use core::marker::PhantomData;
199
200mod common_impl;
201pub mod prelude;
202mod register_access;
203use crate::register_access::{BitFlags, Register};
204mod app_mode;
205mod boot_mode;
206mod traits;
207pub use crate::traits::{Ccs811AppMode, Ccs811BootMode, Ccs811Device};
208mod types;
209pub use crate::types::{
210 AlgorithmResult, DeviceErrors, Error, ErrorAwake, FirmwareMode, InterruptMode, MeasurementMode,
211 ModeChangeError, SlaveAddr,
212};
213pub use nb;
214
215/// CCS811 device driver
216///
217/// Convenience wrapper arount `Ccs811Awake` which handles waking up the device on each operation.
218#[derive(Debug)]
219pub struct Ccs811<I2C, NWAKE, WAKEDELAY, MODE> {
220 dev: Ccs811Awake<I2C, MODE>,
221 n_wake_pin: NWAKE,
222 wake_delay: WAKEDELAY,
223 _mode: PhantomData<MODE>,
224}
225
226/// Already awake CCS811 device driver
227///
228/// This can be used when the nWAKE pin is connected directly to GND or when
229/// handling the device waking manually instead of using the `Ccs811` wrapper type.
230#[derive(Debug)]
231pub struct Ccs811Awake<I2C, MODE> {
232 /// The concrete I²C device implementation.
233 i2c: I2C,
234 address: u8,
235 meas_mode_reg: u8,
236 in_progress: ActionInProgress,
237 _mode: PhantomData<MODE>,
238}
239
240#[derive(Debug, PartialEq)]
241enum ActionInProgress {
242 None,
243 Verification,
244 Erase,
245}
246
247/// Mode marker
248pub mod mode {
249 /// Boot mode
250 pub struct Boot(());
251 /// App mode
252 pub struct App(());
253}
254
255mod private {
256 use super::{mode, Ccs811, Ccs811Awake};
257 pub trait Sealed {}
258
259 impl Sealed for mode::Boot {}
260 impl Sealed for mode::App {}
261 impl<I2C, NWAKE, WAKEDELAY, MODE> Sealed for Ccs811<I2C, NWAKE, WAKEDELAY, MODE> {}
262 impl<I2C, MODE> Sealed for Ccs811Awake<I2C, MODE> {}
263}