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..2f42165 --- /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::SCALING_FACTOR > Unit1::SCALING_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::SCALING_FACTOR > Unit1::SCALING_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::SCALING_FACTOR / Unit::SCALING_FACTOR } + + const { + TargetUnit::ZERO_OFFSET + - Unit::ZERO_OFFSET * (TargetUnit::SCALING_FACTOR / Unit::SCALING_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..d0a7d2a --- /dev/null +++ b/sensor/src/core/temperature/unit.rs @@ -0,0 +1,25 @@ +/// A temperature unit, defined by its zero offset and scaling factor relative to the absolute temperature (Kelvin). +pub trait TemperatureUnit { + const ZERO_OFFSET: f32; + const SCALING_FACTOR: f32; +} + +#[derive(Debug)] +pub struct Kelvin; +impl TemperatureUnit for Kelvin { + const ZERO_OFFSET: f32 = 0.0; + const SCALING_FACTOR: f32 = 1.0; +} + +#[derive(Debug)] +pub struct Celsius; +impl TemperatureUnit for Celsius { + const ZERO_OFFSET: f32 = -273.15; + const SCALING_FACTOR: f32 = 1.0; +} +#[derive(Debug)] +pub struct Fahrenheit; +impl TemperatureUnit for Fahrenheit { + const ZERO_OFFSET: f32 = -459.67; + const SCALING_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;