From fe3e984e54d4dfef31aabc7bc026a83669be1ed9 Mon Sep 17 00:00:00 2001 From: Justus Fluegel Date: Fri, 6 Mar 2026 18:27:40 +0100 Subject: [PATCH 1/2] Implement initial temperature modules Signed-off-by: Justus Fluegel --- sensor/src/core/temperature.rs | 0 sensor/src/core/temperature/dynamic.rs | 122 +++++++++++++++++++ sensor/src/core/temperature/dynamic_range.rs | 75 ++++++++++++ sensor/src/core/temperature/mod.rs | 83 +++++++++++++ sensor/src/core/temperature/unit.rs | 24 ++++ sensor/src/lib.rs | 1 + 6 files changed, 305 insertions(+) delete mode 100644 sensor/src/core/temperature.rs create mode 100644 sensor/src/core/temperature/dynamic.rs create mode 100644 sensor/src/core/temperature/dynamic_range.rs create mode 100644 sensor/src/core/temperature/mod.rs create mode 100644 sensor/src/core/temperature/unit.rs diff --git a/sensor/src/core/temperature.rs b/sensor/src/core/temperature.rs deleted file mode 100644 index e69de29..0000000 diff --git a/sensor/src/core/temperature/dynamic.rs b/sensor/src/core/temperature/dynamic.rs new file mode 100644 index 0000000..7c720d8 --- /dev/null +++ b/sensor/src/core/temperature/dynamic.rs @@ -0,0 +1,122 @@ +use std::cmp::Ordering; + +use crate::core::temperature::{Celsius, Fahrenheit, Kelvin, Temperature, TemperatureUnit}; + +// A temperatures with a unknown unit at compile time +// +// Can be either `Kelvin`, `Celsius` or `Fahrenheit` +// +// Try to use `Kelvin`, `Celsius` or `Fahrenheit` instead whenever possible. +#[derive(Debug, Clone, Copy)] +pub enum DynamicTemperature { + Kelvin(Kelvin), + Celsius(Celsius), + Fahrenheit(Fahrenheit), +} + +impl From for DynamicTemperature { + fn from(value: Kelvin) -> Self { + Self::Kelvin(value) + } +} +impl From for DynamicTemperature { + fn from(value: Celsius) -> Self { + Self::Celsius(value) + } +} +impl From for DynamicTemperature { + fn from(value: Fahrenheit) -> Self { + Self::Fahrenheit(value) + } +} + +impl PartialEq> for DynamicTemperature +where + Unit: TemperatureUnit, +{ + fn eq(&self, other: &Temperature) -> bool { + match self { + Self::Kelvin(temperature) => temperature.eq(other), + Self::Celsius(temperature) => temperature.eq(other), + Self::Fahrenheit(temperature) => temperature.eq(other), + } + } +} + +impl PartialEq for Temperature +where + Unit: TemperatureUnit, +{ + fn eq(&self, other: &DynamicTemperature) -> bool { + other == self + } +} + +impl PartialEq for DynamicTemperature { + fn eq(&self, other: &Self) -> bool { + match self { + Self::Kelvin(temperature) => temperature.eq(other), + Self::Celsius(temperature) => temperature.eq(other), + Self::Fahrenheit(temperature) => temperature.eq(other), + } + } +} + +impl PartialOrd> for DynamicTemperature +where + Unit: TemperatureUnit, +{ + fn partial_cmp(&self, other: &Temperature) -> Option { + match self { + Self::Kelvin(temperature) => temperature.partial_cmp(other), + Self::Celsius(temperature) => temperature.partial_cmp(other), + Self::Fahrenheit(temperature) => temperature.partial_cmp(other), + } + } +} + +impl PartialOrd for Temperature +where + Unit: TemperatureUnit, +{ + fn partial_cmp(&self, other: &DynamicTemperature) -> Option { + other.partial_cmp(self).map(Ordering::reverse) + } +} + +impl PartialOrd for DynamicTemperature { + fn partial_cmp(&self, other: &Self) -> Option { + match self { + Self::Kelvin(temperature) => temperature.partial_cmp(other), + Self::Celsius(temperature) => temperature.partial_cmp(other), + Self::Fahrenheit(temperature) => temperature.partial_cmp(other), + } + } +} + +impl DynamicTemperature { + pub fn kelvin(value: f32) -> Self { + Self::Kelvin(Temperature::new(value)) + } + + pub fn celsius(value: f32) -> Self { + Self::Celsius(Temperature::new(value)) + } + + pub fn fahrenheit(value: f32) -> Self { + Self::Fahrenheit(Temperature::new(value)) + } + + pub fn convert(self) -> Temperature { + match self { + Self::Kelvin(val) => val.convert(), + Self::Celsius(val) => val.convert(), + Self::Fahrenheit(val) => val.convert(), + } + } +} + +#[cfg(test)] +mod test { + // TODO: implement test cases +} diff --git a/sensor/src/core/temperature/dynamic_range.rs b/sensor/src/core/temperature/dynamic_range.rs new file mode 100644 index 0000000..527e347 --- /dev/null +++ b/sensor/src/core/temperature/dynamic_range.rs @@ -0,0 +1,75 @@ +use std::ops::Range; + +use crate::core::temperature::{Celsius, Fahrenheit, Kelvin, Temperature, TemperatureUnit}; + +// A range of temperatures with a unknown unit at compile time +// +// Can be either `Kelvin`, `Celsius` or `Fahrenheit` +// +// Try to use `Range`, `Range` or `Range` instead whenever possible. +#[derive(Debug, Clone)] +pub enum DynamicRange { + Kelvin(Range), + Celsius(Range), + Fahrenheit(Range), +} +impl From> for DynamicRange { + fn from(value: Range) -> Self { + Self::Kelvin(value) + } +} +impl From> for DynamicRange { + fn from(value: Range) -> Self { + Self::Celsius(value) + } +} +impl From> for DynamicRange { + fn from(value: Range) -> Self { + Self::Fahrenheit(value) + } +} + +impl DynamicRange { + pub fn kelvin(value: Range) -> Self { + Self::Kelvin(Range { + start: Temperature::new(value.start), + end: Temperature::new(value.end), + }) + } + + pub fn celsius(value: Range) -> Self { + Self::Celsius(Range { + start: Temperature::new(value.start), + end: Temperature::new(value.end), + }) + } + + pub fn fahrenheit(value: Range) -> Self { + Self::Fahrenheit(Range { + start: Temperature::new(value.start), + end: Temperature::new(value.end), + }) + } + + pub fn convert(self) -> Range> { + match self { + Self::Kelvin(val) => Range { + start: val.start.convert(), + end: val.end.convert(), + }, + Self::Celsius(val) => Range { + start: val.start.convert(), + end: val.end.convert(), + }, + Self::Fahrenheit(val) => Range { + start: val.start.convert(), + end: val.end.convert(), + }, + } + } +} + +#[cfg(test)] +mod test { + // TODO: implement test cases +} diff --git a/sensor/src/core/temperature/mod.rs b/sensor/src/core/temperature/mod.rs new file mode 100644 index 0000000..b428a7a --- /dev/null +++ b/sensor/src/core/temperature/mod.rs @@ -0,0 +1,83 @@ +mod dynamic; +pub use dynamic::*; + +pub mod unit; +pub use unit::TemperatureUnit; +pub type Celsius = Temperature; +pub type Kelvin = Temperature; +pub type Fahrenheit = Temperature; + +pub mod dynamic_range; +pub use dynamic_range::*; + +use std::{cmp::Ordering, marker::PhantomData}; + +#[derive(Debug)] +pub struct Temperature(f32, PhantomData); + +impl Clone for Temperature { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Temperature {} + +impl PartialEq> for Temperature +where + Unit1: TemperatureUnit, + Unit2: TemperatureUnit, +{ + fn eq(&self, other: &Temperature) -> bool { + // avoid precision loss, although partialeq on floats is probably a pretty bad idea anyways + if const { Unit2::KELVIN_FACTOR > Unit1::KELVIN_FACTOR } { + other.convert::().0 == self.0 + } else { + self.convert::().0 == other.0 + } + } +} + +impl PartialOrd> for Temperature +where + Unit1: TemperatureUnit, + Unit2: TemperatureUnit, +{ + fn partial_cmp(&self, other: &Temperature) -> Option { + // avoid precision loss + if const { Unit2::KELVIN_FACTOR > Unit1::KELVIN_FACTOR } { + other + .convert::() + .0 + .partial_cmp(&self.0) + .map(Ordering::reverse) + } else { + self.convert::().0.partial_cmp(&other.0) + } + } +} + +impl Temperature +where + Unit: TemperatureUnit, +{ + pub fn new(value: f32) -> Self { + Self(value, PhantomData) + } + + pub fn convert(self) -> Temperature { + // this is essentially a f(g^-1(x)) composition in normal form. The factors get calculated at compile time, eliminating overhead. + let result = self.0 * const { TargetUnit::KELVIN_FACTOR / Unit::KELVIN_FACTOR } + + const { + TargetUnit::KELVIN_OFFSET + - Unit::KELVIN_OFFSET * (TargetUnit::KELVIN_FACTOR / Unit::KELVIN_FACTOR) + }; + + Temperature(result, PhantomData) + } +} + +#[cfg(test)] +mod test { + // TODO: implement test cases +} diff --git a/sensor/src/core/temperature/unit.rs b/sensor/src/core/temperature/unit.rs new file mode 100644 index 0000000..6c52dc7 --- /dev/null +++ b/sensor/src/core/temperature/unit.rs @@ -0,0 +1,24 @@ +pub trait TemperatureUnit { + const KELVIN_OFFSET: f32; + const KELVIN_FACTOR: f32; +} + +#[derive(Debug)] +pub struct Kelvin; +impl TemperatureUnit for Kelvin { + const KELVIN_OFFSET: f32 = 0.0; + const KELVIN_FACTOR: f32 = 1.0; +} + +#[derive(Debug)] +pub struct Celsius; +impl TemperatureUnit for Celsius { + const KELVIN_OFFSET: f32 = -273.15; + const KELVIN_FACTOR: f32 = 1.0; +} +#[derive(Debug)] +pub struct Fahrenheit; +impl TemperatureUnit for Fahrenheit { + const KELVIN_OFFSET: f32 = -459.67; + const KELVIN_FACTOR: f32 = 1.8; +} diff --git a/sensor/src/lib.rs b/sensor/src/lib.rs index 39c7708..c5cef94 100644 --- a/sensor/src/lib.rs +++ b/sensor/src/lib.rs @@ -1,3 +1,4 @@ +pub mod core; pub mod error; pub mod ezo; pub mod i2c_bus; From 1bf5c6e05c47f4ca8664c43d6b4a0eb442810916 Mon Sep 17 00:00:00 2001 From: Justus Fluegel Date: Fri, 6 Mar 2026 18:36:45 +0100 Subject: [PATCH 2/2] Rename KELVIN_OFFSET and KELVIN_FACTOR to ZERO_OFFSET and SCALING_FACTOR respectively to better represent their purpose Signed-off-by: Justus Fluegel --- sensor/src/core/temperature/mod.rs | 10 +++++----- sensor/src/core/temperature/unit.rs | 17 +++++++++-------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/sensor/src/core/temperature/mod.rs b/sensor/src/core/temperature/mod.rs index b428a7a..2f42165 100644 --- a/sensor/src/core/temperature/mod.rs +++ b/sensor/src/core/temperature/mod.rs @@ -30,7 +30,7 @@ where { fn eq(&self, other: &Temperature) -> bool { // avoid precision loss, although partialeq on floats is probably a pretty bad idea anyways - if const { Unit2::KELVIN_FACTOR > Unit1::KELVIN_FACTOR } { + if const { Unit2::SCALING_FACTOR > Unit1::SCALING_FACTOR } { other.convert::().0 == self.0 } else { self.convert::().0 == other.0 @@ -45,7 +45,7 @@ where { fn partial_cmp(&self, other: &Temperature) -> Option { // avoid precision loss - if const { Unit2::KELVIN_FACTOR > Unit1::KELVIN_FACTOR } { + if const { Unit2::SCALING_FACTOR > Unit1::SCALING_FACTOR } { other .convert::() .0 @@ -67,10 +67,10 @@ where pub fn convert(self) -> Temperature { // this is essentially a f(g^-1(x)) composition in normal form. The factors get calculated at compile time, eliminating overhead. - let result = self.0 * const { TargetUnit::KELVIN_FACTOR / Unit::KELVIN_FACTOR } + let result = self.0 * const { TargetUnit::SCALING_FACTOR / Unit::SCALING_FACTOR } + const { - TargetUnit::KELVIN_OFFSET - - Unit::KELVIN_OFFSET * (TargetUnit::KELVIN_FACTOR / Unit::KELVIN_FACTOR) + TargetUnit::ZERO_OFFSET + - Unit::ZERO_OFFSET * (TargetUnit::SCALING_FACTOR / Unit::SCALING_FACTOR) }; Temperature(result, PhantomData) diff --git a/sensor/src/core/temperature/unit.rs b/sensor/src/core/temperature/unit.rs index 6c52dc7..d0a7d2a 100644 --- a/sensor/src/core/temperature/unit.rs +++ b/sensor/src/core/temperature/unit.rs @@ -1,24 +1,25 @@ +/// A temperature unit, defined by its zero offset and scaling factor relative to the absolute temperature (Kelvin). pub trait TemperatureUnit { - const KELVIN_OFFSET: f32; - const KELVIN_FACTOR: f32; + const ZERO_OFFSET: f32; + const SCALING_FACTOR: f32; } #[derive(Debug)] pub struct Kelvin; impl TemperatureUnit for Kelvin { - const KELVIN_OFFSET: f32 = 0.0; - const KELVIN_FACTOR: f32 = 1.0; + const ZERO_OFFSET: f32 = 0.0; + const SCALING_FACTOR: f32 = 1.0; } #[derive(Debug)] pub struct Celsius; impl TemperatureUnit for Celsius { - const KELVIN_OFFSET: f32 = -273.15; - const KELVIN_FACTOR: f32 = 1.0; + const ZERO_OFFSET: f32 = -273.15; + const SCALING_FACTOR: f32 = 1.0; } #[derive(Debug)] pub struct Fahrenheit; impl TemperatureUnit for Fahrenheit { - const KELVIN_OFFSET: f32 = -459.67; - const KELVIN_FACTOR: f32 = 1.8; + const ZERO_OFFSET: f32 = -459.67; + const SCALING_FACTOR: f32 = 1.8; }