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