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 if let Ok(value) = subfeat.get_value() {
154 if (-100.0..=150.0).contains(&value) {
155 vals.push(config_scale.from_celsius(value));
156 } else {
157 eprintln!(
158 "Temperature ({value}) outside of range ([-100, 150])"
159 );
160 }
161 }
162 }
163 }
164 }
165 }
166 Ok(vals)
167 })
168 .await
169 .error("Failed to join tokio task")??;
170
171 let min_temp = temp
172 .iter()
173 .min_by(|a, b| a.partial_cmp(b).unwrap())
174 .cloned()
175 .unwrap_or(0.0);
176 let max_temp = temp
177 .iter()
178 .max_by(|a, b| a.partial_cmp(b).unwrap())
179 .cloned()
180 .unwrap_or(0.0);
181 let avg_temp = temp.iter().sum::<f64>() / temp.len() as f64;
182
183 let mut widget = Widget::new().with_format(format.clone());
184
185 widget.state = match max_temp {
186 x if x <= good => State::Good,
187 x if x <= idle => State::Idle,
188 x if x <= info => State::Info,
189 x if x <= warn => State::Warning,
190 _ => State::Critical,
191 };
192
193 widget.set_values(map! {
194 "icon" => Value::icon_progression_bound("thermometer", max_temp, good, warn),
195 "average" => Value::degrees(avg_temp),
196 "min" => Value::degrees(min_temp),
197 "max" => Value::degrees(max_temp),
198 });
199
200 api.set_widget(widget)?;
201
202 select! {
203 _ = sleep(config.interval.0) => (),
204 _ = api.wait_for_update_request() => (),
205 Some(action) = actions.recv() => match action.as_ref() {
206 "toggle_format" => {
207 if let Some(format_alt) = &mut format_alt {
208 std::mem::swap(format_alt, &mut format);
209 }
210 }
211 _ => (),
212 }
213 }
214 }
215}