use super::prelude::*;
use sensors::FeatureType::SENSORS_FEATURE_TEMP;
use sensors::Sensors;
use sensors::SubfeatureType::SENSORS_SUBFEATURE_TEMP_INPUT;
const DEFAULT_GOOD: f64 = 20.0;
const DEFAULT_IDLE: f64 = 45.0;
const DEFAULT_INFO: f64 = 60.0;
const DEFAULT_WARN: f64 = 80.0;
#[derive(Deserialize, Debug, SmartDefault)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
pub format: FormatConfig,
pub format_alt: Option<FormatConfig>,
#[default(5.into())]
pub interval: Seconds,
pub scale: TemperatureScale,
pub good: Option<f64>,
pub idle: Option<f64>,
pub info: Option<f64>,
pub warning: Option<f64>,
pub chip: Option<String>,
pub inputs: Option<Vec<String>>,
}
#[derive(Deserialize, Debug, SmartDefault, Clone, Copy, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum TemperatureScale {
#[default]
Celsius,
Fahrenheit,
}
impl TemperatureScale {
#[allow(clippy::wrong_self_convention)]
pub fn from_celsius(self, val: f64) -> f64 {
match self {
Self::Celsius => val,
Self::Fahrenheit => val * 1.8 + 32.0,
}
}
}
pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
let mut actions = api.get_actions()?;
api.set_default_actions(&[(MouseButton::Left, None, "toggle_format")])?;
let mut format = config
.format
.with_default(" $icon $average avg, $max max ")?;
let mut format_alt = match &config.format_alt {
Some(f) => Some(f.with_default("")?),
None => None,
};
let good = config
.good
.unwrap_or_else(|| config.scale.from_celsius(DEFAULT_GOOD));
let idle = config
.idle
.unwrap_or_else(|| config.scale.from_celsius(DEFAULT_IDLE));
let info = config
.info
.unwrap_or_else(|| config.scale.from_celsius(DEFAULT_INFO));
let warn = config
.warning
.unwrap_or_else(|| config.scale.from_celsius(DEFAULT_WARN));
loop {
let chip = config.chip.clone();
let inputs = config.inputs.clone();
let config_scale = config.scale;
let temp = tokio::task::spawn_blocking(move || {
let mut vals = Vec::new();
let sensors = Sensors::new();
let chips = match &chip {
Some(chip) => sensors
.detected_chips(chip)
.error("Failed to create chip iterator")?,
None => sensors.into_iter(),
};
for chip in chips {
for feat in chip {
if *feat.feature_type() != SENSORS_FEATURE_TEMP {
continue;
}
if let Some(inputs) = &inputs {
let label = feat.get_label().error("Failed to get input label")?;
if !inputs.contains(&label) {
continue;
}
}
for subfeat in feat {
if *subfeat.subfeature_type() == SENSORS_SUBFEATURE_TEMP_INPUT
&& let Ok(value) = subfeat.get_value()
{
if (-100.0..=150.0).contains(&value) {
vals.push(config_scale.from_celsius(value));
} else {
eprintln!("Temperature ({value}) outside of range ([-100, 150])");
}
}
}
}
}
Ok(vals)
})
.await
.error("Failed to join tokio task")??;
let min_temp = temp
.iter()
.min_by(|a, b| a.partial_cmp(b).unwrap())
.cloned()
.unwrap_or(0.0);
let max_temp = temp
.iter()
.max_by(|a, b| a.partial_cmp(b).unwrap())
.cloned()
.unwrap_or(0.0);
let avg_temp = temp.iter().sum::<f64>() / temp.len() as f64;
let mut widget = Widget::new().with_format(format.clone());
widget.state = match max_temp {
x if x <= good => State::Good,
x if x <= idle => State::Idle,
x if x <= info => State::Info,
x if x <= warn => State::Warning,
_ => State::Critical,
};
widget.set_values(map! {
"icon" => Value::icon_progression_bound("thermometer", max_temp, good, warn),
"average" => Value::degrees(avg_temp),
"min" => Value::degrees(min_temp),
"max" => Value::degrees(max_temp),
});
api.set_widget(widget)?;
select! {
_ = sleep(config.interval.0) => (),
_ = api.wait_for_update_request() => (),
Some(action) = actions.recv() => match action.as_ref() {
"toggle_format" => {
if let Some(format_alt) = &mut format_alt {
std::mem::swap(format_alt, &mut format);
}
}
_ => (),
}
}
}
}