summaryrefslogtreecommitdiff
path: root/tmp117/src
diff options
context:
space:
mode:
authorunitexe <unitexe70@gmail.com>2026-04-08 23:53:17 -0500
committerunitexe <unitexe70@gmail.com>2026-04-08 23:54:28 -0500
commitde3f7ed45185f3a678ba0de04d98cc9ac92de0c8 (patch)
tree3db0159c1370f240d42c1e8b89a67247538f43d1 /tmp117/src
Initial commit
Diffstat (limited to 'tmp117/src')
-rw-r--r--tmp117/src/client.rs199
-rw-r--r--tmp117/src/conf.rs547
-rw-r--r--tmp117/src/eeprom.rs7
-rw-r--r--tmp117/src/id.rs52
-rw-r--r--tmp117/src/lib.rs5
-rw-r--r--tmp117/src/reg.rs74
6 files changed, 884 insertions, 0 deletions
diff --git a/tmp117/src/client.rs b/tmp117/src/client.rs
new file mode 100644
index 0000000..6618d72
--- /dev/null
+++ b/tmp117/src/client.rs
@@ -0,0 +1,199 @@
+use crate::conf::Configuration;
+use crate::eeprom::EEPROM;
+use crate::id::Identity;
+use crate::reg::Registers;
+use embedded_hal::{delay::DelayNs, i2c::I2c};
+
+pub struct TMP117<I2C, Delay> {
+ addr: u8,
+ i2c: I2C,
+ delay: Delay,
+}
+
+impl<I2C: I2c, Delay: DelayNs> TMP117<I2C, Delay> {
+ const CELSIUS_PER_LSB: f32 = 7.8125e-3;
+
+ pub fn new(addr: u8, i2c: I2C, delay: Delay) -> Self {
+ Self { addr, i2c, delay }
+ }
+
+ pub fn configure(&mut self, configuration: u16) -> Result<(), I2C::Error> {
+ let bytes = configuration.to_be_bytes();
+ self.i2c.write(
+ self.addr,
+ &[Registers::Configuration as u8, bytes[0], bytes[1]],
+ )
+ }
+
+ pub fn configure_typed(&mut self, configuration: Configuration) -> Result<(), I2C::Error> {
+ let configuration = u16::from(&configuration);
+ self.configure(configuration)
+ }
+
+ pub fn get_configuration(&mut self) -> Result<u16, I2C::Error> {
+ let mut bytes = [0u8; 2];
+ self.i2c
+ .write_read(self.addr, &[Registers::Configuration as u8], &mut bytes)?;
+ let configuration = u16::from_be_bytes(bytes);
+ Ok(configuration)
+ }
+
+ pub fn get_configuration_typed(&mut self) -> Result<Configuration, I2C::Error> {
+ let configuration = self.get_configuration()?;
+ let configuration = Configuration::from(configuration);
+ Ok(configuration)
+ }
+
+ pub fn configure_low_limit(&mut self, limit: i16) -> Result<(), I2C::Error> {
+ let bytes = limit.to_be_bytes();
+ self.i2c
+ .write(self.addr, &[Registers::LowLimit as u8, bytes[0], bytes[1]])
+ }
+
+ pub fn configure_low_limit_celsius(&mut self, limit: f32) -> Result<(), I2C::Error> {
+ let limit = limit / Self::CELSIUS_PER_LSB;
+ let limit = limit.round() as i16;
+ self.configure_low_limit(limit)
+ }
+
+ pub fn get_low_limit(&mut self) -> Result<i16, I2C::Error> {
+ let mut limit = [0u8; 2];
+ self.i2c
+ .write_read(self.addr, &[Registers::LowLimit as u8], &mut limit)?;
+ let limit = i16::from_be_bytes(limit);
+ Ok(limit)
+ }
+
+ pub fn get_low_limit_celsius(&mut self) -> Result<f32, I2C::Error> {
+ let limit = self.get_low_limit()?;
+ let limit = limit as f32 * Self::CELSIUS_PER_LSB;
+ Ok(limit)
+ }
+
+ pub fn configure_high_limit(&mut self, limit: i16) -> Result<(), I2C::Error> {
+ let limit = limit.to_be_bytes();
+ self.i2c
+ .write(self.addr, &[Registers::HighLimit as u8, limit[0], limit[1]])
+ }
+
+ pub fn configure_high_limit_celsius(&mut self, limit: f32) -> Result<(), I2C::Error> {
+ let limit = limit / Self::CELSIUS_PER_LSB;
+ let limit = limit.round() as i16;
+ self.configure_high_limit(limit)
+ }
+
+ pub fn get_high_limit(&mut self) -> Result<i16, I2C::Error> {
+ let mut limit = [0u8; 2];
+ self.i2c
+ .write_read(self.addr, &[Registers::HighLimit as u8], &mut limit)?;
+ let limit = i16::from_be_bytes(limit);
+ Ok(limit)
+ }
+
+ pub fn get_high_limit_celsius(&mut self) -> Result<f32, I2C::Error> {
+ let high_limit = self.get_high_limit()?;
+ let high_limit = high_limit as f32 * Self::CELSIUS_PER_LSB;
+ Ok(high_limit)
+ }
+
+ pub fn configure_offset_temperature(&mut self, offset: i16) -> Result<(), I2C::Error> {
+ let offset = offset.to_be_bytes();
+ self.i2c.write(
+ self.addr,
+ &[Registers::TemperatureOffset as u8, offset[0], offset[1]],
+ )
+ }
+
+ pub fn configure_offset_temperature_celsius(&mut self, offset: f32) -> Result<(), I2C::Error> {
+ let offset = offset / Self::CELSIUS_PER_LSB;
+ let offset = offset.round() as i16;
+ self.configure_offset_temperature(offset)
+ }
+
+ pub fn get_offset_temperature(&mut self) -> Result<i16, I2C::Error> {
+ let mut offset = [0u8; 2];
+ self.i2c.write_read(
+ self.addr,
+ &[Registers::TemperatureOffset as u8],
+ &mut offset,
+ )?;
+ let offset = i16::from_be_bytes(offset);
+ Ok(offset)
+ }
+
+ pub fn get_offset_temperature_celsius(&mut self) -> Result<f32, I2C::Error> {
+ let offset = self.get_offset_temperature()?;
+ let offset = offset as f32 * Self::CELSIUS_PER_LSB;
+ Ok(offset)
+ }
+
+ pub fn get_eeprom_1(&mut self) -> Result<u16, I2C::Error> {
+ let mut eeprom = [0u8; 2];
+ self.i2c
+ .write_read(self.addr, &[Registers::Eeprom1 as u8], &mut eeprom)?;
+ let eeprom = u16::from_be_bytes(eeprom);
+ Ok(eeprom)
+ }
+
+ pub fn get_eeprom_2(&mut self) -> Result<u16, I2C::Error> {
+ let mut eeprom = [0u8; 2];
+ self.i2c
+ .write_read(self.addr, &[Registers::Eeprom2 as u8], &mut eeprom)?;
+ let eeprom = u16::from_be_bytes(eeprom);
+ Ok(eeprom)
+ }
+
+ pub fn get_eeprom_3(&mut self) -> Result<u16, I2C::Error> {
+ let mut eeprom = [0u8; 2];
+ self.i2c
+ .write_read(self.addr, &[Registers::Eeprom3 as u8], &mut eeprom)?;
+ let eeprom = u16::from_be_bytes(eeprom);
+ Ok(eeprom)
+ }
+
+ pub fn get_eeprom_all(&mut self) -> Result<EEPROM, I2C::Error> {
+ let eeprom1 = self.get_eeprom_1()?;
+ let eeprom2 = self.get_eeprom_2()?;
+ let eeprom3 = self.get_eeprom_3()?;
+ let eeprom = EEPROM {
+ eeprom1,
+ eeprom2,
+ eeprom3,
+ };
+ Ok(eeprom)
+ }
+
+ pub fn get_identity(&mut self) -> Result<Identity, I2C::Error> {
+ let mut identity = [0u8; 2];
+ self.i2c
+ .write_read(self.addr, &[Registers::DeviceId as u8], &mut identity)?;
+ let identity = u16::from_be_bytes(identity);
+ let identity = Identity::from(identity);
+ Ok(identity)
+ }
+
+ pub fn get_temperature(&mut self) -> Result<i16, I2C::Error> {
+ let mut temperature = [0u8; 2];
+ self.i2c
+ .write_read(self.addr, &[Registers::Temperature as u8], &mut temperature)?;
+ let temperature = i16::from_be_bytes(temperature);
+ Ok(temperature)
+ }
+
+ pub fn get_temperature_celsius(&mut self) -> Result<f32, I2C::Error> {
+ let temperature = self.get_temperature()?;
+ let temperature = temperature as f32 * Self::CELSIUS_PER_LSB;
+ Ok(temperature)
+ }
+
+ pub fn software_reset(&mut self) -> Result<(), I2C::Error> {
+ let mask: u16 = 0x0002;
+ let mask = mask.to_be_bytes();
+ self.i2c.write(
+ self.addr,
+ &[Registers::Configuration as u8, mask[0], mask[1]],
+ )?;
+ self.delay.delay_ms(2);
+ Ok(())
+ }
+}
diff --git a/tmp117/src/conf.rs b/tmp117/src/conf.rs
new file mode 100644
index 0000000..1ea275b
--- /dev/null
+++ b/tmp117/src/conf.rs
@@ -0,0 +1,547 @@
+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<u16> 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<u16> 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());
+ }
+}
diff --git a/tmp117/src/eeprom.rs b/tmp117/src/eeprom.rs
new file mode 100644
index 0000000..293c74f
--- /dev/null
+++ b/tmp117/src/eeprom.rs
@@ -0,0 +1,7 @@
+pub struct EEPROM {
+ pub eeprom1: u16,
+
+ pub eeprom2: u16,
+
+ pub eeprom3: u16,
+}
diff --git a/tmp117/src/id.rs b/tmp117/src/id.rs
new file mode 100644
index 0000000..e1fcc8b
--- /dev/null
+++ b/tmp117/src/id.rs
@@ -0,0 +1,52 @@
+pub struct Identity {
+ pub device_id: u16,
+
+ pub revision_number: u8,
+}
+
+impl From<u16> for Identity {
+ fn from(register: u16) -> Self {
+ let device_id = register & 0x0FFF;
+ let revision_number = ((register >> 12) & 0xF) as u8;
+ Self {
+ device_id,
+ revision_number,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_identity_from_known_device_id() {
+ let identity = Identity::from(0x0117);
+ assert_eq!(identity.device_id, 0x117);
+ assert_eq!(identity.revision_number, 0);
+ }
+
+ #[test]
+ fn test_identity_device_id_masked() {
+ // bits 11:0 are device ID
+ let identity = Identity::from(0x0FFF);
+ assert_eq!(identity.device_id, 0x0FFF);
+ assert_eq!(identity.revision_number, 0);
+ }
+
+ #[test]
+ fn test_identity_revision_number_extracted() {
+ // bits 15:12 are revision
+ let identity = Identity::from(0x1000);
+ assert_eq!(identity.device_id, 0x000);
+ assert_eq!(identity.revision_number, 1);
+ }
+
+ #[test]
+ fn test_identity_both_fields() {
+ // revision = 0xA, device_id = 0x117
+ let identity = Identity::from(0xA117);
+ assert_eq!(identity.device_id, 0x117);
+ assert_eq!(identity.revision_number, 0xA);
+ }
+}
diff --git a/tmp117/src/lib.rs b/tmp117/src/lib.rs
new file mode 100644
index 0000000..4c26e67
--- /dev/null
+++ b/tmp117/src/lib.rs
@@ -0,0 +1,5 @@
+pub mod client;
+pub mod conf;
+pub mod eeprom;
+pub mod id;
+pub mod reg;
diff --git a/tmp117/src/reg.rs b/tmp117/src/reg.rs
new file mode 100644
index 0000000..20ac429
--- /dev/null
+++ b/tmp117/src/reg.rs
@@ -0,0 +1,74 @@
+#[repr(u8)]
+pub enum Registers {
+ Temperature = 0x00,
+ Configuration = 0x01,
+ HighLimit = 0x02,
+ LowLimit = 0x03,
+ Eeprom1 = 0x05,
+ Eeprom2 = 0x06,
+ TemperatureOffset = 0x07,
+ Eeprom3 = 0x08,
+ DeviceId = 0x0F,
+}
+
+impl Registers {
+ pub const TEMPERATURE_RESET: u16 = 0x8000;
+ pub const CONFIGURATION_RESET: u16 = 0x0220;
+ pub const HIGH_LIMIT_RESET: u16 = 0x6000;
+ pub const LOW_LIMIT_RESET: u16 = 0x8000;
+ pub const TEMPERATURE_OFFSET_RESET: u16 = 0x0000;
+ pub const DEVICE_ID_RESET: u16 = 0x0117;
+
+ pub fn factory_default_reset(&self) -> Option<u16> {
+ match self {
+ Registers::Temperature => Some(Registers::TEMPERATURE_RESET),
+ Registers::Configuration => Some(Registers::CONFIGURATION_RESET),
+ Registers::HighLimit => Some(Registers::HIGH_LIMIT_RESET),
+ Registers::LowLimit => Some(Registers::LOW_LIMIT_RESET),
+ Registers::TemperatureOffset => Some(Registers::TEMPERATURE_OFFSET_RESET),
+ Registers::DeviceId => Some(Registers::DEVICE_ID_RESET),
+ _ => None,
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_register_addresses() {
+ assert_eq!(Registers::Temperature as u8, 0x00);
+ assert_eq!(Registers::Configuration as u8, 0x01);
+ assert_eq!(Registers::HighLimit as u8, 0x02);
+ assert_eq!(Registers::LowLimit as u8, 0x03);
+ assert_eq!(Registers::Eeprom1 as u8, 0x05);
+ assert_eq!(Registers::Eeprom2 as u8, 0x06);
+ assert_eq!(Registers::TemperatureOffset as u8, 0x07);
+ assert_eq!(Registers::Eeprom3 as u8, 0x08);
+ assert_eq!(Registers::DeviceId as u8, 0x0F);
+ }
+
+ #[test]
+ fn test_factory_default_reset_known() {
+ assert_eq!(Registers::Temperature.factory_default_reset(), Some(0x8000));
+ assert_eq!(
+ Registers::Configuration.factory_default_reset(),
+ Some(0x0220)
+ );
+ assert_eq!(Registers::HighLimit.factory_default_reset(), Some(0x6000));
+ assert_eq!(Registers::LowLimit.factory_default_reset(), Some(0x8000));
+ assert_eq!(
+ Registers::TemperatureOffset.factory_default_reset(),
+ Some(0x0000)
+ );
+ assert_eq!(Registers::DeviceId.factory_default_reset(), Some(0x0117));
+ }
+
+ #[test]
+ fn test_factory_default_reset_unknown() {
+ assert_eq!(Registers::Eeprom1.factory_default_reset(), None);
+ assert_eq!(Registers::Eeprom2.factory_default_reset(), None);
+ assert_eq!(Registers::Eeprom3.factory_default_reset(), None);
+ }
+}