wlan_common/
energy.rs

1// Copyright 2019 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use core::ops::{Add, AddAssign, Sub, SubAssign};
6
7#[derive(PartialEq, PartialOrd, Eq, Hash, Debug, Clone, Copy)]
8pub struct DecibelMilliWatt(pub i8);
9
10// In the past dBm values were simply added up when computing a moving average rather than first
11// converting dBm to mWatt. To prevent such mistakes in the future, provide an implementation for
12// adding and subtracting dBm values.
13impl 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    /// Uses absolute value of femtoWatt.
74    fn from(fw: FemtoWatt) -> Self {
75        // Note: Negative power returns an invalid value.
76        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
91/// Rust's i8 clamp requires unstable library feature 'clamp'.
92fn 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
102/// Polynomial approximation of f(x)=2^x in 24:8 fixed point format minimizing the maximum relative
103/// error for fractional inputs in the range [0, 1] below 2%.
104fn fp_exp2(x_fp: u64) -> u64 {
105    // f(x) = A + x * (B + x * (C + x * (D + x * E)))
106    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    // Note: adjustment of scaling factor needed for multiplications in fixed point format if
113    // same scaling factor is used.
114    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    /// Converts dBm to femtoWatts by approximation.
123    ///
124    /// FemtoWatts are approximated and within a maximum relative error of < 3% for dBm inputs in
125    /// the range [-100, 48]. Inputs must lay within [-120, 48] dBm.
126    ///
127    /// Resulting femtoWatts are always less than 2^56.
128    fn from(dbm: DecibelMilliWatt) -> Self {
129        let dbm = clamp(dbm.0, -120, 48) as i16;
130        // mWatts = 10^(dBm / 10)
131        //        = 10^(0.1 * dBm)
132        // Femtowatts = 10^12 * mWatts
133        //            = 10^12 * 10^(0.1 * dBm)
134        //            = 10^(0.1 * (120 + dBm))
135        //
136        // Convert to base 2:
137        // 2^x = 10^(0.1 * (120 + dBm))
138        // log(2^x) = log(10^(0.1 * (120 + dBm)))
139        // xlog(2) = 0.1 * (120 + dBm) * log(10)
140        // x = 0.1 * (120 + dBm) * log(10) / log(2)
141        // x = C * T where T = 0.1 * log(10) / log(2)
142        //                 C = 120 + dBm
143        // T in 24:8 fixed point format: 0.1 * log(10)/log(2) << 8 = 85
144        const T_FP: u64 = 85;
145        let c = (120 + dbm) as u64;
146        // x in 24:8 fixed point format.
147        // Scaling factor is the product of the scaling factors of c & T_FP respectively.
148        let x_fp = c * T_FP;
149
150        // Fixed point pow-function of x:
151        // 2^x = a * b where a = 2^(integer part of C * t)
152        //                   b = 2^(fraction part of C * t)
153        let a = 1_u64 << (x_fp >> 8_u64);
154        let b_fp = fp_exp2(x_fp & 0xFF_u64);
155        // Scaling factor is the product of the scaling factors of a & b respectively.
156        // Convert back to regular integer representation.
157        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            // Test dbm -> femtoWatt
186            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            // Test femtoWatt -> dbm
193            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        // 0.00158489319 mWatt + 19.9526232 mWatt = 19.9542081 mWatt
223        // 19.9542081 mWatt = 13.000344971 dbm
224        let dbm = DecibelMilliWatt(-28) + DecibelMilliWatt(13);
225        assert_eq!(dbm.0, 13);
226
227        // 19.95262315 mWatt - 10 mWatt = 9.9526232 mWatt
228        // 9.9526232 mWatt = 9.9793756227 dbm
229        let dbm = DecibelMilliWatt(13) - DecibelMilliWatt(10);
230        assert_eq!(dbm.0, 10);
231
232        // 31.622776602 - 10 mWatt = 21.6227766 mWatt
233        // 21.6227766 mWatt = 13.349114613 dbm
234        let dbm = DecibelMilliWatt(15) - DecibelMilliWatt(10);
235        assert_eq!(dbm.0, 13);
236
237        // Avoid underflow
238        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}