aranet_core/
validation.rs

1//! Data validation and bounds checking for sensor readings.
2//!
3//! This module provides validation utilities to detect anomalous readings
4//! and flag potential sensor issues.
5//!
6//! # Example
7//!
8//! ```
9//! use aranet_core::ReadingValidator;
10//! use aranet_core::validation::ValidatorConfig;
11//! use aranet_types::{CurrentReading, Status};
12//!
13//! // Create a validator with default config
14//! let validator = ReadingValidator::default();
15//!
16//! // Create a reading to validate
17//! let reading = CurrentReading {
18//!     co2: 800,
19//!     temperature: 22.5,
20//!     pressure: 1013.0,
21//!     humidity: 45,
22//!     battery: 85,
23//!     status: Status::Green,
24//!     interval: 300,
25//!     age: 60,
26//!     captured_at: None,
27//!     radon: None,
28//!     radiation_rate: None,
29//!     radiation_total: None,
30//!     radon_avg_24h: None,
31//!     radon_avg_7d: None,
32//!     radon_avg_30d: None,
33//! };
34//!
35//! let result = validator.validate(&reading);
36//! assert!(result.is_valid);
37//! assert!(!result.has_warnings());
38//! ```
39
40use serde::{Deserialize, Serialize};
41
42use aranet_types::{CurrentReading, DeviceType};
43
44/// Warning types for validation issues.
45///
46/// This enum is marked `#[non_exhaustive]` to allow adding new warning types
47/// in future versions without breaking downstream code.
48#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
49#[non_exhaustive]
50pub enum ValidationWarning {
51    /// CO2 reading is below minimum expected value.
52    Co2TooLow { value: u16, min: u16 },
53    /// CO2 reading is above maximum expected value.
54    Co2TooHigh { value: u16, max: u16 },
55    /// Temperature is below minimum expected value.
56    TemperatureTooLow { value: f32, min: f32 },
57    /// Temperature is above maximum expected value.
58    TemperatureTooHigh { value: f32, max: f32 },
59    /// Pressure is below minimum expected value.
60    PressureTooLow { value: f32, min: f32 },
61    /// Pressure is above maximum expected value.
62    PressureTooHigh { value: f32, max: f32 },
63    /// Humidity is above 100%.
64    HumidityOutOfRange { value: u8 },
65    /// Battery level is above 100%.
66    BatteryOutOfRange { value: u8 },
67    /// CO2 is zero which may indicate sensor error.
68    Co2Zero,
69    /// All values are zero which may indicate communication error.
70    AllZeros,
71    /// Radon reading is above maximum expected value.
72    RadonTooHigh { value: u32, max: u32 },
73    /// Radiation rate is above maximum expected value.
74    RadiationRateTooHigh { value: f32, max: f32 },
75    /// Radiation total is above maximum expected value.
76    RadiationTotalTooHigh { value: f64, max: f64 },
77}
78
79impl std::fmt::Display for ValidationWarning {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        match self {
82            ValidationWarning::Co2TooLow { value, min } => {
83                write!(f, "CO2 {} ppm is below minimum {} ppm", value, min)
84            }
85            ValidationWarning::Co2TooHigh { value, max } => {
86                write!(f, "CO2 {} ppm exceeds maximum {} ppm", value, max)
87            }
88            ValidationWarning::TemperatureTooLow { value, min } => {
89                write!(f, "Temperature {}°C is below minimum {}°C", value, min)
90            }
91            ValidationWarning::TemperatureTooHigh { value, max } => {
92                write!(f, "Temperature {}°C exceeds maximum {}°C", value, max)
93            }
94            ValidationWarning::PressureTooLow { value, min } => {
95                write!(f, "Pressure {} hPa is below minimum {} hPa", value, min)
96            }
97            ValidationWarning::PressureTooHigh { value, max } => {
98                write!(f, "Pressure {} hPa exceeds maximum {} hPa", value, max)
99            }
100            ValidationWarning::HumidityOutOfRange { value } => {
101                write!(f, "Humidity {}% is out of valid range (0-100)", value)
102            }
103            ValidationWarning::BatteryOutOfRange { value } => {
104                write!(f, "Battery {}% is out of valid range (0-100)", value)
105            }
106            ValidationWarning::Co2Zero => {
107                write!(f, "CO2 reading is zero - possible sensor error")
108            }
109            ValidationWarning::AllZeros => {
110                write!(f, "All readings are zero - possible communication error")
111            }
112            ValidationWarning::RadonTooHigh { value, max } => {
113                write!(f, "Radon {} Bq/m³ exceeds maximum {} Bq/m³", value, max)
114            }
115            ValidationWarning::RadiationRateTooHigh { value, max } => {
116                write!(
117                    f,
118                    "Radiation rate {} µSv/h exceeds maximum {} µSv/h",
119                    value, max
120                )
121            }
122            ValidationWarning::RadiationTotalTooHigh { value, max } => {
123                write!(
124                    f,
125                    "Radiation total {} µSv exceeds maximum {} µSv",
126                    value, max
127                )
128            }
129        }
130    }
131}
132
133/// Result of validating a reading.
134#[derive(Debug, Clone)]
135pub struct ValidationResult {
136    /// Whether the reading passed validation.
137    pub is_valid: bool,
138    /// List of warnings (may be non-empty even if valid).
139    pub warnings: Vec<ValidationWarning>,
140}
141
142impl ValidationResult {
143    /// Create a successful validation result with no warnings.
144    pub fn valid() -> Self {
145        Self {
146            is_valid: true,
147            warnings: Vec::new(),
148        }
149    }
150
151    /// Create an invalid result with the given warnings.
152    pub fn invalid(warnings: Vec<ValidationWarning>) -> Self {
153        Self {
154            is_valid: false,
155            warnings,
156        }
157    }
158
159    /// Create a valid result with warnings.
160    pub fn valid_with_warnings(warnings: Vec<ValidationWarning>) -> Self {
161        Self {
162            is_valid: true,
163            warnings,
164        }
165    }
166
167    /// Check if there are any warnings.
168    pub fn has_warnings(&self) -> bool {
169        !self.warnings.is_empty()
170    }
171}
172
173/// Configuration for reading validation.
174#[derive(Debug, Clone)]
175pub struct ValidatorConfig {
176    /// Minimum expected CO2 value (ppm).
177    pub co2_min: u16,
178    /// Maximum expected CO2 value (ppm).
179    pub co2_max: u16,
180    /// Minimum expected temperature (°C).
181    pub temperature_min: f32,
182    /// Maximum expected temperature (°C).
183    pub temperature_max: f32,
184    /// Minimum expected pressure (hPa).
185    pub pressure_min: f32,
186    /// Maximum expected pressure (hPa).
187    pub pressure_max: f32,
188    /// Maximum expected radon value (Bq/m³).
189    pub radon_max: u32,
190    /// Maximum expected radiation rate (µSv/h).
191    pub radiation_rate_max: f32,
192    /// Maximum expected radiation total (mSv).
193    pub radiation_total_max: f64,
194    /// Treat CO2 = 0 as an error.
195    pub warn_on_zero_co2: bool,
196    /// Treat all zeros as an error.
197    pub warn_on_all_zeros: bool,
198}
199
200impl Default for ValidatorConfig {
201    fn default() -> Self {
202        Self {
203            co2_min: 300,   // Outdoor ambient is ~400 ppm
204            co2_max: 10000, // Very high but possible in some scenarios
205            temperature_min: -40.0,
206            temperature_max: 85.0,
207            pressure_min: 300.0,           // Very high altitude
208            pressure_max: 1100.0,          // Sea level or below
209            radon_max: 1000,               // WHO action level is 100-300 Bq/m³
210            radiation_rate_max: 100.0,     // Normal background is ~0.1-0.2 µSv/h
211            radiation_total_max: 100000.0, // Reasonable upper bound for accumulated dose
212            warn_on_zero_co2: true,
213            warn_on_all_zeros: true,
214        }
215    }
216}
217
218impl ValidatorConfig {
219    /// Create new validator config with defaults.
220    #[must_use]
221    pub fn new() -> Self {
222        Self::default()
223    }
224
225    /// Set minimum CO2 value (ppm).
226    #[must_use]
227    pub fn co2_min(mut self, min: u16) -> Self {
228        self.co2_min = min;
229        self
230    }
231
232    /// Set maximum CO2 value (ppm).
233    #[must_use]
234    pub fn co2_max(mut self, max: u16) -> Self {
235        self.co2_max = max;
236        self
237    }
238
239    /// Set CO2 range (min, max).
240    #[must_use]
241    pub fn co2_range(mut self, min: u16, max: u16) -> Self {
242        self.co2_min = min;
243        self.co2_max = max;
244        self
245    }
246
247    /// Set minimum temperature (°C).
248    #[must_use]
249    pub fn temperature_min(mut self, min: f32) -> Self {
250        self.temperature_min = min;
251        self
252    }
253
254    /// Set maximum temperature (°C).
255    #[must_use]
256    pub fn temperature_max(mut self, max: f32) -> Self {
257        self.temperature_max = max;
258        self
259    }
260
261    /// Set temperature range (min, max).
262    #[must_use]
263    pub fn temperature_range(mut self, min: f32, max: f32) -> Self {
264        self.temperature_min = min;
265        self.temperature_max = max;
266        self
267    }
268
269    /// Set minimum pressure (hPa).
270    #[must_use]
271    pub fn pressure_min(mut self, min: f32) -> Self {
272        self.pressure_min = min;
273        self
274    }
275
276    /// Set maximum pressure (hPa).
277    #[must_use]
278    pub fn pressure_max(mut self, max: f32) -> Self {
279        self.pressure_max = max;
280        self
281    }
282
283    /// Set pressure range (min, max).
284    #[must_use]
285    pub fn pressure_range(mut self, min: f32, max: f32) -> Self {
286        self.pressure_min = min;
287        self.pressure_max = max;
288        self
289    }
290
291    /// Set whether to warn on CO2 = 0.
292    #[must_use]
293    pub fn warn_on_zero_co2(mut self, warn: bool) -> Self {
294        self.warn_on_zero_co2 = warn;
295        self
296    }
297
298    /// Set whether to warn on all zeros.
299    #[must_use]
300    pub fn warn_on_all_zeros(mut self, warn: bool) -> Self {
301        self.warn_on_all_zeros = warn;
302        self
303    }
304
305    /// Set maximum radon value (Bq/m³).
306    #[must_use]
307    pub fn radon_max(mut self, max: u32) -> Self {
308        self.radon_max = max;
309        self
310    }
311
312    /// Set maximum radiation rate (µSv/h).
313    #[must_use]
314    pub fn radiation_rate_max(mut self, max: f32) -> Self {
315        self.radiation_rate_max = max;
316        self
317    }
318
319    /// Set maximum radiation total (mSv).
320    #[must_use]
321    pub fn radiation_total_max(mut self, max: f64) -> Self {
322        self.radiation_total_max = max;
323        self
324    }
325
326    /// Create strict validation config (narrow ranges).
327    pub fn strict() -> Self {
328        Self {
329            co2_min: 350,
330            co2_max: 5000,
331            temperature_min: -10.0,
332            temperature_max: 50.0,
333            pressure_min: 800.0,
334            pressure_max: 1100.0,
335            radon_max: 300, // WHO action level
336            radiation_rate_max: 10.0,
337            radiation_total_max: 10000.0,
338            warn_on_zero_co2: true,
339            warn_on_all_zeros: true,
340        }
341    }
342
343    /// Create relaxed validation config (wide ranges).
344    pub fn relaxed() -> Self {
345        Self {
346            co2_min: 0,
347            co2_max: 20000,
348            temperature_min: -50.0,
349            temperature_max: 100.0,
350            pressure_min: 200.0,
351            pressure_max: 1200.0,
352            radon_max: 5000,
353            radiation_rate_max: 1000.0,
354            radiation_total_max: 1000000.0,
355            warn_on_zero_co2: false,
356            warn_on_all_zeros: false,
357        }
358    }
359
360    /// Create validation config optimized for Aranet4 (CO2 sensor).
361    ///
362    /// Aranet4 measures CO2, temperature, humidity, and pressure.
363    /// This preset uses appropriate ranges for indoor air quality monitoring.
364    pub fn for_aranet4() -> Self {
365        Self {
366            co2_min: 300,   // Outdoor ambient is ~400 ppm
367            co2_max: 10000, // Aranet4 max range
368            temperature_min: -40.0,
369            temperature_max: 60.0, // Aranet4 operating range
370            pressure_min: 300.0,
371            pressure_max: 1100.0,
372            radon_max: 0,             // Not applicable
373            radiation_rate_max: 0.0,  // Not applicable
374            radiation_total_max: 0.0, // Not applicable
375            warn_on_zero_co2: true,
376            warn_on_all_zeros: true,
377        }
378    }
379
380    /// Create validation config optimized for Aranet2 (temperature/humidity sensor).
381    ///
382    /// Aranet2 measures only temperature and humidity.
383    /// CO2 and pressure validation is disabled.
384    ///
385    /// Note: This preset is based on device specifications. Actual testing
386    /// with an Aranet2 device may reveal adjustments needed.
387    pub fn for_aranet2() -> Self {
388        Self {
389            co2_min: 0,     // Not applicable - disable CO2 validation
390            co2_max: 65535, // Not applicable
391            temperature_min: -40.0,
392            temperature_max: 60.0,
393            pressure_min: 0.0,        // Not applicable
394            pressure_max: 2000.0,     // Not applicable
395            radon_max: 0,             // Not applicable
396            radiation_rate_max: 0.0,  // Not applicable
397            radiation_total_max: 0.0, // Not applicable
398            warn_on_zero_co2: false,  // CO2 is not measured
399            warn_on_all_zeros: false,
400        }
401    }
402
403    /// Create validation config optimized for AranetRn+ (radon sensor).
404    ///
405    /// AranetRn+ measures radon, temperature, humidity, and pressure.
406    /// CO2 validation is disabled.
407    ///
408    /// Note: This preset is based on device specifications. Actual testing
409    /// with an AranetRn+ device may reveal adjustments needed.
410    pub fn for_aranet_radon() -> Self {
411        Self {
412            co2_min: 0,     // Not applicable
413            co2_max: 65535, // Not applicable
414            temperature_min: -40.0,
415            temperature_max: 60.0,
416            pressure_min: 300.0,
417            pressure_max: 1100.0,
418            radon_max: 1000,          // WHO action level is 100-300 Bq/m³
419            radiation_rate_max: 0.0,  // Not applicable
420            radiation_total_max: 0.0, // Not applicable
421            warn_on_zero_co2: false,
422            warn_on_all_zeros: false,
423        }
424    }
425
426    /// Create validation config optimized for Aranet Radiation sensor.
427    ///
428    /// Aranet Radiation measures gamma radiation rate and accumulated dose.
429    /// CO2 and radon validation is disabled.
430    ///
431    /// Note: This preset is based on device specifications. Actual testing
432    /// with an Aranet Radiation device may reveal adjustments needed.
433    pub fn for_aranet_radiation() -> Self {
434        Self {
435            co2_min: 0,     // Not applicable
436            co2_max: 65535, // Not applicable
437            temperature_min: -40.0,
438            temperature_max: 60.0,
439            pressure_min: 300.0,
440            pressure_max: 1100.0,
441            radon_max: 0,                  // Not applicable
442            radiation_rate_max: 100.0,     // Normal background is ~0.1-0.2 µSv/h
443            radiation_total_max: 100000.0, // Reasonable upper bound
444            warn_on_zero_co2: false,
445            warn_on_all_zeros: false,
446        }
447    }
448
449    /// Create validation config for a specific device type.
450    ///
451    /// Automatically selects the appropriate preset based on the device type:
452    /// - [`DeviceType::Aranet4`] → [`for_aranet4()`](Self::for_aranet4)
453    /// - [`DeviceType::Aranet2`] → [`for_aranet2()`](Self::for_aranet2)
454    /// - [`DeviceType::AranetRadon`] → [`for_aranet_radon()`](Self::for_aranet_radon)
455    /// - [`DeviceType::AranetRadiation`] → [`for_aranet_radiation()`](Self::for_aranet_radiation)
456    /// - Unknown types → default config
457    ///
458    /// # Example
459    ///
460    /// ```
461    /// use aranet_core::validation::ValidatorConfig;
462    /// use aranet_types::DeviceType;
463    ///
464    /// let config = ValidatorConfig::for_device(DeviceType::Aranet4);
465    /// assert_eq!(config.co2_max, 10000);
466    ///
467    /// let config = ValidatorConfig::for_device(DeviceType::AranetRadon);
468    /// assert_eq!(config.radon_max, 1000);
469    /// ```
470    #[must_use]
471    pub fn for_device(device_type: DeviceType) -> Self {
472        match device_type {
473            DeviceType::Aranet4 => Self::for_aranet4(),
474            DeviceType::Aranet2 => Self::for_aranet2(),
475            DeviceType::AranetRadon => Self::for_aranet_radon(),
476            DeviceType::AranetRadiation => Self::for_aranet_radiation(),
477            _ => Self::default(),
478        }
479    }
480}
481
482/// Validator for sensor readings.
483#[derive(Debug, Clone, Default)]
484pub struct ReadingValidator {
485    config: ValidatorConfig,
486}
487
488impl ReadingValidator {
489    /// Create a new validator with the given configuration.
490    pub fn new(config: ValidatorConfig) -> Self {
491        Self { config }
492    }
493
494    /// Get the configuration.
495    pub fn config(&self) -> &ValidatorConfig {
496        &self.config
497    }
498
499    /// Validate a sensor reading.
500    pub fn validate(&self, reading: &CurrentReading) -> ValidationResult {
501        let mut warnings = Vec::new();
502
503        // Check for all zeros (use approximate comparison for floats)
504        if self.config.warn_on_all_zeros
505            && reading.co2 == 0
506            && reading.temperature.abs() < f32::EPSILON
507            && reading.pressure.abs() < f32::EPSILON
508            && reading.humidity == 0
509        {
510            warnings.push(ValidationWarning::AllZeros);
511            return ValidationResult::invalid(warnings);
512        }
513
514        // Check CO2
515        if reading.co2 > 0 {
516            if reading.co2 < self.config.co2_min {
517                warnings.push(ValidationWarning::Co2TooLow {
518                    value: reading.co2,
519                    min: self.config.co2_min,
520                });
521            }
522            if reading.co2 > self.config.co2_max {
523                warnings.push(ValidationWarning::Co2TooHigh {
524                    value: reading.co2,
525                    max: self.config.co2_max,
526                });
527            }
528        } else if self.config.warn_on_zero_co2 {
529            warnings.push(ValidationWarning::Co2Zero);
530        }
531
532        // Check temperature
533        if reading.temperature < self.config.temperature_min {
534            warnings.push(ValidationWarning::TemperatureTooLow {
535                value: reading.temperature,
536                min: self.config.temperature_min,
537            });
538        }
539        if reading.temperature > self.config.temperature_max {
540            warnings.push(ValidationWarning::TemperatureTooHigh {
541                value: reading.temperature,
542                max: self.config.temperature_max,
543            });
544        }
545
546        // Check pressure (skip if 0, might be Aranet2)
547        if reading.pressure > 0.0 {
548            if reading.pressure < self.config.pressure_min {
549                warnings.push(ValidationWarning::PressureTooLow {
550                    value: reading.pressure,
551                    min: self.config.pressure_min,
552                });
553            }
554            if reading.pressure > self.config.pressure_max {
555                warnings.push(ValidationWarning::PressureTooHigh {
556                    value: reading.pressure,
557                    max: self.config.pressure_max,
558                });
559            }
560        }
561
562        // Check humidity
563        if reading.humidity > 100 {
564            warnings.push(ValidationWarning::HumidityOutOfRange {
565                value: reading.humidity,
566            });
567        }
568
569        // Check battery
570        if reading.battery > 100 {
571            warnings.push(ValidationWarning::BatteryOutOfRange {
572                value: reading.battery,
573            });
574        }
575
576        // Check radon (if present)
577        if let Some(radon) = reading.radon
578            && radon > self.config.radon_max
579        {
580            warnings.push(ValidationWarning::RadonTooHigh {
581                value: radon,
582                max: self.config.radon_max,
583            });
584        }
585
586        // Check radiation rate (if present)
587        if let Some(rate) = reading.radiation_rate
588            && rate > self.config.radiation_rate_max
589        {
590            warnings.push(ValidationWarning::RadiationRateTooHigh {
591                value: rate,
592                max: self.config.radiation_rate_max,
593            });
594        }
595
596        // Check radiation total (if present)
597        if let Some(total) = reading.radiation_total
598            && total > self.config.radiation_total_max
599        {
600            warnings.push(ValidationWarning::RadiationTotalTooHigh {
601                value: total,
602                max: self.config.radiation_total_max,
603            });
604        }
605
606        if warnings.is_empty() {
607            ValidationResult::valid()
608        } else {
609            // Determine if any warnings are critical
610            let has_critical = warnings.iter().any(|w| {
611                matches!(
612                    w,
613                    ValidationWarning::AllZeros
614                        | ValidationWarning::Co2TooHigh { .. }
615                        | ValidationWarning::TemperatureTooHigh { .. }
616                        | ValidationWarning::RadonTooHigh { .. }
617                        | ValidationWarning::RadiationRateTooHigh { .. }
618                )
619            });
620
621            if has_critical {
622                ValidationResult::invalid(warnings)
623            } else {
624                ValidationResult::valid_with_warnings(warnings)
625            }
626        }
627    }
628
629    /// Quick check if a CO2 value is within expected range.
630    pub fn is_co2_valid(&self, co2: u16) -> bool {
631        co2 >= self.config.co2_min && co2 <= self.config.co2_max
632    }
633
634    /// Quick check if a temperature value is within expected range.
635    pub fn is_temperature_valid(&self, temp: f32) -> bool {
636        temp >= self.config.temperature_min && temp <= self.config.temperature_max
637    }
638}
639
640#[cfg(test)]
641mod tests {
642    use super::*;
643    use aranet_types::Status;
644
645    fn make_reading(co2: u16, temp: f32, pressure: f32, humidity: u8) -> CurrentReading {
646        CurrentReading {
647            co2,
648            temperature: temp,
649            pressure,
650            humidity,
651            battery: 80,
652            status: Status::Green,
653            interval: 300,
654            age: 60,
655            captured_at: None,
656            radon: None,
657            radiation_rate: None,
658            radiation_total: None,
659            radon_avg_24h: None,
660            radon_avg_7d: None,
661            radon_avg_30d: None,
662        }
663    }
664
665    #[test]
666    fn test_valid_reading() {
667        let validator = ReadingValidator::default();
668        let reading = make_reading(800, 22.5, 1013.2, 50);
669        let result = validator.validate(&reading);
670        assert!(result.is_valid);
671        assert!(result.warnings.is_empty());
672    }
673
674    #[test]
675    fn test_co2_too_high() {
676        let validator = ReadingValidator::default();
677        let reading = make_reading(15000, 22.5, 1013.2, 50);
678        let result = validator.validate(&reading);
679        assert!(!result.is_valid);
680        assert!(
681            result
682                .warnings
683                .iter()
684                .any(|w| matches!(w, ValidationWarning::Co2TooHigh { .. }))
685        );
686    }
687
688    #[test]
689    fn test_all_zeros() {
690        let validator = ReadingValidator::default();
691        let reading = make_reading(0, 0.0, 0.0, 0);
692        let result = validator.validate(&reading);
693        assert!(!result.is_valid);
694        assert!(
695            result
696                .warnings
697                .iter()
698                .any(|w| matches!(w, ValidationWarning::AllZeros))
699        );
700    }
701
702    #[test]
703    fn test_humidity_out_of_range() {
704        let validator = ReadingValidator::default();
705        let reading = make_reading(800, 22.5, 1013.2, 150);
706        let result = validator.validate(&reading);
707        assert!(result.has_warnings());
708        assert!(
709            result
710                .warnings
711                .iter()
712                .any(|w| matches!(w, ValidationWarning::HumidityOutOfRange { .. }))
713        );
714    }
715
716    #[test]
717    fn test_for_device_aranet4() {
718        let config = ValidatorConfig::for_device(DeviceType::Aranet4);
719        assert_eq!(config.co2_min, 300);
720        assert_eq!(config.co2_max, 10000);
721        assert!(config.warn_on_zero_co2);
722    }
723
724    #[test]
725    fn test_for_device_aranet2() {
726        let config = ValidatorConfig::for_device(DeviceType::Aranet2);
727        assert_eq!(config.co2_min, 0); // CO2 validation disabled
728        assert!(!config.warn_on_zero_co2);
729    }
730
731    #[test]
732    fn test_for_device_aranet_radon() {
733        let config = ValidatorConfig::for_device(DeviceType::AranetRadon);
734        assert_eq!(config.radon_max, 1000);
735        assert!(!config.warn_on_zero_co2);
736    }
737
738    #[test]
739    fn test_for_device_aranet_radiation() {
740        let config = ValidatorConfig::for_device(DeviceType::AranetRadiation);
741        assert_eq!(config.radiation_rate_max, 100.0);
742        assert_eq!(config.radiation_total_max, 100000.0);
743        assert!(!config.warn_on_zero_co2);
744    }
745}