bosch_bme680/
config.rs

1use core::time::Duration;
2
3use log::warn;
4
5use crate::{
6    constants::{GAS_ARRAY_1, GAS_ARRAY_2, MAX_HEATER_TEMPERATURE, MAX_HEATER_WAIT_DURATION_MS},
7    data::CalibrationData,
8};
9use crate::constants::{CYCLE_DURATION, GAS_MEAS_DURATION, TPH_SWITCHING_DURATION, WAKEUP_DURATION};
10
11/// Use Primary if SDO connector of the sensor is connected to ground and Secondary if SDO is connected to Vin.
12#[repr(u8)]
13pub enum DeviceAddress {
14    Primary = 0x76,
15    Secondary = 0x77,
16}
17
18impl From<DeviceAddress> for u8 {
19    fn from(value: DeviceAddress) -> Self {
20        match value {
21            DeviceAddress::Primary => 0x76,
22            DeviceAddress::Secondary => 0x77,
23        }
24    }
25}
26
27impl Default for DeviceAddress {
28    fn default() -> Self {
29        Self::Primary
30    }
31}
32// Variant_id
33// gas_low = 0
34// gas_high = 1
35pub enum Variant {
36    GasLow = 0,
37    GasHigh = 1,
38}
39impl From<u8> for Variant {
40    fn from(value: u8) -> Self {
41        match value {
42            0 => Variant::GasLow,
43            1 => Variant::GasHigh,
44            x => panic!(
45                "Got unimplemented device variant from sensor: {x}. Possible values are: [0, 1]."
46            ),
47        }
48    }
49}
50
51impl Variant {
52    pub fn calc_gas_resistance(
53        &self,
54        adc_gas: u16,
55        range_switching_error: i8,
56        gas_range: usize,
57    ) -> f32 {
58        match self {
59            Self::GasLow => {
60                let adc_gas = adc_gas as f32;
61                let gas_range_f = (1 << gas_range) as f32;
62                let var1 = 1340. + (5. * range_switching_error as f32);
63                let var2 = var1 * (1. + GAS_ARRAY_1[gas_range] / 100.);
64                let var3 = 1. + (GAS_ARRAY_2[gas_range] / 100.);
65
66                1. / (var3 * (0.000000125) * gas_range_f * (((adc_gas - 512.) / var2) + 1.))
67            }
68            Self::GasHigh => {
69                let var1 = 262144_u32 >> gas_range;
70                let mut var2 = adc_gas as i32 - 512_i32;
71                var2 *= 3;
72                var2 += 4096;
73                1000000. * var1 as f32 / var2 as f32
74            }
75        }
76    }
77}
78
79#[derive(Debug, PartialEq, Eq)]
80pub enum SensorMode {
81    Sleep,
82    Forced,
83}
84
85impl From<SensorMode> for u8 {
86    fn from(value: SensorMode) -> Self {
87        match value {
88            SensorMode::Sleep => 0,
89            SensorMode::Forced => 1,
90        }
91    }
92}
93impl From<u8> for SensorMode {
94    fn from(val: u8) -> Self {
95        match val {
96            0 => SensorMode::Sleep,
97            1 => SensorMode::Forced,
98            invalid => panic!("Failed to read sensor mode. Received {invalid:b} possible values are 0b00(sleep) or 0b01(forced)"),
99        }
100    }
101}
102
103/// Used to enable gas measurement.
104/// Default values are 150ms heater duration and 300°C heater target temperature
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct GasConfig {
107    heater_duration: Duration,
108    heater_target_temperature: u16,
109    // idac heat is not implemented since the control loop will find the current after a few iterations anyway.
110}
111impl Default for GasConfig {
112    /// Defaults to 150ms heater duration and 300°C heater target temperature
113    fn default() -> Self {
114        Self {
115            heater_duration: Duration::from_millis(150),
116            heater_target_temperature: 300,
117        }
118    }
119}
120impl GasConfig {
121    pub fn calc_gas_wait(&self) -> u8 {
122        let mut duration = self.heater_duration.as_millis() as u16;
123        let mut factor: u8 = 0;
124
125        if duration >= MAX_HEATER_WAIT_DURATION_MS {
126            warn!("Specified heater duration longer than {MAX_HEATER_WAIT_DURATION_MS}ms. Setting to {MAX_HEATER_WAIT_DURATION_MS}ms instead.");
127            0xff /* Max duration*/
128        } else {
129            while duration > 0x3F {
130                duration /= 4;
131                factor += 1;
132            }
133            duration as u8 + factor * 64
134        }
135    }
136    pub fn calc_res_heat(
137        &self,
138        calibration_data: &CalibrationData,
139        ambient_temperature: i32,
140    ) -> u8 {
141        // cap at 400°C
142        let target_temperature = if self.heater_target_temperature > MAX_HEATER_TEMPERATURE {
143            warn!(
144                "Specified heater target temperature higher than {MAX_HEATER_TEMPERATURE}°C. Setting to 400°C instead."  
145          );
146            400u16
147        } else {
148            self.heater_target_temperature
149        };
150        let var1 = ((ambient_temperature * calibration_data.par_gh3 as i32) / 1000) * 256;
151        let var2 = (calibration_data.par_gh1 as i32 + 784)
152            * (((((calibration_data.par_gh2 as i32 + 154009) * target_temperature as i32 * 5)
153                / 100)
154                + 3276800)
155                / 10);
156        let var3 = var1 + (var2 / 2);
157        let var4 = var3 / (calibration_data.res_heat_range as i32 + 4);
158        let var5 = (131 * calibration_data.res_heat_val as i32) + 65536;
159        let heatr_res_x100 = ((var4 / var5) - 250) * 34;
160        ((heatr_res_x100 + 50) / 100) as u8
161    }
162    pub fn heater_duration(&self) -> Duration {
163        self.heater_duration
164    }
165}
166
167/// Used to set Sensor settings.
168/// All options not set by the builder are set to default values.
169///
170/// ```rust
171/// # use bosch_bme680::{Configuration, Oversampling, IIRFilter};
172/// # fn main() {
173/// let configuration = Configuration::builder()
174///                     .temperature_oversampling(Oversampling::By2)
175///                     .pressure_oversampling(Oversampling::By16)
176///                     .humidity_oversampling(Oversampling::By1)
177///                     .filter(IIRFilter::Coeff1)
178///                     // Gas measurement is enabled by default. To disable it pass None as the GasConfig
179///                     .gas_config(None)
180///                     .build();
181///                         
182/// # }
183/// ```
184#[derive(Debug, Clone, PartialEq, Eq)]
185pub struct Configuration {
186    pub temperature_oversampling: Option<Oversampling>,
187    pub pressure_oversampling: Option<Oversampling>,
188    pub humidity_oversampling: Option<Oversampling>,
189    pub filter: Option<IIRFilter>,
190    pub gas_config: Option<GasConfig>,
191}
192
193impl Default for Configuration {
194    /// Sets sensible default values for all options.
195    /// Temperature oversampling: By2,
196    /// Pressure oversampling: By16,
197    /// Humidity oversampling: By1,
198    /// IIRFilter: Coeff1,
199    /// Gas config:
200    /// heating duration: 150ms,
201    /// heater target temperature: 300°C
202    fn default() -> Self {
203        Self {
204            temperature_oversampling: Some(Oversampling::By2),
205            pressure_oversampling: Some(Oversampling::By16),
206            humidity_oversampling: Some(Oversampling::By1),
207            filter: Some(IIRFilter::Coeff1),
208            gas_config: Some(GasConfig::default()),
209        }
210    }
211}
212impl Configuration {
213    pub fn builder() -> ConfigBuilder {
214        ConfigBuilder {
215            config: Configuration::default(),
216        }
217    }
218
219    // calculates the delay period needed for a measurement in microseconds.
220    // Also add the heater duration in microseconds like the Adafruit driver does.
221    // https://github.com/adafruit/Adafruit_BME680/blob/master/bme68x.c#L490
222    pub(crate) fn calculate_delay_period_us(&self) -> u32 {
223        let mut measurement_cycles: u32 = 0;
224        if let Some(temperature_oversampling) = &self.temperature_oversampling {
225            measurement_cycles += temperature_oversampling.cycles();
226        }
227        if let Some(humidity_oversampling) = &self.humidity_oversampling {
228            measurement_cycles += humidity_oversampling.cycles();
229        }
230        if let Some(pressure_oversampling) = &self.pressure_oversampling {
231            measurement_cycles += pressure_oversampling.cycles();
232        }
233        let mut measurement_duration = measurement_cycles * CYCLE_DURATION;
234
235        // https://github.com/adafruit/Adafruit_BME680/blob/master/Adafruit_BME680.cpp#L311
236        if let Some(gas_config) = &self.gas_config {
237            measurement_duration += gas_config.heater_duration().as_micros() as u32;
238        }
239
240        measurement_duration += TPH_SWITCHING_DURATION;
241        measurement_duration += GAS_MEAS_DURATION;
242        measurement_duration += WAKEUP_DURATION;
243
244        measurement_duration
245    }
246
247}
248pub struct ConfigBuilder {
249    config: Configuration,
250}
251impl ConfigBuilder {
252    pub fn temperature_oversampling(mut self, oversampling: Oversampling) -> Self {
253        self.config.temperature_oversampling = Some(oversampling);
254        self
255    }
256    pub fn humidity_oversampling(mut self, oversampling: Oversampling) -> Self {
257        self.config.humidity_oversampling = Some(oversampling);
258        self
259    }
260    pub fn pressure_oversampling(mut self, oversampling: Oversampling) -> Self {
261        self.config.pressure_oversampling = Some(oversampling);
262        self
263    }
264    pub fn filter(mut self, filter: IIRFilter) -> Self {
265        self.config.filter = Some(filter);
266        self
267    }
268    pub fn gas_config(mut self, gas_config: Option<GasConfig>) -> Self {
269        self.config.gas_config = gas_config;
270        self
271    }
272    pub fn build(self) -> Configuration {
273        self.config
274    }
275}
276/// Oversampling settings for temperature, humidity, pressure.
277/// Skipping means no measurement will be taken, which is not recommended for the temperature
278/// as it's needed to calculate the adjusted values for humidity and pressure.
279#[derive(Debug, Eq, PartialEq, Clone)]
280pub enum Oversampling {
281    Skipped,
282    By1,
283    By2,
284    By4,
285    By8,
286    By16,
287}
288impl Oversampling {
289    pub fn cycles(&self) -> u32 {
290        match self {
291            Self::Skipped => 0,
292            Self::By1 => 1,
293            Self::By2 => 2,
294            Self::By4 => 4,
295            Self::By8 => 8,
296            Self::By16 => 16,
297        }
298    }
299}
300impl From<u8> for Oversampling {
301    fn from(val: u8) -> Self {
302        match val {
303            0 => Oversampling::Skipped,
304            1 => Oversampling::By1,
305            2 => Oversampling::By2,
306            3 => Oversampling::By4,
307            4 => Oversampling::By8,
308            _ => Oversampling::By16,
309        }
310    }
311}
312
313impl From<Oversampling> for u8 {
314    fn from(value: Oversampling) -> Self {
315        match value {
316            Oversampling::Skipped => 0,
317            Oversampling::By1 => 1,
318            Oversampling::By2 => 2,
319            Oversampling::By4 => 3,
320            Oversampling::By8 => 4,
321            Oversampling::By16 => 5,
322        }
323    }
324}
325
326/// IIR filter control only applies to temperature and pressure data.
327#[derive(Debug, Eq, PartialEq, Clone)]
328pub enum IIRFilter {
329    Coeff0,
330    Coeff1,
331    Coeff3,
332    Coeff7,
333    Coeff15,
334    Coeff31,
335    Coeff63,
336    Coeff127,
337}
338impl From<u8> for IIRFilter {
339    fn from(value: u8) -> Self {
340        match value {
341            0 => Self::Coeff0,
342            1 => Self::Coeff1,
343            2 => Self::Coeff3,
344            3 => Self::Coeff7,
345            4 => Self::Coeff15,
346            5 => Self::Coeff31,
347            6 => Self::Coeff63,
348            _ => Self::Coeff127,
349        }
350    }
351}
352
353impl From<IIRFilter> for u8 {
354    fn from(value: IIRFilter) -> Self {
355        match value {
356            IIRFilter::Coeff0 => 0,
357            IIRFilter::Coeff1 => 1,
358            IIRFilter::Coeff3 => 2,
359            IIRFilter::Coeff7 => 3,
360            IIRFilter::Coeff15 => 4,
361            IIRFilter::Coeff31 => 5,
362            IIRFilter::Coeff63 => 6,
363            IIRFilter::Coeff127 => 7,
364        }
365    }
366}
367
368#[derive(Debug, Eq, PartialEq, Clone)]
369pub enum HeaterProfile {
370    Profile0,
371    Profile1,
372    Profile2,
373    Profile3,
374    Profile4,
375    Profile5,
376    Profile6,
377    Profile7,
378    Profile8,
379    Profile9,
380}
381impl From<u8> for HeaterProfile {
382    fn from(value: u8) -> Self {
383        match value {
384            0 => Self::Profile0,
385            1 => Self::Profile1,
386            2 => Self::Profile2,
387            3 => Self::Profile3,
388            4 => Self::Profile4,
389            5 => Self::Profile5,
390            6 => Self::Profile6,
391            7 => Self::Profile7,
392            8 => Self::Profile8,
393            _ => Self::Profile9,
394        }
395    }
396}
397
398impl From<HeaterProfile> for u8 {
399    fn from(value: HeaterProfile) -> Self {
400        match value {
401            HeaterProfile::Profile0 => 0,
402            HeaterProfile::Profile1 => 1,
403            HeaterProfile::Profile2 => 2,
404            HeaterProfile::Profile3 => 3,
405            HeaterProfile::Profile4 => 4,
406            HeaterProfile::Profile5 => 5,
407            HeaterProfile::Profile6 => 6,
408            HeaterProfile::Profile7 => 7,
409            HeaterProfile::Profile8 => 8,
410            HeaterProfile::Profile9 => 9,
411        }
412    }
413}
414
415#[cfg(test)]
416mod config_tests {
417    extern crate std;
418    use std::time::Duration;
419
420    use crate::config::SensorMode;
421
422    use super::GasConfig;
423
424    #[test]
425    fn test_sensor_mode() {
426        let sleeping = 0u8;
427        let forced = 1u8;
428        assert_eq!(SensorMode::Sleep, sleeping.into());
429        assert_eq!(SensorMode::Forced, forced.into());
430    }
431    #[test]
432    fn test_gas_config() {
433        let config = GasConfig {
434            heater_duration: Duration::from_millis(100),
435            heater_target_temperature: 200,
436        };
437        assert!(config.calc_gas_wait() <= config.heater_duration.as_millis() as u8);
438        // taken from data sheet
439        assert_eq!(config.calc_gas_wait(), 0x59);
440    }
441}