monitrs_core/model/
sensors.rs1use core::time::Duration;
8
9use crate::model::MetricState;
10use crate::units::Percent;
11
12#[derive(Clone, Debug, PartialEq)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize))]
15pub struct TemperatureReading {
16 pub label: Box<str>,
18 pub celsius: f32,
20 pub high_celsius: Option<f32>,
22 pub critical_celsius: Option<f32>,
24}
25
26impl TemperatureReading {
27 #[must_use]
34 pub fn is_critical(&self) -> Option<bool> {
35 self.critical_celsius
36 .map(|threshold| self.celsius >= threshold)
37 }
38}
39
40#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize))]
43#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
44pub enum ChargeState {
45 Charging,
47 Discharging,
49 Full,
51 NotCharging,
53 #[default]
55 Unknown,
56}
57
58impl ChargeState {
59 #[must_use]
61 pub const fn symbol(self) -> char {
62 match self {
63 Self::Charging => '+',
64 Self::Discharging => '-',
65 Self::Full => '=',
66 Self::NotCharging => '.',
67 Self::Unknown => '?',
68 }
69 }
70
71 #[must_use]
73 pub const fn label(self) -> &'static str {
74 match self {
75 Self::Charging => "charging",
76 Self::Discharging => "discharging",
77 Self::Full => "full",
78 Self::NotCharging => "not charging",
79 Self::Unknown => "unknown",
80 }
81 }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize))]
87pub struct BatterySnapshot {
88 pub charge: Percent,
90 pub state: ChargeState,
92 pub time_remaining: MetricState<Duration>,
94 pub cycle_count: MetricState<u32>,
96 pub health: MetricState<Percent>,
98}
99
100#[derive(Clone, Debug, PartialEq)]
102#[cfg_attr(feature = "serde", derive(serde::Serialize))]
103pub struct SensorSnapshot {
104 pub temperatures: MetricState<Vec<TemperatureReading>>,
106 pub battery: MetricState<BatterySnapshot>,
108}
109
110impl SensorSnapshot {
111 #[must_use]
113 pub const fn warming_up() -> Self {
114 Self {
115 temperatures: MetricState::WarmingUp,
116 battery: MetricState::WarmingUp,
117 }
118 }
119
120 #[must_use]
122 pub fn hottest(&self) -> Option<&TemperatureReading> {
123 self.temperatures
124 .fresh()?
125 .iter()
126 .max_by(|a, b| a.celsius.total_cmp(&b.celsius))
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 fn reading(label: &str, celsius: f32, critical: Option<f32>) -> TemperatureReading {
135 TemperatureReading {
136 label: label.into(),
137 celsius,
138 high_celsius: None,
139 critical_celsius: critical,
140 }
141 }
142
143 #[test]
144 fn criticality_is_unknown_without_a_sensor_reported_threshold() {
145 assert_eq!(reading("pkg", 95.0, None).is_critical(), None);
146 assert_eq!(reading("pkg", 95.0, Some(100.0)).is_critical(), Some(false));
147 assert_eq!(reading("pkg", 101.0, Some(100.0)).is_critical(), Some(true));
148 }
149
150 #[test]
151 fn missing_sensors_are_unsupported_not_zero_degrees() {
152 let sensors = SensorSnapshot::warming_up();
153 assert!(sensors.hottest().is_none());
154 assert!(sensors.temperatures.fresh().is_none());
155 }
156
157 #[test]
158 fn hottest_finds_the_maximum_reading() {
159 let sensors = SensorSnapshot {
160 temperatures: MetricState::Available(vec![
161 reading("efficiency", 44.0, None),
162 reading("performance", 78.5, None),
163 reading("ambient", 31.0, None),
164 ]),
165 battery: MetricState::Unsupported,
166 };
167 let hottest = sensors.hottest().expect("three readings");
168 assert_eq!(&*hottest.label, "performance");
169 }
170
171 #[test]
172 fn an_empty_sensor_list_has_no_hottest_reading() {
173 let sensors = SensorSnapshot {
174 temperatures: MetricState::Available(Vec::new()),
175 battery: MetricState::Unsupported,
176 };
177 assert!(sensors.hottest().is_none());
178 }
179
180 #[test]
181 fn charge_state_symbols_are_distinguishable_without_color() {
182 let mut symbols: Vec<char> = [
183 ChargeState::Charging,
184 ChargeState::Discharging,
185 ChargeState::Full,
186 ChargeState::NotCharging,
187 ChargeState::Unknown,
188 ]
189 .iter()
190 .map(|s| s.symbol())
191 .collect();
192 symbols.sort_unstable();
193 symbols.dedup();
194 assert_eq!(symbols.len(), 5);
195 }
196}