Skip to main content

rfham_core/
power.rs

1//! Transmit / receive power type.
2//!
3//! [`Power`] wraps a [`uom`](https://docs.rs/uom) SI watt quantity with ham-radio-centric
4//! constructors and a smart `Display` that selects the most readable unit.
5//!
6//! # Display formats
7//!
8//! The default formatter (`{}`) uses watts. The alternate formatter (`{:#}`) selects milliwatts,
9//! watts, or kilowatts based on the value:
10//!
11//! ```rust
12//! use rfham_core::power::Power;
13//!
14//! assert_eq!(Power::watts(5.0).to_string(),        "5 W");
15//! assert_eq!(format!("{:#}", Power::watts(5.0)),    "5 watts");
16//! assert_eq!(format!("{:#}", Power::milliwatts(0.5)), "0.5 milliwatts"); // < 0.001 W
17//! assert_eq!(format!("{:#}", Power::kilowatts(1.5)), "1.5 kilowatts");
18//! ```
19//!
20//! # Examples
21//!
22//! ```rust
23//! use rfham_core::power::Power;
24//!
25//! // Derived from DC circuit: P = V × I
26//! let p = Power::from_dc_circuit(13.8, 10.0); // 13.8 V × 10 A
27//! assert!((p.value() - 138.0).abs() < 1e-9);
28//!
29//! // Derived from AC circuit: P = V × I × PF
30//! let p = Power::from_ac_circuit(120.0, 5.0, 0.85);
31//! assert!((p.value() - 510.0).abs() < 1e-9);
32//! ```
33
34use crate::error::CoreError;
35use serde::{Deserialize, Serialize};
36use std::{fmt::Display, str::FromStr};
37use uom::{
38    fmt::DisplayStyle,
39    si::{f64::Power as BasePower, power as power_unit},
40};
41
42// ------------------------------------------------------------------------------------------------
43// Public Types
44// ------------------------------------------------------------------------------------------------
45
46#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Deserialize, Serialize)]
47pub struct Power(BasePower);
48
49// ------------------------------------------------------------------------------------------------
50// Public Functions
51// ------------------------------------------------------------------------------------------------
52
53// ------------------------------------------------------------------------------------------------
54// Implementations
55// ------------------------------------------------------------------------------------------------
56
57impl Display for Power {
58    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59        if f.alternate() {
60            if self.value() < 0.001 {
61                self.0
62                    .into_format_args(power_unit::milliwatt, DisplayStyle::Description)
63                    .fmt(f)
64            } else if self.value() >= 1000.0 {
65                self.0
66                    .into_format_args(power_unit::kilowatt, DisplayStyle::Description)
67                    .fmt(f)
68            } else {
69                self.0
70                    .into_format_args(power_unit::watt, DisplayStyle::Description)
71                    .fmt(f)
72            }
73        } else {
74            self.0
75                .into_format_args(power_unit::watt, DisplayStyle::Abbreviation)
76                .fmt(f)
77        }
78    }
79}
80
81impl FromStr for Power {
82    type Err = CoreError;
83
84    fn from_str(s: &str) -> Result<Self, Self::Err> {
85        BasePower::from_str(s)
86            .map(Self)
87            .map_err(|_| CoreError::InvalidValueFromStr(s.to_string(), "Power"))
88    }
89}
90
91impl From<BasePower> for Power {
92    fn from(value: BasePower) -> Self {
93        Self(value)
94    }
95}
96
97impl From<f64> for Power {
98    fn from(value: f64) -> Self {
99        Self::watts(value)
100    }
101}
102
103impl From<Power> for BasePower {
104    fn from(value: Power) -> Self {
105        value.0
106    }
107}
108
109impl From<Power> for f64 {
110    fn from(value: Power) -> Self {
111        value.0.value
112    }
113}
114
115impl AsRef<BasePower> for Power {
116    fn as_ref(&self) -> &BasePower {
117        &self.0
118    }
119}
120
121impl Power {
122    #[inline(always)]
123    pub fn milliwatts(value: f64) -> Self {
124        Self(BasePower::new::<power_unit::milliwatt>(value))
125    }
126
127    #[inline(always)]
128    pub fn watts(value: f64) -> Self {
129        Self(BasePower::new::<power_unit::watt>(value))
130    }
131
132    #[inline(always)]
133    pub fn kilowatts(value: f64) -> Self {
134        Self(BasePower::new::<power_unit::kilowatt>(value))
135    }
136
137    #[inline(always)]
138    pub fn from_dc_circuit(voltage: f64, current: f64) -> Self {
139        Self::watts(voltage * current)
140    }
141
142    #[inline(always)]
143    pub fn from_ac_circuit(voltage: f64, current: f64, factor: f64) -> Self {
144        assert!((0.0..=1.0).contains(&factor));
145        Self::watts(voltage * current * factor)
146    }
147
148    pub const fn value(&self) -> f64 {
149        self.0.value
150    }
151}
152
153// ------------------------------------------------------------------------------------------------
154// Public Functions
155// ------------------------------------------------------------------------------------------------
156
157pub fn watts(value: f64) -> Power {
158    Power::watts(value)
159}
160
161pub fn milliwatts(value: f64) -> Power {
162    Power::milliwatts(value)
163}
164
165pub fn kilowatts(value: f64) -> Power {
166    Power::kilowatts(value)
167}
168
169// ------------------------------------------------------------------------------------------------
170// Unit Tests
171// ------------------------------------------------------------------------------------------------
172
173#[cfg(test)]
174mod tests {
175    use super::Power;
176    use pretty_assertions::assert_eq;
177
178    #[test]
179    fn test_display_watts() {
180        assert_eq!("5 W", &Power::watts(5.0).to_string());
181        assert_eq!("100 W", &Power::watts(100.0).to_string());
182    }
183
184    #[test]
185    fn test_alternate_display() {
186        // milliwatt branch: value < 0.001 W
187        assert_eq!("0.5 milliwatts", &format!("{:#}", Power::milliwatts(0.5)));
188        // watt branch: 0.001 W ≤ value < 1000 W
189        assert_eq!("5 watts", &format!("{:#}", Power::watts(5.0)));
190        // kilowatt branch: value ≥ 1000 W
191        assert_eq!("1.5 kilowatts", &format!("{:#}", Power::kilowatts(1.5)));
192    }
193
194    #[test]
195    fn test_from_dc_circuit() {
196        let p = Power::from_dc_circuit(13.8, 10.0);
197        assert!((p.value() - 138.0).abs() < 1e-9);
198    }
199
200    #[test]
201    fn test_from_ac_circuit() {
202        let p = Power::from_ac_circuit(120.0, 5.0, 0.85);
203        assert!((p.value() - 510.0).abs() < 1e-9);
204    }
205
206    #[test]
207    #[should_panic]
208    fn test_from_ac_circuit_factor_above_one_panics() {
209        Power::from_ac_circuit(120.0, 5.0, 1.5);
210    }
211
212    #[test]
213    fn test_from_f64_is_watts() {
214        let p: Power = 100.0_f64.into();
215        assert_eq!(p.value(), Power::watts(100.0).value());
216    }
217}