use core::fmt; #[derive(Debug, PartialEq)] pub enum AlertPinSelect { /// ALERT pin reflects the status of the alert flags Alert, /// ALERT pin reflects the status of the data ready flag DataReady, } impl fmt::Display for AlertPinSelect { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Alert => write!(f, "Alert"), Self::DataReady => write!(f, "Data Ready"), } } } #[derive(Debug, PartialEq)] pub enum PinPolarity { /// ALERT pin is active low ActiveLow, /// ALERT pin is active high ActiveHigh, } impl fmt::Display for PinPolarity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::ActiveLow => write!(f, "Active Low"), Self::ActiveHigh => write!(f, "Active High"), } } } #[derive(Debug, PartialEq)] pub enum Mode { /// Alert mode Alert, /// Thermal mode Thermal, } impl fmt::Display for Mode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Alert => write!(f, "Alert"), Self::Thermal => write!(f, "Thermal"), } } } #[derive(Debug, PartialEq)] pub enum ConversionMode { /// Continous conversion Continuous, /// Shutdown Shutdown, /// One-shot conversion Oneshot, } impl fmt::Display for ConversionMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Continuous => write!(f, "Continuous"), Self::Shutdown => write!(f, "Shutdown"), Self::Oneshot => write!(f, "One-shot"), } } } /// Determines the number of conversion results that are collected and averaged before updating the temperature register. The average is an accumulated average, not a running average. #[derive(Debug, PartialEq)] pub enum ConversionAveragingMode { /// No averaging None, /// 8 averaged conversions Avg8, /// 32 averaged conversions Avg32, /// 64 averaged conversions Avg64, } impl ConversionAveragingMode { fn minimum_conversion_cycle_time(&self) -> ConversionCycle { match self { ConversionAveragingMode::None => ConversionCycle::Microseconds15500, ConversionAveragingMode::Avg8 => ConversionCycle::Milliseconds125, ConversionAveragingMode::Avg32 => ConversionCycle::Milliseconds500, ConversionAveragingMode::Avg64 => ConversionCycle::Seconds1, } } } impl fmt::Display for ConversionAveragingMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::None => write!(f, "None"), Self::Avg8 => write!(f, "8 conversions"), Self::Avg32 => write!(f, "32 conversions"), Self::Avg64 => write!(f, "64 conversions"), } } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum ConversionCycle { Microseconds15500, Milliseconds125, Milliseconds250, Milliseconds500, Seconds1, Seconds4, Seconds8, Seconds16, } impl fmt::Display for ConversionCycle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Microseconds15500 => write!(f, "15.5ms"), Self::Milliseconds125 => write!(f, "125ms"), Self::Milliseconds250 => write!(f, "250ms"), Self::Milliseconds500 => write!(f, "500ms"), Self::Seconds1 => write!(f, "1s"), Self::Seconds4 => write!(f, "4s"), Self::Seconds8 => write!(f, "8s"), Self::Seconds16 => write!(f, "16s"), } } } pub enum ConfigurationError { CycleTimeAndAveragingModeAreIncompatible, } pub struct Configuration { /// ALERT pin select bit pub alert_pin_select: AlertPinSelect, /// ALERT pin polarity bit pub alert_pin_polarity: PinPolarity, /// Therm/alert mode select pub mode: Mode, /// Conversion averaging modes pub conversion_averaging_mode: ConversionAveragingMode, /// Conversion cycle bit pub conversion_cycle: ConversionCycle, /// Conversion mode pub conversion_mode: ConversionMode, } impl Configuration { pub fn validate(&self) -> Result<(), ConfigurationError> { if self.conversion_cycle < self .conversion_averaging_mode .minimum_conversion_cycle_time() { Err(ConfigurationError::CycleTimeAndAveragingModeAreIncompatible) } else { Ok(()) } } } impl fmt::Display for Configuration { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "Alert Pin Select: {}", self.alert_pin_select)?; writeln!(f, "Alert Pin Polarity: {}", self.alert_pin_polarity)?; writeln!(f, "Mode: {}", self.mode)?; writeln!( f, "Conversion Averaging: {}", self.conversion_averaging_mode )?; writeln!(f, "Conversion Cycle: {}", self.conversion_cycle)?; write!(f, "Conversion Mode: {}", self.conversion_mode) } } impl From<&Configuration> for u16 { fn from(configuration: &Configuration) -> u16 { let mut bits: u16 = 0; bits |= match configuration.alert_pin_select { AlertPinSelect::Alert => 0 << 2, AlertPinSelect::DataReady => 1 << 2, }; bits |= match configuration.alert_pin_polarity { PinPolarity::ActiveLow => 0 << 3, PinPolarity::ActiveHigh => 1 << 3, }; bits |= match configuration.mode { Mode::Alert => 0 << 4, Mode::Thermal => 1 << 4, }; bits |= match configuration.conversion_averaging_mode { ConversionAveragingMode::None => 0b00 << 5, ConversionAveragingMode::Avg8 => 0b01 << 5, ConversionAveragingMode::Avg32 => 0b10 << 5, ConversionAveragingMode::Avg64 => 0b11 << 5, }; bits |= match configuration.conversion_cycle { ConversionCycle::Microseconds15500 => 0b000 << 7, ConversionCycle::Milliseconds125 => 0b001 << 7, ConversionCycle::Milliseconds250 => 0b010 << 7, ConversionCycle::Milliseconds500 => 0b011 << 7, ConversionCycle::Seconds1 => 0b100 << 7, ConversionCycle::Seconds4 => 0b101 << 7, ConversionCycle::Seconds8 => 0b110 << 7, ConversionCycle::Seconds16 => 0b111 << 7, }; bits |= match configuration.conversion_mode { ConversionMode::Continuous => 0b00 << 10, ConversionMode::Shutdown => 0b01 << 10, ConversionMode::Oneshot => 0b11 << 10, }; bits } } impl From for Configuration { fn from(register: u16) -> Self { let alert_pin_select = (register >> 2) & 0x1; let alert_pin_polarity = (register >> 3) & 0x1; let mode = (register >> 4) & 0x1; let conversion_averaging_mode = (register >> 5) & 0x3; let conversion_cycle = (register >> 7) & 0x7; let conversion_mode = (register >> 10) & 0x3; let alert_pin_select = match alert_pin_select { 0 => AlertPinSelect::Alert, 1 => AlertPinSelect::DataReady, _ => todo!("Handle unsupported alert pin select"), }; let alert_pin_polarity = match alert_pin_polarity { 0 => PinPolarity::ActiveLow, 1 => PinPolarity::ActiveHigh, _ => todo!("Handle unsupported alert pin polarity"), }; let mode = match mode { 0 => Mode::Alert, 1 => Mode::Thermal, _ => todo!("Handle unsupported mode"), }; let conversion_averaging_mode = match conversion_averaging_mode { 0b00 => ConversionAveragingMode::None, 0b01 => ConversionAveragingMode::Avg8, 0b10 => ConversionAveragingMode::Avg32, 0b11 => ConversionAveragingMode::Avg64, _ => todo!("Handle unsupported conversion averaging mode"), }; let conversion_cycle = match conversion_cycle { 0b000 => ConversionCycle::Microseconds15500, 0b001 => ConversionCycle::Milliseconds125, 0b010 => ConversionCycle::Milliseconds250, 0b011 => ConversionCycle::Milliseconds500, 0b100 => ConversionCycle::Seconds1, 0b101 => ConversionCycle::Seconds4, 0b110 => ConversionCycle::Seconds8, 0b111 => ConversionCycle::Seconds16, _ => todo!("Handle unsupported conversion cycle"), }; let conversion_mode = match conversion_mode { 0b00 | 0b10 => ConversionMode::Continuous, 0b01 => ConversionMode::Shutdown, 0b11 => ConversionMode::Oneshot, _ => todo!("Handle unsupported conversion mode"), }; Self { alert_pin_select, alert_pin_polarity, mode, conversion_averaging_mode, conversion_cycle, conversion_mode, } } } #[cfg(test)] mod tests { use super::*; // helpers to construct common configurations fn default_configuration() -> Configuration { Configuration { alert_pin_select: AlertPinSelect::Alert, alert_pin_polarity: PinPolarity::ActiveLow, mode: Mode::Alert, conversion_averaging_mode: ConversionAveragingMode::Avg8, conversion_cycle: ConversionCycle::Seconds1, conversion_mode: ConversionMode::Continuous, } } // --- From<&Configuration> for u16 --- #[test] fn test_alert_pin_select_alert_bit() { let mut config = default_configuration(); config.alert_pin_select = AlertPinSelect::Alert; assert_eq!(u16::from(&config) & (1 << 2), 0); } #[test] fn test_alert_pin_select_data_ready_bit() { let mut config = default_configuration(); config.alert_pin_select = AlertPinSelect::DataReady; assert_ne!(u16::from(&config) & (1 << 2), 0); } #[test] fn test_pin_polarity_active_low_bit() { let mut config = default_configuration(); config.alert_pin_polarity = PinPolarity::ActiveLow; assert_eq!(u16::from(&config) & (1 << 3), 0); } #[test] fn test_pin_polarity_active_high_bit() { let mut config = default_configuration(); config.alert_pin_polarity = PinPolarity::ActiveHigh; assert_ne!(u16::from(&config) & (1 << 3), 0); } #[test] fn test_mode_alert_bit() { let mut config = default_configuration(); config.mode = Mode::Alert; assert_eq!(u16::from(&config) & (1 << 4), 0); } #[test] fn test_mode_thermal_bit() { let mut config = default_configuration(); config.mode = Mode::Thermal; assert_ne!(u16::from(&config) & (1 << 4), 0); } #[test] fn test_conversion_averaging_mode_bits() { let mut config = default_configuration(); config.conversion_averaging_mode = ConversionAveragingMode::None; assert_eq!((u16::from(&config) >> 5) & 0x3, 0b00); config.conversion_averaging_mode = ConversionAveragingMode::Avg8; assert_eq!((u16::from(&config) >> 5) & 0x3, 0b01); config.conversion_averaging_mode = ConversionAveragingMode::Avg32; assert_eq!((u16::from(&config) >> 5) & 0x3, 0b10); config.conversion_averaging_mode = ConversionAveragingMode::Avg64; assert_eq!((u16::from(&config) >> 5) & 0x3, 0b11); } #[test] fn test_conversion_cycle_bits() { let mut config = default_configuration(); config.conversion_cycle = ConversionCycle::Microseconds15500; assert_eq!((u16::from(&config) >> 7) & 0x7, 0b000); config.conversion_cycle = ConversionCycle::Milliseconds125; assert_eq!((u16::from(&config) >> 7) & 0x7, 0b001); config.conversion_cycle = ConversionCycle::Milliseconds250; assert_eq!((u16::from(&config) >> 7) & 0x7, 0b010); config.conversion_cycle = ConversionCycle::Milliseconds500; assert_eq!((u16::from(&config) >> 7) & 0x7, 0b011); config.conversion_cycle = ConversionCycle::Seconds1; assert_eq!((u16::from(&config) >> 7) & 0x7, 0b100); config.conversion_cycle = ConversionCycle::Seconds4; assert_eq!((u16::from(&config) >> 7) & 0x7, 0b101); config.conversion_cycle = ConversionCycle::Seconds8; assert_eq!((u16::from(&config) >> 7) & 0x7, 0b110); config.conversion_cycle = ConversionCycle::Seconds16; assert_eq!((u16::from(&config) >> 7) & 0x7, 0b111); } #[test] fn test_conversion_cycle_ordering() { assert!(ConversionCycle::Microseconds15500 < ConversionCycle::Milliseconds125); assert!(ConversionCycle::Milliseconds125 < ConversionCycle::Milliseconds250); assert!(ConversionCycle::Milliseconds250 < ConversionCycle::Milliseconds500); assert!(ConversionCycle::Milliseconds500 < ConversionCycle::Seconds1); assert!(ConversionCycle::Seconds1 < ConversionCycle::Seconds4); assert!(ConversionCycle::Seconds4 < ConversionCycle::Seconds8); assert!(ConversionCycle::Seconds8 < ConversionCycle::Seconds16); } #[test] fn test_conversion_mode_bits() { let mut config = default_configuration(); config.conversion_mode = ConversionMode::Continuous; assert_eq!((u16::from(&config) >> 10) & 0x3, 0b00); config.conversion_mode = ConversionMode::Shutdown; assert_eq!((u16::from(&config) >> 10) & 0x3, 0b01); config.conversion_mode = ConversionMode::Oneshot; assert_eq!((u16::from(&config) >> 10) & 0x3, 0b11); } // --- From for Configuration --- #[test] fn test_configuration_from_factory_reset_value() { // 0x0220 = 0b0000_0010_0010_0000 // bits 6:5 = 0b01 -> Avg8 // bits 11:10 = 0b00 -> Continuous let config = Configuration::from(0x0220); assert_eq!(config.alert_pin_select, AlertPinSelect::Alert); assert_eq!(config.alert_pin_polarity, PinPolarity::ActiveLow); assert_eq!(config.mode, Mode::Alert); assert_eq!( config.conversion_averaging_mode, ConversionAveragingMode::Avg8 ); assert_eq!(config.conversion_mode, ConversionMode::Continuous); } #[test] fn test_configuration_from_thermal_mode() { let register: u16 = 1 << 4; let config = Configuration::from(register); assert_eq!(config.mode, Mode::Thermal); } #[test] fn test_configuration_from_shutdown_mode() { let register: u16 = 0b01 << 10; let config = Configuration::from(register); assert_eq!(config.conversion_mode, ConversionMode::Shutdown); } // --- Round-trip --- #[test] fn test_round_trip_factory_reset() { let original: u16 = 0x0220; let config = Configuration::from(original); let restored = u16::from(&config); assert_eq!(original, restored); } #[test] fn test_round_trip_all_fields_set() { let config = Configuration { alert_pin_select: AlertPinSelect::DataReady, alert_pin_polarity: PinPolarity::ActiveHigh, mode: Mode::Thermal, conversion_averaging_mode: ConversionAveragingMode::Avg64, conversion_cycle: ConversionCycle::Seconds1, conversion_mode: ConversionMode::Oneshot, }; let bits = u16::from(&config); let restored = Configuration::from(bits); assert_eq!(restored.alert_pin_select, AlertPinSelect::DataReady); assert_eq!(restored.alert_pin_polarity, PinPolarity::ActiveHigh); assert_eq!(restored.mode, Mode::Thermal); assert_eq!( restored.conversion_averaging_mode, ConversionAveragingMode::Avg64 ); assert_eq!(restored.conversion_cycle, ConversionCycle::Seconds1); assert_eq!(restored.conversion_mode, ConversionMode::Oneshot); } #[test] fn test_validate_valid_combinations() { let mut config = default_configuration(); // None allows any cycle time config.conversion_averaging_mode = ConversionAveragingMode::None; config.conversion_cycle = ConversionCycle::Microseconds15500; assert!(config.validate().is_ok()); // Avg8 allows 125ms and above config.conversion_averaging_mode = ConversionAveragingMode::Avg8; config.conversion_cycle = ConversionCycle::Milliseconds125; assert!(config.validate().is_ok()); // Avg32 allows 500ms and above config.conversion_averaging_mode = ConversionAveragingMode::Avg32; config.conversion_cycle = ConversionCycle::Milliseconds500; assert!(config.validate().is_ok()); // Avg64 allows 1s and above config.conversion_averaging_mode = ConversionAveragingMode::Avg64; config.conversion_cycle = ConversionCycle::Seconds1; assert!(config.validate().is_ok()); } #[test] fn test_validate_invalid_combinations() { let mut config = default_configuration(); // Avg8 does not allow 15.5ms config.conversion_averaging_mode = ConversionAveragingMode::Avg8; config.conversion_cycle = ConversionCycle::Microseconds15500; assert!(config.validate().is_err()); // Avg32 does not allow 125ms or 250ms config.conversion_averaging_mode = ConversionAveragingMode::Avg32; config.conversion_cycle = ConversionCycle::Milliseconds125; assert!(config.validate().is_err()); config.conversion_cycle = ConversionCycle::Milliseconds250; assert!(config.validate().is_err()); // Avg64 does not allow anything below 1s config.conversion_averaging_mode = ConversionAveragingMode::Avg64; config.conversion_cycle = ConversionCycle::Milliseconds500; assert!(config.validate().is_err()); } #[test] fn test_alert_pin_select_display() { assert_eq!(format!("{}", AlertPinSelect::Alert), "Alert"); assert_eq!(format!("{}", AlertPinSelect::DataReady), "Data Ready"); } #[test] fn test_pin_polarity_display() { assert_eq!(format!("{}", PinPolarity::ActiveLow), "Active Low"); assert_eq!(format!("{}", PinPolarity::ActiveHigh), "Active High"); } #[test] fn test_mode_display() { assert_eq!(format!("{}", Mode::Alert), "Alert"); assert_eq!(format!("{}", Mode::Thermal), "Thermal"); } #[test] fn test_conversion_mode_display() { assert_eq!(format!("{}", ConversionMode::Continuous), "Continuous"); assert_eq!(format!("{}", ConversionMode::Shutdown), "Shutdown"); assert_eq!(format!("{}", ConversionMode::Oneshot), "One-shot"); } #[test] fn test_conversion_averaging_mode_display() { assert_eq!(format!("{}", ConversionAveragingMode::None), "None"); assert_eq!( format!("{}", ConversionAveragingMode::Avg8), "8 conversions" ); assert_eq!( format!("{}", ConversionAveragingMode::Avg32), "32 conversions" ); assert_eq!( format!("{}", ConversionAveragingMode::Avg64), "64 conversions" ); } #[test] fn test_conversion_cycle_display() { assert_eq!(format!("{}", ConversionCycle::Microseconds15500), "15.5ms"); assert_eq!(format!("{}", ConversionCycle::Milliseconds125), "125ms"); assert_eq!(format!("{}", ConversionCycle::Milliseconds250), "250ms"); assert_eq!(format!("{}", ConversionCycle::Milliseconds500), "500ms"); assert_eq!(format!("{}", ConversionCycle::Seconds1), "1s"); assert_eq!(format!("{}", ConversionCycle::Seconds4), "4s"); assert_eq!(format!("{}", ConversionCycle::Seconds8), "8s"); assert_eq!(format!("{}", ConversionCycle::Seconds16), "16s"); } #[test] fn test_configuration_display() { let config = Configuration { alert_pin_select: AlertPinSelect::Alert, alert_pin_polarity: PinPolarity::ActiveLow, mode: Mode::Thermal, conversion_averaging_mode: ConversionAveragingMode::Avg8, conversion_cycle: ConversionCycle::Seconds1, conversion_mode: ConversionMode::Continuous, }; let output = format!("{}", config); assert!(output.contains("Alert")); assert!(output.contains("Active Low")); assert!(output.contains("Thermal")); assert!(output.contains("8 conversions")); assert!(output.contains("1s")); assert!(output.contains("Continuous")); } }