i3status_rs/blocks/
temperature.rs1use super::prelude::*;
60use crate::util::celsius_to_fahrenheit;
61use sensors::FeatureType::SENSORS_FEATURE_TEMP;
62use sensors::Sensors;
63use sensors::SubfeatureType::SENSORS_SUBFEATURE_TEMP_INPUT;
64
65const DEFAULT_GOOD: f64 = 20.0;
66const DEFAULT_IDLE: f64 = 45.0;
67const DEFAULT_INFO: f64 = 60.0;
68const DEFAULT_WARN: f64 = 80.0;
69
70#[derive(Deserialize, Debug, SmartDefault)]
71#[serde(default)]
72pub struct Config {
73 #[serde(flatten)]
74 pub formats: MaybeMultiFormatConfig,
75 #[default(5.into())]
76 pub interval: Seconds,
77 pub scale: TemperatureScale,
78 pub good: Option<f64>,
79 pub idle: Option<f64>,
80 pub info: Option<f64>,
81 pub warning: Option<f64>,
82 pub chip: Option<String>,
83 pub inputs: Option<Vec<String>>,
84}
85
86#[derive(Deserialize, Debug, SmartDefault, Clone, Copy, PartialEq, Eq)]
87#[serde(rename_all = "lowercase")]
88pub enum TemperatureScale {
89 #[default]
90 Celsius,
91 Fahrenheit,
92}
93
94impl TemperatureScale {
95 #[allow(clippy::wrong_self_convention)]
96 pub fn from_celsius(self, val: f64) -> f64 {
97 match self {
98 Self::Celsius => val,
99 Self::Fahrenheit => celsius_to_fahrenheit(val),
100 }
101 }
102
103 pub fn as_value(self, val: f64) -> Value {
104 match self {
105 Self::Celsius => Value::degrees_c(val),
106 Self::Fahrenheit => Value::degrees_f(val),
107 }
108 }
109}
110
111pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
112 let mut actions = api.get_actions()?;
113 api.set_default_actions(&[
114 (MouseButton::Left, None, "next_format"),
115 (MouseButton::Right, None, "prev_format"),
116 ])?;
117
118 let mut formats = config
119 .formats
120 .with_default(" $icon $average avg, $max max ")?;
121
122 let good = config
123 .good
124 .unwrap_or_else(|| config.scale.from_celsius(DEFAULT_GOOD));
125 let idle = config
126 .idle
127 .unwrap_or_else(|| config.scale.from_celsius(DEFAULT_IDLE));
128 let info = config
129 .info
130 .unwrap_or_else(|| config.scale.from_celsius(DEFAULT_INFO));
131 let warn = config
132 .warning
133 .unwrap_or_else(|| config.scale.from_celsius(DEFAULT_WARN));
134
135 loop {
136 let chip = config.chip.clone();
137 let inputs = config.inputs.clone();
138 let config_scale = config.scale;
139 let temp = tokio::task::spawn_blocking(move || {
140 let mut vals = Vec::new();
141 let sensors = Sensors::new();
142 let chips = match &chip {
143 Some(chip) => sensors
144 .detected_chips(chip)
145 .error("Failed to create chip iterator")?,
146 None => sensors.into_iter(),
147 };
148 for chip in chips {
149 for feat in chip {
150 if *feat.feature_type() != SENSORS_FEATURE_TEMP {
151 continue;
152 }
153 if let Some(inputs) = &inputs {
154 let label = feat.get_label().error("Failed to get input label")?;
155 if !inputs.contains(&label) {
156 continue;
157 }
158 }
159 for subfeat in feat {
160 if *subfeat.subfeature_type() == SENSORS_SUBFEATURE_TEMP_INPUT
161 && let Ok(value) = subfeat.get_value()
162 {
163 if (-100.0..=150.0).contains(&value) {
164 vals.push(config_scale.from_celsius(value));
165 } else {
166 eprintln!("Temperature ({value}) outside of range ([-100, 150])");
167 }
168 }
169 }
170 }
171 }
172 Ok(vals)
173 })
174 .await
175 .error("Failed to join tokio task")??;
176
177 let min_temp = temp
178 .iter()
179 .min_by(|a, b| a.partial_cmp(b).unwrap())
180 .cloned()
181 .unwrap_or(0.0);
182 let max_temp = temp
183 .iter()
184 .max_by(|a, b| a.partial_cmp(b).unwrap())
185 .cloned()
186 .unwrap_or(0.0);
187 let avg_temp = temp.iter().sum::<f64>() / temp.len() as f64;
188
189 let mut widget = Widget::new().with_format(formats.get_format());
190
191 widget.state = match max_temp {
192 x if x <= good => State::Good,
193 x if x <= idle => State::Idle,
194 x if x <= info => State::Info,
195 x if x <= warn => State::Warning,
196 _ => State::Critical,
197 };
198
199 widget.set_values(map! {
200 "icon" => Value::icon_progression_bound("thermometer", max_temp, good, warn),
201 "average" => config_scale.as_value(avg_temp),
202 "min" => config_scale.as_value(min_temp),
203 "max" => config_scale.as_value(max_temp),
204 });
205
206 api.set_widget(widget)?;
207
208 select! {
209 _ = sleep(config.interval.0) => (),
210 _ = api.wait_for_update_request() => (),
211 Some(action) = actions.recv() => match action.as_ref() {
212 "next_format" | "toggle_format" => {
213 formats.next_format();
214 }
215 "prev_format" => {
216 formats.prev_format();
217 }
218 _ => (),
219 }
220 }
221 }
222}