use core::fmt;
use core::ops::{Add, Div, Mul, Neg, Sub};
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct Energy(pub f64);
impl Energy {
pub fn from_kwh(kwh: f64) -> Self {
Self(kwh * 1000.0)
}
pub fn to_kwh(self) -> f64 {
self.0 / 1000.0
}
pub fn to_power_w(self, dt_hours: f64) -> crate::units::Power {
crate::units::Power(self.0 / dt_hours)
}
}
impl fmt::Display for Energy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.4} Wh", self.0)
}
}
impl Add for Energy {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}
}
impl Sub for Energy {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self(self.0 - rhs.0)
}
}
impl Mul<f64> for Energy {
type Output = Self;
fn mul(self, rhs: f64) -> Self {
Self(self.0 * rhs)
}
}
impl Div<f64> for Energy {
type Output = Self;
fn div(self, rhs: f64) -> Self {
Self(self.0 / rhs)
}
}
impl Neg for Energy {
type Output = Self;
fn neg(self) -> Self {
Self(-self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct Capacity(pub f64);
impl fmt::Display for Capacity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.4} Ah", self.0)
}
}
impl Add for Capacity {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}
}
impl Sub for Capacity {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self(self.0 - rhs.0)
}
}
impl Mul<f64> for Capacity {
type Output = Self;
fn mul(self, rhs: f64) -> Self {
Self(self.0 * rhs)
}
}
impl Div<f64> for Capacity {
type Output = Self;
fn div(self, rhs: f64) -> Self {
Self(self.0 / rhs)
}
}
impl Neg for Capacity {
type Output = Self;
fn neg(self) -> Self {
Self(-self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct StateOfCharge(pub f64);
impl StateOfCharge {
pub fn new(value: f64) -> Self {
Self(value.clamp(0.0, 1.0))
}
pub fn as_percentage(self) -> f64 {
self.0 * 100.0
}
}
impl fmt::Display for StateOfCharge {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.1}%", self.as_percentage())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_energy_kwh() {
let e = Energy::from_kwh(1.0);
assert!((e.0 - 1000.0).abs() < 1e-10);
assert!((e.to_kwh() - 1.0).abs() < 1e-10);
}
#[test]
fn test_soc_clamping() {
assert_eq!(StateOfCharge::new(1.5).0, 1.0);
assert_eq!(StateOfCharge::new(-0.1).0, 0.0);
assert_eq!(StateOfCharge::new(0.5).0, 0.5);
}
#[test]
fn test_wh_to_joules_ratio() {
assert!((Energy(1.0).0 - 1.0).abs() < 1e-10);
assert!((Energy::from_kwh(1.0).0 - 1000.0).abs() < 1e-10);
let joules = Energy(1.0).0 * 3600.0;
assert!((joules - 3600.0).abs() < 1e-10);
assert!((Energy(2.0).0 - 2.0).abs() < 1e-10);
}
#[test]
fn test_mwh_kwh_scaling() {
assert!((Energy::from_kwh(1000.0).0 - 1_000_000.0).abs() < 1e-10);
assert!((Energy::from_kwh(1000.0).to_kwh() - 1000.0).abs() < 1e-10);
}
#[test]
fn test_energy_addition() {
let sum = Energy(500.0) + Energy(700.0);
assert!((sum.0 - 1200.0).abs() < 1e-10);
let diff = Energy(700.0) - Energy(200.0);
assert!((diff.0 - 500.0).abs() < 1e-10);
}
#[test]
fn test_energy_comparison() {
assert!(Energy(100.0) < Energy(200.0));
assert!(Energy(500.0) > Energy(499.9));
assert!(Energy(0.0) == Energy(0.0));
}
#[test]
fn test_zero_energy() {
assert!((Energy::default().0 - 0.0).abs() < 1e-10);
let added = Energy(0.0) + Energy(100.0);
assert!((added.0 - 100.0).abs() < 1e-10);
let zeroed = Energy(50.0) - Energy(50.0);
assert!(zeroed.0.abs() < 1e-10);
}
#[test]
fn test_negative_energy_deficit() {
assert!((Energy(-500.0).0 - (-500.0)).abs() < 1e-10);
let neg = -Energy(300.0);
assert!((neg.0 - (-300.0)).abs() < 1e-10);
let deficit = Energy(100.0) - Energy(400.0);
assert!((deficit.0 - (-300.0)).abs() < 1e-10);
}
}