use crate::device::macos_native::get_thermal_state;
use crate::device::macos_native::smc::SmcConnection;
use crate::device::{ChassisInfo, ChassisReader, FanInfo};
use crate::utils::get_hostname;
use chrono::Local;
use std::collections::HashMap;
use std::sync::Mutex;
pub struct IntelMacChassisReader {
hostname: String,
smc: Mutex<SmcConnection>,
}
impl Default for IntelMacChassisReader {
fn default() -> Self {
Self::new()
}
}
impl IntelMacChassisReader {
pub fn new() -> Self {
Self {
hostname: get_hostname(),
smc: Mutex::new(SmcConnection::default()),
}
}
}
#[derive(Debug, Default)]
struct SmcSnapshot {
total_power_watts: Option<f64>,
cpu_power_watts: Option<f64>,
fans: Vec<FanInfo>,
}
impl ChassisReader for IntelMacChassisReader {
fn get_chassis_info(&self) -> Option<ChassisInfo> {
let snapshot = self.read_smc();
let thermal_pressure = get_thermal_state().as_str().to_string();
let mut detail = HashMap::new();
detail.insert("platform".to_string(), "Intel Mac".to_string());
detail.insert("api".to_string(), "Native (SMC)".to_string());
if let Some(cpu_power) = snapshot.cpu_power_watts {
detail.insert("cpu_power_watts".to_string(), format!("{cpu_power:.2}"));
}
if snapshot.total_power_watts.is_some() {
detail.insert(
"power_source".to_string(),
"SMC PSTR (approximate)".to_string(),
);
}
let hostname = self.hostname.clone();
Some(ChassisInfo {
host_id: hostname.clone(),
hostname: hostname.clone(),
instance: hostname,
total_power_watts: snapshot.total_power_watts,
inlet_temperature: None, outlet_temperature: None, thermal_pressure: Some(thermal_pressure),
fan_speeds: snapshot.fans,
psu_status: Vec::new(), detail,
time: Local::now().format("%Y-%m-%d %H:%M:%S").to_string(),
})
}
}
impl IntelMacChassisReader {
fn read_smc(&self) -> SmcSnapshot {
let Ok(mut guard) = self.smc.lock() else {
return SmcSnapshot::default();
};
let Some(smc) = guard.get() else {
return SmcSnapshot::default();
};
SmcSnapshot {
total_power_watts: smc.get_system_power(),
cpu_power_watts: smc.get_cpu_package_power(),
fans: smc
.get_fan_readings()
.into_iter()
.map(|fan| FanInfo {
id: fan.index,
name: fan.name(),
speed_rpm: fan.actual_rpm,
max_rpm: fan.max_rpm,
})
.collect(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intel_mac_chassis_reader_creation() {
let reader = IntelMacChassisReader::new();
assert!(!reader.hostname.is_empty());
}
#[test]
fn always_reports_thermal_pressure_even_without_smc_power() {
let reader = IntelMacChassisReader::new();
let info = reader
.get_chassis_info()
.expect("Intel Mac chassis info is always produced");
assert!(info.thermal_pressure.is_some());
assert_eq!(
info.detail.get("platform").map(String::as_str),
Some("Intel Mac")
);
assert!(info.inlet_temperature.is_none());
assert!(info.psu_status.is_empty());
}
#[test]
fn power_source_detail_tracks_the_power_reading() {
let reader = IntelMacChassisReader::new();
let info = reader.get_chassis_info().expect("chassis info");
assert_eq!(
info.total_power_watts.is_some(),
info.detail.contains_key("power_source")
);
}
}