diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b85c5c5..0c55ff1 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -209,6 +209,9 @@ jobs: - name: cargo test (blocking) run: cargo test --locked + - name: cargo test (embedded-sensors-hal) + run: cargo test --locked -F embedded-sensors-hal + - name: cargo test (async) run: cargo test --locked -F async diff --git a/Cargo.lock b/Cargo.lock index 6a67544..3bc50b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -305,15 +305,18 @@ dependencies = [ [[package]] name = "embedded-sensors-hal" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "971cd616a2326c63f660375e485e2f4573575b0bd293d228d7817c2b07be3475" +checksum = "8c703756bee31e7aaf55d8fb6dcf7337cfc231cfb4a3ad34b9df509846fd9001" +dependencies = [ + "paste", +] [[package]] name = "embedded-sensors-hal-async" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51c524a78b2804eca0d9ec05154e51d9af948b40cd0a6bbcc4d5832ff7e47b5b" +checksum = "3e51dce2828cbbbca20bd69857628584deccae0fa4e7724f3fceb5d197bdffb0" dependencies = [ "embedded-sensors-hal", "paste", diff --git a/Cargo.toml b/Cargo.toml index 9de7b2f..90382e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,8 +23,8 @@ rust-version = "1.85.0" device-driver = { version = "1.0.7", default-features = false, features = ["toml"] } embedded-hal = "1.0.0" embedded-hal-async = { version = "1.0.0", optional = true } -embedded-sensors-hal = { version = "0.1.0", optional = true } -embedded-sensors-hal-async = { version = "0.3.0", optional = true } +embedded-sensors-hal = { version = "0.1.1", optional = true } +embedded-sensors-hal-async = { version = "0.4.0", optional = true } maybe-async-cfg = "0.2.5" [features] diff --git a/src/lib.rs b/src/lib.rs index ff70f80..e6bb258 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -518,6 +518,51 @@ impl embedded_sensors_hal::temperature::Temperature } } +#[cfg(all(feature = "embedded-sensors-hal", not(feature = "async")))] +impl embedded_sensors_hal::temperature::TemperatureThresholdSet for Tmp108 { + fn set_temperature_threshold_low( + &mut self, + threshold: embedded_sensors_hal::temperature::DegreesCelsius, + ) -> Result<(), Self::Error> { + self.set_low_limit(threshold).map_err(Error::Bus) + } + + fn set_temperature_threshold_high( + &mut self, + threshold: embedded_sensors_hal::temperature::DegreesCelsius, + ) -> Result<(), Self::Error> { + self.set_high_limit(threshold).map_err(Error::Bus) + } +} + +#[cfg(all(feature = "embedded-sensors-hal", not(feature = "async")))] +impl embedded_sensors_hal::temperature::TemperatureHysteresis for Tmp108 { + fn set_temperature_threshold_hysteresis( + &mut self, + hysteresis: embedded_sensors_hal::temperature::DegreesCelsius, + ) -> Result<(), Self::Error> { + // Trait method takes a continuous range of f32 values as argument, but internally driver + // only accepts 4 discrete values for hysteresis. + // + // We ensure only a correct value for hysteresis is passed in, and return error otherwise. + let hysteresis = if (hysteresis - 0.0).abs() < f32::EPSILON { + Hysteresis::_0C + } else if (hysteresis - 1.0).abs() < f32::EPSILON { + Hysteresis::_1C + } else if (hysteresis - 2.0).abs() < f32::EPSILON { + Hysteresis::_2C + } else if (hysteresis - 4.0).abs() < f32::EPSILON { + Hysteresis::_4C + } else { + return Err(Error::InvalidInput); + }; + + let mut config = self.read_configuration().map_err(|_| Error::Other)?; + config.hysteresis = hysteresis; + self.configure(config).map_err(Error::Bus) + } +} + #[cfg(all(feature = "embedded-sensors-hal-async", feature = "async"))] impl embedded_sensors_hal_async::sensor::Error for Error { fn kind(&self) -> embedded_sensors_hal_async::sensor::ErrorKind { @@ -951,6 +996,128 @@ mod tests { let mut mock = tmp108.destroy(); mock.done(); } + + #[cfg(feature = "embedded-sensors-hal")] + #[test] + fn handle_threshold_settings() { + use embedded_sensors_hal::temperature::TemperatureThresholdSet; + + // I2C transaction expectations + // Temperature Register = 0x00 + // Configuration Register = 0x01 + // Low Limit Register = 0x02 + // High Limit Register = 0x03 + let expectations = vec![ + // Configure: Read current config + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x10]), + // Configure: Write new config (Interrupt mode, Active Low) + Transaction::write(0x48, vec![0x01, 0x26, 0x10]), + // Set low threshold to 25.0 C + Transaction::write(0x48, vec![0x02, 0x19, 0x00]), + // Set high threshold to 80.0 C + Transaction::write(0x48, vec![0x03, 0x50, 0x00]), + // Set low threshold to 20.0 C (0x1400) + Transaction::write(0x48, vec![0x02, 0x14, 0x00]), + // Set high threshold to 85.0 C (0x5500) + Transaction::write(0x48, vec![0x03, 0x55, 0x00]), + ]; + + let mock = Mock::new(&expectations); + let mut tmp108 = Tmp108::new_with_a0_gnd(mock); + + // Configure as active-low interrupt mode + let cfg = Config { + thermostat_mode: Thermostat::Interrupt, + alert_polarity: Polarity::ActiveLow, + ..Default::default() + }; + + let result = tmp108.configure(cfg); + assert!(result.is_ok()); + + let result = tmp108.set_temperature_threshold_low(25.0); + assert!(result.is_ok()); + let result = tmp108.set_temperature_threshold_high(80.0); + assert!(result.is_ok()); + + // Set thresholds again with different values + let result = tmp108.set_temperature_threshold_low(20.0); + assert!(result.is_ok()); + let result = tmp108.set_temperature_threshold_high(85.0); + assert!(result.is_ok()); + + let mut mock = tmp108.destroy(); + mock.done(); + } + + #[cfg(feature = "embedded-sensors-hal")] + #[test] + fn handle_hysteresis_settings() { + use embedded_sensors_hal::temperature::TemperatureHysteresis; + + // Test all valid hysteresis values: 0.0, 1.0, 2.0, 4.0 + // Setting hysteresis involves reading current config, modifying HYS bits (Byte 2 : Bit 4 and 5), writing back. + let expectations = vec![ + // Set hysteresis to 0.0 C + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x10]), // read_configuration + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x10]), // configure: read + Transaction::write(0x48, vec![0x01, 0x22, 0x00]), // configure: write with Hysteresis::_0C (0x2200) + // Set hysteresis to 1.0 C + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x00]), // read_configuration + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x00]), // configure: read + Transaction::write(0x48, vec![0x01, 0x22, 0x10]), // configure: write with Hysteresis::_1C (0x2210) + // Set hysteresis to 2.0 C + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x10]), // read_configuration + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x10]), // configure: read + Transaction::write(0x48, vec![0x01, 0x22, 0x20]), // configure: write with Hysteresis::_2C (0x2220) + // Set hysteresis to 4.0 C + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x20]), // read_configuration + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x20]), // configure: read + Transaction::write(0x48, vec![0x01, 0x22, 0x30]), // configure: write with Hysteresis::_4C (0x2230) + ]; + + let mock = Mock::new(&expectations); + let mut tmp108 = Tmp108::new_with_a0_gnd(mock); + + // Test valid hysteresis values, sensor only supports 0.0, 1.0, 2.0, 4.0 Celsius. + let result = tmp108.set_temperature_threshold_hysteresis(0.0); + assert!(result.is_ok()); + + let result = tmp108.set_temperature_threshold_hysteresis(1.0); + assert!(result.is_ok()); + + let result = tmp108.set_temperature_threshold_hysteresis(2.0); + assert!(result.is_ok()); + + let result = tmp108.set_temperature_threshold_hysteresis(4.0); + assert!(result.is_ok()); + + let mut mock = tmp108.destroy(); + mock.done(); + + // Test invalid hysteresis values (should return error) + let mock = Mock::new(&vec![]); + let mut tmp108 = Tmp108::new_with_a0_gnd(mock); + + // Test positive invalid value + let result = tmp108.set_temperature_threshold_hysteresis(3.0); + assert!(result.is_err()); + match result { + Err(Error::InvalidInput) => {} // Expected error + _ => panic!("Expected InvalidInput error for invalid hysteresis value"), + } + + // Test negative invalid value + let result = tmp108.set_temperature_threshold_hysteresis(-1.0); + assert!(result.is_err()); + match result { + Err(Error::InvalidInput) => {} // Expected error + _ => panic!("Expected InvalidInput error for negative hysteresis value"), + } + + let mut mock = tmp108.destroy(); + mock.done(); + } } #[cfg(feature = "async")] @@ -1201,5 +1368,74 @@ mod tests { i2c_mock.done(); pin_mock.done(); } + + #[cfg(feature = "embedded-sensors-hal-async")] + #[tokio::test] + async fn handle_hysteresis_settings() { + use embedded_sensors_hal_async::temperature::TemperatureHysteresis; + + // Test all valid hysteresis values: 0.0, 1.0, 2.0, 4.0 + // Setting hysteresis involves reading current config, modifying HYS bits, writing back. + let expectations = vec![ + // Set hysteresis to 0.0 C + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x10]), // read_configuration + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x10]), // configure: read + Transaction::write(0x48, vec![0x01, 0x22, 0x00]), // configure: write with Hysteresis::_0C (0x0022) + // Set hysteresis to 1.0 C + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x00]), // read_configuration + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x00]), // configure: read + Transaction::write(0x48, vec![0x01, 0x22, 0x10]), // configure: write with Hysteresis::_1C (0x1022) + // Set hysteresis to 2.0 C + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x10]), // read_configuration + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x10]), // configure: read + Transaction::write(0x48, vec![0x01, 0x22, 0x20]), // configure: write with Hysteresis::_2C (0x2022) + // Set hysteresis to 4.0 C + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x20]), // read_configuration + Transaction::write_read(0x48, vec![0x01], vec![0x22, 0x20]), // configure: read + Transaction::write(0x48, vec![0x01, 0x22, 0x30]), // configure: write with Hysteresis::_4C (0x3022) + ]; + + let mock = Mock::new(&expectations); + let mut tmp108 = Tmp108::new_with_a0_gnd(mock); + + // Test valid hysteresis values, sensor only supports 0.0, 1.0, 2.0, 4.0 Celsius. + let result = tmp108.set_temperature_threshold_hysteresis(0.0).await; + assert!(result.is_ok()); + + let result = tmp108.set_temperature_threshold_hysteresis(1.0).await; + assert!(result.is_ok()); + + let result = tmp108.set_temperature_threshold_hysteresis(2.0).await; + assert!(result.is_ok()); + + let result = tmp108.set_temperature_threshold_hysteresis(4.0).await; + assert!(result.is_ok()); + + let mut mock = tmp108.destroy(); + mock.done(); + + // Test invalid hysteresis values (should return error) + let mock = Mock::new(&vec![]); + let mut tmp108 = Tmp108::new_with_a0_gnd(mock); + + // Test positive invalid value + let result = tmp108.set_temperature_threshold_hysteresis(3.0).await; + assert!(result.is_err()); + match result { + Err(Error::InvalidInput) => {} // Expected error + _ => panic!("Expected InvalidInput error for invalid hysteresis value"), + } + + // Test negative invalid value + let result = tmp108.set_temperature_threshold_hysteresis(-1.0).await; + assert!(result.is_err()); + match result { + Err(Error::InvalidInput) => {} // Expected error + _ => panic!("Expected InvalidInput error for negative hysteresis value"), + } + + let mut mock = tmp108.destroy(); + mock.done(); + } } }