//! Static board config: rail labels + display limits. Zero deps: simple //! `KEY = VALUE` lines, `#` comments, blank lines ignored. Unknown keys are //! ignored (forward compatibility). Loaded once at startup. /// Which hwmon input of the SIO chip maps to what. `(key, label)` pairs; /// the key is the sysfs stem, e.g. "in0" → `in0_input`. pub struct Rail { pub input: String, pub label: String, /// Inputs not connected on this board report garbage (floating) values; /// hide them unless the user maps them. pub hide: bool, } pub struct Config { pub rails: Vec, pub vcore_limit: f64, pub temp_warn: f64, pub temp_crit: f64, pub vcore_bar_max: f64, pub power_bar_max: f64, } impl Default for Config { fn default() -> Self { Config { rails: default_rails(), vcore_limit: 1.403, temp_warn: 80.0, temp_crit: 95.0, vcore_bar_max: 1.50, power_bar_max: 200.0, } } } /// Best-guess rail map for ASRock Z690M ITX/ax (nct6798). Voltage evidence /// from 2026-08-29 probe; labels are GUESSES except in0/12 (load-tested) — /// verify against the BIOS HW monitor and fix in the config file. pub fn default_rails() -> Vec { vec![ rail("in0", "CPU Vcore", false), rail("in1", "VCCSA?", false), rail("in2", "+5V?", false), rail("in3", "+3.3V", false), rail("in4", "VCCIN?", false), rail("in5", "", true), rail("in6", "", true), rail("in7", "+5V?", false), rail("in8", "3VSB", false), rail("in9", "VCCIO?", false), rail("in10", "", true), rail("in11", "", true), rail("in12", "VCCSA?", false), rail("in13", "", true), rail("in14", "DRAM VDD", false), ] } fn rail(input: &str, label: &str, hide: bool) -> Rail { Rail { input: input.to_string(), label: label.to_string(), hide, } } /// Parse config text over a default Config. Lines: /// rail.in12.label = VCCSA /// rail.in5.hide = true /// vcore_limit = 1.35 fn apply_line(cfg: &mut Config, line: &str) { let line = line.split('#').next().unwrap_or("").trim(); if line.is_empty() { return; } let Some((key, val)) = line.split_once('=') else { return; }; let key = key.trim(); let val = val.trim(); match key { "vcore_limit" => cfg.vcore_limit = val.parse().unwrap_or(cfg.vcore_limit), "temp_warn" => cfg.temp_warn = val.parse().unwrap_or(cfg.temp_warn), "temp_crit" => cfg.temp_crit = val.parse().unwrap_or(cfg.temp_crit), "vcore_bar_max" => cfg.vcore_bar_max = val.parse().unwrap_or(cfg.vcore_bar_max), "power_bar_max" => cfg.power_bar_max = val.parse().unwrap_or(cfg.power_bar_max), "rail.hide" => return, // needs input suffix; handled below _ => {} } if let Some(rest) = key.strip_prefix("rail.") { match rest.split_once('.') { Some((input, "label")) => set_rail(cfg, input, val, None), Some((input, "hide")) => set_rail(cfg, input, "", Some(val == "true")), _ => {} } } } fn set_rail(cfg: &mut Config, input: &str, label: &str, hide: Option) { if let Some(r) = cfg.rails.iter_mut().find(|r| r.input == input) { if !label.is_empty() { r.label = label.to_string(); } if let Some(h) = hide { r.hide = h; } } else { cfg.rails.push(Rail { input: input.to_string(), label: label.to_string(), hide: hide.unwrap_or(false), }); } } pub fn load() -> Config { let mut cfg = Config::default(); // Search order: ./adlermon.conf then ~/.config/adlermon/adlermon.conf let candidates = [ Some(std::path::PathBuf::from("adlermon.conf")), dirs_home().map(|h| h.join(".config/adlermon/adlermon.conf")), ]; for path in candidates.into_iter().flatten() { if let Ok(text) = std::fs::read_to_string(&path) { for line in text.lines() { apply_line(&mut cfg, line); } break; } } cfg } fn dirs_home() -> Option { std::env::var_os("HOME").map(std::path::PathBuf::from) } #[cfg(test)] mod tests { use super::*; #[test] fn parses_rail_label() { let mut cfg = Config::default(); apply_line(&mut cfg, "rail.in12.label = CPU Vccio # note"); assert_eq!(cfg.rails.iter().find(|r| r.input == "in12").unwrap().label, "CPU Vccio"); } #[test] fn parses_hide_and_limits() { let mut cfg = Config::default(); apply_line(&mut cfg, "rail.in5.hide = true"); apply_line(&mut cfg, "vcore_limit = 1.35"); assert!(cfg.rails.iter().find(|r| r.input == "in5").unwrap().hide); assert_eq!(cfg.vcore_limit, 1.35); } #[test] fn tolerates_junk() { let mut cfg = Config::default(); apply_line(&mut cfg, "not a config line"); apply_line(&mut cfg, "unknown_key = 42"); apply_line(&mut cfg, "vcore_limit = notanumber"); assert_eq!(cfg.vcore_limit, 1.403); } }