1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
//! Platform-specific types are required to implement the following traits.
use std::fmt::Debug;
use std::rc::Rc;
use num_traits::identities::Zero;
use uom::si::time::{day, hour};
use crate::units::{Bound, ElectricPotential, Energy, Power, Ratio, ThermodynamicTemperature, Time};
use crate::{Result, State, Technology};
pub trait BatteryManager: Debug + Sized {
type Iterator: BatteryIterator;
fn new() -> Result<Self>;
fn refresh(&self, battery: &mut <Self::Iterator as BatteryIterator>::Device) -> Result<()>;
}
pub trait BatteryIterator: Iterator<Item = Result<<Self as BatteryIterator>::Device>> + Debug + Sized {
type Manager: BatteryManager<Iterator = Self>;
type Device: BatteryDevice;
/// Iterator is required to store reference to the `Self::Manager` type,
/// even if it does not use it.
/// In that case all iterator instances will be freed before the manager.
///
/// Implemented `next()` for `<Self as Iterator>` must preload all needed battery data
/// in this method, because `BatteryDevice` methods are infallible.
fn new(manager: Rc<Self::Manager>) -> Result<Self>;
}
/// Underline type for `Battery`, different for each supported platform.
pub trait BatteryDevice: Sized + Debug {
fn state_of_health(&self) -> Ratio {
// It it possible to get values greater that `1.0`, which is logical nonsense,
// forcing the value to be in `0.0..=1.0` range
(self.energy_full() / self.energy_full_design()).into_bounded()
}
fn state_of_charge(&self) -> Ratio {
// It it possible to get values greater that `1.0`, which is logical nonsense,
// forcing the value to be in `0.0..=1.0` range
(self.energy() / self.energy_full()).into_bounded()
}
fn energy(&self) -> Energy;
fn energy_full(&self) -> Energy;
fn energy_full_design(&self) -> Energy;
fn energy_rate(&self) -> Power;
fn state(&self) -> State;
fn voltage(&self) -> ElectricPotential;
fn temperature(&self) -> Option<ThermodynamicTemperature>;
fn vendor(&self) -> Option<&str>;
fn model(&self) -> Option<&str>;
fn serial_number(&self) -> Option<&str>;
fn technology(&self) -> Technology;
fn cycle_count(&self) -> Option<u32>;
// Default implementation for `time_to_full` and `time_to_empty`
// uses calculation based on the current energy flow,
// but if device provides by itself provides these **instant** values (do not use average values),
// it would be easier and cheaper to return them instead of making some calculations
fn time_to_full(&self) -> Option<Time> {
let energy_rate = self.energy_rate();
match self.state() {
// In some cases energy_rate can be 0 while Charging, for example just after
// plugging in the charger. Assume that the battery doesn't have time_to_full in such
// cases, to avoid division by zero. See https://github.com/svartalf/rust-battery/pull/5
State::Charging if !energy_rate.is_zero() => {
// Some drivers might report that `energy_full` is lower than `energy`,
// but battery is still charging. What should we do in that case?
// As for now, assuming that battery is fully charged, since we can't guess,
// how much time left.
let energy_left = match self.energy_full() - self.energy() {
value if value.is_sign_positive() => value,
_ => return None,
};
let time_to_full = energy_left / energy_rate;
if time_to_full.get::<hour>() > 10.0 {
// Ten hours for charging is too much
None
} else {
Some(time_to_full)
}
}
_ => None,
}
}
fn time_to_empty(&self) -> Option<Time> {
let energy_rate = self.energy_rate();
match self.state() {
// In some cases energy_rate can be 0 while Discharging, for example just after
// unplugging the charger. Assume that the battery doesn't have time_to_empty in such
// cases, to avoid divison by zero. See https://github.com/svartalf/rust-battery/pull/5
State::Discharging if !energy_rate.is_zero() => {
let time_to_empty = self.energy() / energy_rate;
if time_to_empty.get::<day>() > 10.0 {
// Ten days for discharging is too much
None
} else {
Some(time_to_empty)
}
}
_ => None,
}
}
}