wlan_common/
energy.rs
1use core::ops::{Add, AddAssign, Sub, SubAssign};
6
7#[derive(PartialEq, PartialOrd, Eq, Hash, Debug, Clone, Copy)]
8pub struct DecibelMilliWatt(pub i8);
9
10impl Add for DecibelMilliWatt {
14 type Output = Self;
15
16 fn add(self, rhs: Self) -> Self::Output {
17 (FemtoWatt::from(self) + FemtoWatt::from(rhs)).into()
18 }
19}
20
21impl AddAssign for DecibelMilliWatt {
22 fn add_assign(&mut self, rhs: Self) {
23 *self = (FemtoWatt::from(*self) + FemtoWatt::from(rhs)).into();
24 }
25}
26
27impl Sub for DecibelMilliWatt {
28 type Output = Self;
29
30 fn sub(self, rhs: Self) -> Self::Output {
31 (FemtoWatt::from(self) - FemtoWatt::from(rhs)).into()
32 }
33}
34
35impl SubAssign for DecibelMilliWatt {
36 fn sub_assign(&mut self, rhs: Self) {
37 *self = (FemtoWatt::from(*self) - FemtoWatt::from(rhs)).into();
38 }
39}
40
41#[derive(PartialEq, Debug, Clone, Copy)]
42pub struct FemtoWatt(pub u64);
43
44impl Add for FemtoWatt {
45 type Output = Self;
46
47 fn add(self, rhs: Self) -> Self::Output {
48 Self(self.0 + rhs.0)
49 }
50}
51
52impl AddAssign for FemtoWatt {
53 fn add_assign(&mut self, rhs: Self) {
54 *self = Self(self.0 + rhs.0);
55 }
56}
57
58impl Sub for FemtoWatt {
59 type Output = Self;
60
61 fn sub(self, rhs: Self) -> Self::Output {
62 Self(if self.0 > rhs.0 { self.0 - rhs.0 } else { 0 })
63 }
64}
65
66impl SubAssign for FemtoWatt {
67 fn sub_assign(&mut self, rhs: Self) {
68 *self = Self(if self.0 > rhs.0 { self.0 - rhs.0 } else { 0 });
69 }
70}
71
72impl From<FemtoWatt> for DecibelMilliWatt {
73 fn from(fw: FemtoWatt) -> Self {
75 if fw.0 == 0 {
77 DecibelMilliWatt(std::i8::MIN)
78 } else {
79 let dbm = 10.0 * ((fw.0 as f64).log10() - 12.0);
80 DecibelMilliWatt(dbm.round() as i8)
81 }
82 }
83}
84
85impl From<DecibelMilliWatt> for i8 {
86 fn from(dbm: DecibelMilliWatt) -> i8 {
87 dbm.0
88 }
89}
90
91fn clamp<T: PartialOrd>(v: T, lower: T, upper: T) -> T {
93 if v < lower {
94 lower
95 } else if v > upper {
96 upper
97 } else {
98 v
99 }
100}
101
102fn fp_exp2(x_fp: u64) -> u64 {
105 const A_FP: u64 = 256;
107 const B_FP: u64 = 177;
108 const C_FP: u64 = 61;
109 const D_FP: u64 = 13;
110 const E_FP: u64 = 3;
111
112 let mut r_fp = D_FP + ((x_fp * E_FP) >> 8);
115 r_fp = C_FP + ((x_fp * r_fp) >> 8);
116 r_fp = B_FP + ((x_fp * r_fp) >> 8);
117 r_fp = A_FP + ((x_fp * r_fp) >> 8);
118 r_fp
119}
120
121impl From<DecibelMilliWatt> for FemtoWatt {
122 fn from(dbm: DecibelMilliWatt) -> Self {
129 let dbm = clamp(dbm.0, -120, 48) as i16;
130 const T_FP: u64 = 85;
145 let c = (120 + dbm) as u64;
146 let x_fp = c * T_FP;
149
150 let a = 1_u64 << (x_fp >> 8_u64);
154 let b_fp = fp_exp2(x_fp & 0xFF_u64);
155 Self((a * b_fp) >> 8)
158 }
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 pub fn fp_exp2_maximum_error() {
167 let mut x = 0.0;
168 while x < 1.0 {
169 x += 0.1;
170
171 let approx = fp_exp2((x * 256.0) as u64) as f64 / 256.0;
172 let actual = 2.0_f64.powf(x);
173 let error = (approx - actual).abs() / actual;
174 if error > 0.02 {
175 panic!("exceeded maximum expected error of 2% for: {} (error: {})", x, error);
176 }
177 }
178 }
179
180 #[test]
181 pub fn dbm_femtowatt_conversion() {
182 for dbm in -100_i8..=48 {
183 let approx_fw: FemtoWatt = DecibelMilliWatt(dbm).into();
184
185 let actual_fw = 10_f64.powf((dbm as f64) / 10.0) * 1e+12;
187 let error = (approx_fw.0 as f64 - actual_fw).abs() / actual_fw;
188 if error > 0.03 {
189 panic!("exceeded maximum expected error of 3% for: {} (error: {})", dbm, error);
190 }
191
192 assert_eq!(dbm, DecibelMilliWatt::from(approx_fw).0);
194 }
195 }
196
197 #[test]
198 pub fn dbm_femtowatt_conversion_bounds() {
199 for dbm in std::i8::MIN..=-120 {
200 let actual_fw: FemtoWatt = DecibelMilliWatt(dbm).into();
201 let expected_fw: FemtoWatt = DecibelMilliWatt(-120).into();
202 assert_eq!(actual_fw.0, expected_fw.0);
203 }
204
205 for dbm in 48..=std::i8::MAX {
206 let actual_fw: FemtoWatt = DecibelMilliWatt(dbm).into();
207 let expected_fw: FemtoWatt = DecibelMilliWatt(48).into();
208 assert_eq!(actual_fw.0, expected_fw.0);
209 }
210 }
211
212 #[test]
213 pub fn dbm_femtowatt_maximum_size() {
214 for dbm in std::i8::MIN..=-std::i8::MAX {
215 let actual_fw: FemtoWatt = DecibelMilliWatt(dbm).into();
216 assert!(actual_fw.0 < (1_u64 << 56));
217 }
218 }
219
220 #[test]
221 pub fn add_sub_dbm() {
222 let dbm = DecibelMilliWatt(-28) + DecibelMilliWatt(13);
225 assert_eq!(dbm.0, 13);
226
227 let dbm = DecibelMilliWatt(13) - DecibelMilliWatt(10);
230 assert_eq!(dbm.0, 10);
231
232 let dbm = DecibelMilliWatt(15) - DecibelMilliWatt(10);
235 assert_eq!(dbm.0, 13);
236
237 assert_eq!((DecibelMilliWatt(1) - DecibelMilliWatt(2)).0, -128);
239 }
240
241 #[test]
242 pub fn add_sub_fwatt() {
243 let fw = FemtoWatt(10) + FemtoWatt(20);
244 assert_eq!(fw.0, 30);
245 let mut fw = FemtoWatt(10);
246 fw += FemtoWatt(20);
247 assert_eq!(fw.0, 30);
248
249 let fw = FemtoWatt(20) - FemtoWatt(10);
250 assert_eq!(fw.0, 10);
251 let mut fw = FemtoWatt(20);
252 fw -= FemtoWatt(10);
253 assert_eq!(fw.0, 10);
254
255 let fw = FemtoWatt(10) - FemtoWatt(20);
256 assert_eq!(fw.0, 0);
257 let mut fw = FemtoWatt(10);
258 fw -= FemtoWatt(20);
259 assert_eq!(fw.0, 0);
260 }
261}