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#[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}
32pub 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#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct GasConfig {
107 heater_duration: Duration,
108 heater_target_temperature: u16,
109 }
111impl Default for GasConfig {
112 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 } 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 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#[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 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 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 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#[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#[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 assert_eq!(config.calc_gas_wait(), 0x59);
440 }
441}