bosch_bme680/
lib.rs

1//! This a pure rust crate to read out sensor data from the [BME680](https://www.bosch-sensortec.com/products/environmental-sensors/gas-sensors/bme680/) environmental sensor from bosch.
2//!
3//! Notes:
4//! This library only supports reading out data with I²C but not SPI and
5//! only works for the BME680 and NOT for the BME688 though this could be implemented.
6//! The [official](https://github.com/BoschSensortec/BME68x-Sensor-API/) c implementation from Bosch was used as a reference.
7//!
8//! For further information about the sensors capabilities and settings refer to the official [product page](https://www.bosch-sensortec.com/products/environmental-sensors/gas-sensors/bme680/).[]
9//!
10//!
11//! ## [`embedded-hal-async`] usage
12//!
13//! This crate has optional support for [`embedded-hal-async`], which provides
14//! asynchronous versions of the [`embedded-hal`] traits. To avoid an unnecessary
15//! dependency on `embedded-hal-async` for projects which do not require it, the
16//! `embedded-hal-async` support is an optional feature.
17//!
18//! In order to use the `embedded-hal-async` driver, add the following to your
19//! `Cargo.toml`:
20//!
21//! ```toml
22//! [dependencies]
23//! bosch-bme680 = { version = "1.0.3", features = ["embedded-hal-async"] }
24//! ```
25//!
26//! Then, construct an instance of the `AsyncBme680` struct using the
27//! `embedded_hal_async` `I2c` and `Delay` traits.
28//!
29//! [`embedded-hal`]: https://crates.io/crates/embedded-hal-async
30//! [`embedded-hal-async`]: https://crates.io/crates/embedded-hal-async
31
32// TODO add example here
33#![no_std]
34#![forbid(unsafe_code)]
35#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
36#![allow(clippy::excessive_precision)]
37#![allow(clippy::unusual_byte_groupings)]
38
39use self::config::{SensorMode, Variant};
40
41use data::CalibrationData;
42use embedded_hal::delay::DelayNs;
43use embedded_hal::i2c::{I2c, SevenBitAddress};
44use i2c_helper::I2CHelper;
45
46pub use self::config::{Configuration, DeviceAddress, GasConfig, IIRFilter, Oversampling};
47use crate::data::{calculate_humidity, calculate_pressure, calculate_temperature};
48pub use data::MeasurmentData;
49pub use error::BmeError;
50
51#[cfg(feature = "embedded-hal-async")]
52mod async_impl;
53#[cfg(feature = "embedded-hal-async")]
54pub use async_impl::AsyncBme680;
55mod bitfields;
56mod calculations;
57mod config;
58mod constants;
59mod data;
60mod error;
61mod i2c_helper;
62
63/// Sensor driver
64pub struct Bme680<I2C, D> {
65    // actually communicates with sensor
66    i2c: I2CHelper<I2C, D>,
67    // calibration data that was saved on the sensor
68    calibration_data: CalibrationData,
69    // used to calculate measurement delay period
70    current_sensor_config: Configuration,
71    // needed to calculate the gas resistance since it differs between bme680 and bme688
72    variant: Variant,
73}
74impl<I2C, D> Bme680<I2C, D>
75where
76    I2C: I2c<SevenBitAddress>,
77    // <I2C as WriteRead>::Error: core::fmt::Debug,
78    // <I2C as Write>::Error: core::fmt::Debug,
79    D: DelayNs,
80{
81    /// Creates a new instance of the Sensor
82    ///
83    /// # Arguments
84    /// * `delayer` - Used to wait for the triggered measurement to finish
85    /// * `ambient_temperature` - Needed to calculate the heater target temperature
86    pub fn new(
87        i2c_interface: I2C,
88        device_address: DeviceAddress,
89        delayer: D,
90        sensor_config: &Configuration,
91        ambient_temperature: i32,
92    ) -> Result<Self, BmeError<I2C>> {
93        let mut i2c = I2CHelper::new(i2c_interface, device_address, delayer, ambient_temperature)?;
94
95        let calibration_data = i2c.get_calibration_data()?;
96        i2c.set_config(sensor_config, &calibration_data)?;
97        let variant = i2c.get_variant_id()?;
98        let bme = Self {
99            i2c,
100            calibration_data,
101            current_sensor_config: sensor_config.clone(),
102            variant,
103        };
104
105        Ok(bme)
106    }
107    /// Returns the wrapped i2c interface
108    pub fn into_inner(self) -> I2C {
109        self.i2c.into_inner()
110    }
111
112    fn put_to_sleep(&mut self) -> Result<(), BmeError<I2C>> {
113        self.i2c.set_mode(SensorMode::Sleep)
114    }
115    pub fn set_configuration(&mut self, config: &Configuration) -> Result<(), BmeError<I2C>> {
116        self.put_to_sleep()?;
117        self.i2c.set_config(config, &self.calibration_data)?;
118        // current conf is used to calculate measurement delay period
119        self.current_sensor_config = config.clone();
120        Ok(())
121    }
122    /// Trigger a new measurement.
123    /// # Errors
124    /// If no new data is generated in 5 tries a Timeout error is returned.
125    // Sets the sensor mode to forced
126    // Tries to wait 5 times for new data with a delay calculated based on the set sensor config
127    // If no new data could be read in those 5 attempts a Timeout error is returned
128    pub fn measure(&mut self) -> Result<MeasurmentData, BmeError<I2C>> {
129        self.i2c.set_mode(SensorMode::Forced)?;
130        let delay_period = self.current_sensor_config.calculate_delay_period_us();
131        self.i2c.delay(delay_period);
132        // try read new values 5 times and delay if no new data is available or the sensor is still measuring
133        for _i in 0..5 {
134            let raw_data = self.i2c.get_field_data()?;
135            match MeasurmentData::from_raw(raw_data, &self.calibration_data, &self.variant) {
136                Some(data) => {
137                    // update the current ambient temperature which is needed to calculate the target heater temp
138                    self.i2c.ambient_temperature = data.temperature as i32;
139                    return Ok(data);
140                }
141                None => self.i2c.delay(delay_period),
142            }
143        }
144        // Shouldn't happen
145        Err(BmeError::MeasuringTimeOut)
146    }
147
148    pub fn get_calibration_data(&self) -> &CalibrationData {
149        &self.calibration_data
150    }
151}
152
153#[cfg(test)]
154mod library_tests {
155    extern crate std;
156
157    use std::vec;
158    use std::vec::Vec;
159
160    use crate::constants::{
161        ADDR_CHIP_ID, ADDR_CONFIG, ADDR_CONTROL_MODE, ADDR_GAS_WAIT_0, ADDR_REG_COEFF1,
162        ADDR_REG_COEFF2, ADDR_REG_COEFF3, ADDR_RES_HEAT_0, ADDR_SOFT_RESET, ADDR_VARIANT_ID,
163        CHIP_ID, CMD_SOFT_RESET, LEN_COEFF1, LEN_COEFF2, LEN_COEFF3,
164    };
165    use crate::i2c_helper::extract_calibration_data;
166
167    const CALIBRATION_DATA: [u8; 42] = [
168        179, 193, 176, 188, 21, 51, 11, 29, 222, 179, 184, 1, 230, 47, 209, 22, 154, 34, 237, 70,
169        148, 134, 44, 13, 204, 61, 206, 69, 18, 43, 124, 164, 92, 132, 19, 63, 29, 28, 201, 140,
170        70, 24,
171    ];
172
173    use super::*;
174    use crate::bitfields::RawConfig;
175    use embedded_hal_mock::eh1::delay::NoopDelay;
176    use embedded_hal_mock::eh1::i2c::{Mock as I2cMock, Transaction as I2cTransaction};
177
178    fn setup_transactions() -> Vec<I2cTransaction> {
179        let mut transactions = vec![];
180        let calibration_data_1 = CALIBRATION_DATA[0..LEN_COEFF1].to_vec();
181        let calibration_data_2 = CALIBRATION_DATA[LEN_COEFF1..LEN_COEFF1 + LEN_COEFF2].to_vec();
182        let calibration_data_3 = CALIBRATION_DATA
183            [LEN_COEFF1 + LEN_COEFF2..LEN_COEFF1 + LEN_COEFF2 + LEN_COEFF3]
184            .to_vec();
185        assert_eq!(calibration_data_1.len(), LEN_COEFF1);
186        assert_eq!(calibration_data_2.len(), LEN_COEFF2);
187        assert_eq!(calibration_data_3.len(), LEN_COEFF3);
188        // soft reset
189        transactions.push(I2cTransaction::write(
190            DeviceAddress::Primary.into(),
191            vec![ADDR_SOFT_RESET, CMD_SOFT_RESET],
192        ));
193        // check device id
194        transactions.push(I2cTransaction::write_read(
195            DeviceAddress::Primary.into(),
196            vec![ADDR_CHIP_ID],
197            vec![CHIP_ID],
198        ));
199        // calibration_data
200        transactions.push(I2cTransaction::write_read(
201            DeviceAddress::Primary.into(),
202            vec![ADDR_REG_COEFF1],
203            calibration_data_1,
204        ));
205        transactions.push(I2cTransaction::write_read(
206            DeviceAddress::Primary.into(),
207            vec![ADDR_REG_COEFF2],
208            calibration_data_2,
209        ));
210        transactions.push(I2cTransaction::write_read(
211            DeviceAddress::Primary.into(),
212            vec![ADDR_REG_COEFF3],
213            calibration_data_3,
214        ));
215        // set config
216        // 1. get current config by writing to register 0x71 with buffer len 5.
217        // 2. apply default user facing config to the default values of the sensor.
218        // 3. write gas_wait_0 and res_heat_0
219        // config defaults to 0s
220        // 1.
221        let default_config = [0u8; 5];
222        transactions.push(I2cTransaction::write_read(
223            DeviceAddress::Primary.into(),
224            vec![ADDR_CONFIG],
225            default_config.into(),
226        ));
227        // 2.
228        let user_config = Configuration::default();
229        let mut raw_config = RawConfig(default_config);
230        raw_config.apply_config(&user_config);
231        // add unique write for each register
232        raw_config
233            .0
234            .into_iter()
235            .enumerate()
236            .for_each(|(register_offset, register_content)| {
237                transactions.push(I2cTransaction::write(
238                    DeviceAddress::Primary.into(),
239                    vec![ADDR_CONFIG + register_offset as u8, register_content],
240                ));
241            });
242        // 3.
243        let gas_config = user_config.gas_config.unwrap();
244        let gas_wait_0 = gas_config.calc_gas_wait();
245        let res_heat_0 = gas_config.calc_res_heat(&extract_calibration_data(CALIBRATION_DATA), 20);
246        transactions.push(I2cTransaction::write(
247            DeviceAddress::Primary.into(),
248            vec![ADDR_GAS_WAIT_0, gas_wait_0],
249        ));
250        transactions.push(I2cTransaction::write(
251            DeviceAddress::Primary.into(),
252            vec![ADDR_RES_HEAT_0, res_heat_0],
253        ));
254        // get chip variant
255        transactions.push(I2cTransaction::write_read(
256            DeviceAddress::Primary.into(),
257            vec![ADDR_VARIANT_ID],
258            vec![0],
259        ));
260        transactions
261    }
262    fn add_sleep_to_sleep_transactions(transactions: &mut Vec<I2cTransaction>) {
263        transactions.push(I2cTransaction::write_read(
264            DeviceAddress::Primary.into(),
265            vec![ADDR_CONTROL_MODE],
266            // sleep mode
267            vec![0b101011_00],
268        ));
269    }
270    #[test]
271    fn test_setup() {
272        let transactions = setup_transactions();
273        let i2c_interface = I2cMock::new(&transactions);
274        let bme = Bme680::new(
275            i2c_interface,
276            DeviceAddress::Primary,
277            NoopDelay::new(),
278            &Configuration::default(),
279            20,
280        )
281        .unwrap();
282        bme.into_inner().done();
283    }
284
285    #[test]
286    fn test_set_mode_forced_to_sleep() {
287        let mut transactions = setup_transactions();
288        transactions.push(I2cTransaction::write_read(
289            DeviceAddress::Primary.into(),
290            vec![ADDR_CONTROL_MODE],
291            // sleep mode
292            vec![0b101011_01],
293        ));
294        transactions.push(I2cTransaction::write(
295            DeviceAddress::Primary.into(),
296            vec![ADDR_CONTROL_MODE, 0b101011_00],
297        ));
298        transactions.push(I2cTransaction::write_read(
299            DeviceAddress::Primary.into(),
300            vec![ADDR_CONTROL_MODE],
301            // sleep mode
302            vec![0b101011_00],
303        ));
304        // Transactions: Get(Forced) -> Set(Sleep) -> Get(Sleep)
305        let i2c_interface = I2cMock::new(&transactions);
306        let mut bme = Bme680::new(
307            i2c_interface,
308            DeviceAddress::Primary,
309            NoopDelay::new(),
310            &Configuration::default(),
311            20,
312        )
313        .unwrap();
314        bme.put_to_sleep().unwrap();
315        bme.into_inner().done();
316    }
317    #[test]
318    fn test_set_mode_sleep_to_sleep() {
319        let mut transactions = setup_transactions();
320        add_sleep_to_sleep_transactions(&mut transactions);
321        let i2c_interface = I2cMock::new(&transactions);
322        let mut bme = Bme680::new(
323            i2c_interface,
324            DeviceAddress::Primary,
325            NoopDelay::new(),
326            &Configuration::default(),
327            20,
328        )
329        .unwrap();
330        bme.put_to_sleep().unwrap();
331        bme.into_inner().done();
332    }
333}