Initial proof of concept
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
use crate::quantity::{Quantity, Value};
|
||||
use generate_quantity::quantity_type;
|
||||
|
||||
//----- Watts per Square Meter ----------------------------------
|
||||
quantity_type! {WattsPerSquareMeter, "W/m²"}
|
||||
@@ -0,0 +1,170 @@
|
||||
mod irradiance;
|
||||
mod resistance;
|
||||
mod temperature;
|
||||
mod voltage;
|
||||
mod volume;
|
||||
mod volume_rate;
|
||||
mod pressure;
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
pub use defmt_impl::*;
|
||||
|
||||
pub use irradiance::*;
|
||||
pub use resistance::*;
|
||||
pub use temperature::*;
|
||||
pub use voltage::*;
|
||||
pub use volume::*;
|
||||
pub use volume_rate::*;
|
||||
pub use pressure::*;
|
||||
|
||||
use core::fmt::Display;
|
||||
use core::marker::PhantomData;
|
||||
use core::ops::{Add, Sub};
|
||||
use num_traits::{FromPrimitive, Num, NumCast};
|
||||
|
||||
const DECA: u8 = 10;
|
||||
const DECI: u8 = 10;
|
||||
const HECTO: u8 = 100;
|
||||
const CENTI: u8 = 100;
|
||||
const KILO: u16 = 1_000;
|
||||
const MILLI: u16 = 1_000;
|
||||
const MEGA: u32 = 1_000_000;
|
||||
const MICRO: u32 = 1_000_000;
|
||||
const GIGA: u32 = 1_000_000_000;
|
||||
const NANO: u32 = 1_000_000_000;
|
||||
|
||||
pub trait Quantity<V: Value>: Copy + PartialEq + PartialOrd + Add + Sub {
|
||||
fn value(self) -> V;
|
||||
|
||||
fn symbol() -> &'static str;
|
||||
|
||||
/// Returns a wrapper that implements [Display] and [defmt::Format] for [Quantity]s with core number values.
|
||||
///
|
||||
/// - `rounding` - Sets the number of decimal places to round to when formatting (currently ignored with defmt).
|
||||
#[inline(always)]
|
||||
fn fmt(self, rounding: Option<usize>) -> Fmt<V, Self> {
|
||||
Fmt::new(self, rounding)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Value: Num + Copy + PartialOrd + FromPrimitive + NumCast + Display {}
|
||||
impl<V> Value for V where V: Num + Copy + PartialOrd + FromPrimitive + NumCast + Display {}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Fmt<V: Value, Q: Quantity<V>> {
|
||||
quantity: Q,
|
||||
rounding: Option<usize>,
|
||||
_phantom: PhantomData<V>,
|
||||
}
|
||||
|
||||
impl<V: Value, Q: Quantity<V>> Fmt<V, Q> {
|
||||
#[inline(always)]
|
||||
fn new(quantity: Q, rounding: Option<usize>) -> Self {
|
||||
Self {
|
||||
quantity,
|
||||
rounding,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: Value, Q: Quantity<V>> Display for Fmt<V, Q> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self.rounding {
|
||||
Some(places) => {
|
||||
write!(f, "{:.prec$} {}", self.quantity.value(), Q::symbol(), prec = places)
|
||||
},
|
||||
None => write!(f, "{} {}", self.quantity.value(), Q::symbol()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
mod defmt_impl {
|
||||
use super::*;
|
||||
use defmt::{write, Format, Formatter};
|
||||
|
||||
impl<Q: Quantity<u8>> Format for Fmt<u8, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<u16>> Format for Fmt<u16, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<u32>> Format for Fmt<u32, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<u64>> Format for Fmt<u64, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<u128>> Format for Fmt<u128, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<usize>> Format for Fmt<usize, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<i8>> Format for Fmt<i8, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<i16>> Format for Fmt<i16, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<i32>> Format for Fmt<i32, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<i64>> Format for Fmt<i64, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<i128>> Format for Fmt<i128, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<isize>> Format for Fmt<isize, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<f32>> Format for Fmt<f32, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
|
||||
impl<Q: Quantity<f64>> Format for Fmt<f64, Q> {
|
||||
fn format(&self, fmt: Formatter) {
|
||||
write!(fmt, "{} {}", self.quantity.value(), Q::symbol());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
use crate::quantity::{KILO, Quantity, Value};
|
||||
use generate_quantity::quantity_type;
|
||||
|
||||
const PASCALS_PER_TORR: f64 = 101325.0 / 760.0;
|
||||
const KILO_PASCALS_PER_PSI: f64 = 6.894757293168364;
|
||||
|
||||
//----- Pascals ----------------------------------
|
||||
quantity_type! {Pascals, "Pa"}
|
||||
|
||||
impl<V: Value> Pascals<V> {
|
||||
#[inline]
|
||||
pub fn to_kilo_pascals(self) -> KiloPascals<V> {
|
||||
let divisor = V::from_u16(KILO).unwrap();
|
||||
KiloPascals(self.0 / divisor)
|
||||
}
|
||||
|
||||
// Should this be in separate imple with `V` bound to `num_traits::Float`?
|
||||
#[inline]
|
||||
pub fn to_torr(self) -> Torr<V> {
|
||||
let divisor = V::from_f64(PASCALS_PER_TORR).unwrap();
|
||||
Torr(self.0 / divisor)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Kilopascals ----------------------------------
|
||||
quantity_type! {KiloPascals, "kPa"}
|
||||
|
||||
impl<V: Value> KiloPascals<V> {
|
||||
#[inline]
|
||||
pub fn to_pascals(self) -> Pascals<V> {
|
||||
let multiplier = V::from_u16(KILO).unwrap();
|
||||
Pascals(self.0 * multiplier)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_psi(self) -> Psi<V> {
|
||||
let divisor = V::from_f64(KILO_PASCALS_PER_PSI).unwrap();
|
||||
Psi(self.0 / divisor)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Torr ----------------------------------
|
||||
quantity_type! {Torr, "Torr"}
|
||||
|
||||
// Should this bound `V` to `num_traits::Float`?
|
||||
impl<V: Value> Torr<V> {
|
||||
#[inline]
|
||||
pub fn to_pascals(self) -> Pascals<V> {
|
||||
let multiplier = V::from_f64(PASCALS_PER_TORR).unwrap();
|
||||
Pascals(self.0 * multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
//----- PSI ----------------------------------
|
||||
quantity_type! {Psi, "PSI"}
|
||||
|
||||
impl<V: Value> Psi<V> {
|
||||
#[inline]
|
||||
pub fn to_kilo_pascals(self) -> KiloPascals<V> {
|
||||
let multiplier = V::from_f64(KILO_PASCALS_PER_PSI).unwrap();
|
||||
KiloPascals(self.0 * multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Tests ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use float_cmp::assert_approx_eq;
|
||||
|
||||
#[test]
|
||||
fn pascals_kilo_pascals() {
|
||||
let pascals: Pascals<u32> = 1_000.pascals();
|
||||
let kilo_pascals: KiloPascals<u32> = 1.kilo_pascals();
|
||||
|
||||
assert_eq!(pascals.0, kilo_pascals.to_pascals().0);
|
||||
assert_eq!(kilo_pascals.0, pascals.to_kilo_pascals().0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn torr_pascals() {
|
||||
let torr: Torr<f32> = 7.5.torr();
|
||||
let pascals: Pascals<f32> = 999.9177631578947.pascals();
|
||||
|
||||
assert_approx_eq!(f32, pascals.to_torr().0, torr.0);
|
||||
assert_approx_eq!(f32, torr.to_pascals().0, pascals.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn psi_kilo_pascals() {
|
||||
let psi: Psi<f32> = 2.5.psi();
|
||||
let kilo_pascals: KiloPascals<f32> = 17.23689323292091.kilo_pascals();
|
||||
|
||||
assert_approx_eq!(f32, psi.to_kilo_pascals().0, kilo_pascals.0);
|
||||
assert_approx_eq!(f32, kilo_pascals.to_psi().0, psi.0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
use generate_quantity::quantity_type;
|
||||
use crate::quantity::{Quantity, Value};
|
||||
|
||||
//----- Ohms ----------------------------------
|
||||
quantity_type! {Ohms, "Ω"}
|
||||
@@ -0,0 +1,96 @@
|
||||
use generate_quantity::quantity_type;
|
||||
use crate::quantity::{DECI, Quantity, Value};
|
||||
|
||||
const KELVIN_CELSIUS_OFFSET: f64 = 273.15;
|
||||
|
||||
//----- Kelvins ----------------------------------
|
||||
quantity_type! {Kelvins, "K"}
|
||||
|
||||
impl <V: Value> Kelvins<V> {
|
||||
#[inline]
|
||||
pub fn to_celsius(self) -> Celsius<V> {
|
||||
//TODO: Make const
|
||||
let offset = V::from_f64(KELVIN_CELSIUS_OFFSET).unwrap();
|
||||
Celsius(self.0 - offset)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_deci_kelvins(self) -> DeciKelvins<V> {
|
||||
let multiplier = V::from_u8(DECI).unwrap();
|
||||
DeciKelvins(self.0 * multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Decikelvins ----------------------------------
|
||||
quantity_type! {DeciKelvins, "dK"}
|
||||
|
||||
impl<V: Value> DeciKelvins<V> {
|
||||
#[inline]
|
||||
pub fn to_kelvins(self) -> Kelvins<V> {
|
||||
let divisor = V::from_u8(DECI).unwrap();
|
||||
Kelvins(self.0 / divisor)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Degrees Celsius ----------------------------------
|
||||
quantity_type! {Celsius, "℃"}
|
||||
|
||||
impl <V: Value> Celsius<V> {
|
||||
#[inline]
|
||||
pub fn to_kelvins(self) -> Kelvins<V> {
|
||||
//TODO: Make const
|
||||
let offset = V::from_f64(KELVIN_CELSIUS_OFFSET).unwrap();
|
||||
Kelvins(self.0 + offset)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn to_deci_celsius(self) -> DeciCelsius<V> {
|
||||
let multiplier = V::from_u8(DECI).unwrap();
|
||||
DeciCelsius(self.0 * multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Deci Degrees Celsius ----------------------------------
|
||||
quantity_type! {DeciCelsius, "d℃"}
|
||||
|
||||
impl<V: Value> DeciCelsius<V> {
|
||||
#[inline]
|
||||
pub fn to_celsius(self) -> Celsius<V> {
|
||||
let divisor = V::from_u8(DECI).unwrap();
|
||||
Celsius(self.0 / divisor)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Tests ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use float_cmp::assert_approx_eq;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn convert_f32() {
|
||||
let kelvins: Kelvins<f32> = 298.15.kelvins();
|
||||
let deci_kelvins: DeciKelvins<f32> = 2_981.5.deci_kelvins();
|
||||
let celsius: Celsius<f32> = 25.0.celsius();
|
||||
let deci_celsius: DeciCelsius<f32> = 250.0.deci_celsius();
|
||||
|
||||
assert_approx_eq!(f32, kelvins.to_celsius().0, celsius.0);
|
||||
assert_approx_eq!(f32, celsius.to_kelvins().0, kelvins.0);
|
||||
|
||||
assert_approx_eq!(f32, deci_kelvins.to_kelvins().0, kelvins.0);
|
||||
assert_approx_eq!(f32, kelvins.to_deci_kelvins().0, deci_kelvins.0);
|
||||
|
||||
assert_approx_eq!(f32, deci_celsius.to_celsius().0, celsius.0);
|
||||
assert_approx_eq!(f32, celsius.to_deci_celsius().0, deci_celsius.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_u16() {
|
||||
let kelvins: Kelvins<u16> = 298.kelvins();
|
||||
let celsius: Celsius<u16> = 25.celsius();
|
||||
|
||||
assert_eq!(celsius.0, kelvins.to_celsius().0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
use generate_quantity::quantity_type;
|
||||
use crate::quantity::{Quantity, Value};
|
||||
|
||||
const VOLT_MV_RATIO: u16 = 1_000;
|
||||
|
||||
//----- Volts ----------------------------------
|
||||
quantity_type! {Volts, "V"}
|
||||
|
||||
impl<V: Value> Volts<V> {
|
||||
#[inline]
|
||||
pub fn to_milli_volts(self) -> MilliVolts<V> {
|
||||
let multiplier = V::from_u16(VOLT_MV_RATIO).unwrap();
|
||||
MilliVolts(self.0 * multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Millivolts ----------------------------------
|
||||
quantity_type! {MilliVolts, "mV"}
|
||||
|
||||
impl<V: Value> MilliVolts<V> {
|
||||
pub fn to_volts(self) -> Volts<V> {
|
||||
let divisor = V::from_u16(VOLT_MV_RATIO).unwrap();
|
||||
Volts(self.0 / divisor)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Tests ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use float_cmp::assert_approx_eq;
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn convert_u32() {
|
||||
let volts: Volts<u32> = 3.volts();
|
||||
let millivolts: MilliVolts<u32> = 3_000.milli_volts();
|
||||
|
||||
assert_eq!(volts.to_milli_volts().0, millivolts.0);
|
||||
assert_eq!(millivolts.to_volts().0, volts.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_f64() {
|
||||
let volts: Volts<f64> = 3.0.volts();
|
||||
let millivolts: MilliVolts<f64> = 3_000.0.milli_volts();
|
||||
|
||||
assert_approx_eq!(f64, volts.to_milli_volts().0, millivolts.0);
|
||||
assert_approx_eq!(f64, millivolts.to_volts().0, volts.0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
use crate::quantity::{Quantity, Value};
|
||||
use generate_quantity::quantity_type;
|
||||
|
||||
use crate::quantity::MILLI;
|
||||
|
||||
//----- Liters ----------------------------------
|
||||
quantity_type! {Liters, "L"}
|
||||
|
||||
impl<V: Value> Liters<V> {
|
||||
#[inline]
|
||||
pub fn to_milli_liters(self) -> MilliLiters<V> {
|
||||
let multiplier = V::from_u16(MILLI).unwrap();
|
||||
MilliLiters(self.0 * multiplier)
|
||||
}
|
||||
}
|
||||
|
||||
//----- Milliliters ----------------------------------
|
||||
quantity_type! {MilliLiters, "mL"}
|
||||
|
||||
impl<V: Value> MilliLiters<V> {
|
||||
#[inline]
|
||||
pub fn to_liters(self) -> MilliLiters<V> {
|
||||
let divisor = V::from_u16(MILLI).unwrap();
|
||||
MilliLiters(self.0 / divisor)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
// ----- Tests ------------------------
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn convert_u32() {
|
||||
let liters: Liters<u32> = 12.liters();
|
||||
let milli_liters: MilliLiters<u32> = 12_000.milli_liters();
|
||||
|
||||
assert_eq!(liters.0, milli_liters.to_liters().0);
|
||||
assert_eq!(milli_liters.0, liters.to_milli_liters().0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
use crate::quantity::{Quantity, Value};
|
||||
use generate_quantity::quantity_type;
|
||||
|
||||
//----- Liters per Minute ----------------------------------
|
||||
quantity_type! {LitersPerMinute, "L/min"}
|
||||
Reference in New Issue
Block a user